C 避免重复代码
假设我有:C 避免重复代码,c,c99,C,C99,假设我有: switch( choice ) { case A: stmt; do_stmt_related2A; break; case B: stmt; do_stmt_related2B; break; case C: something_different(); ... } 如何避免重复stmt代码 但是有什么解决办法吗? gcc扩展标签作为值在这种情况下看起来非常好 switch( choice
switch( choice ) {
case A:
stmt;
do_stmt_related2A;
break;
case B:
stmt;
do_stmt_related2B;
break;
case C: something_different();
...
}
如何避免重复stmt代码
但是有什么解决办法吗?
gcc扩展标签作为值在这种情况下看起来非常好
switch( choice ) {
do {
case A: ptr = &&A_label;
break;
case B: ptr = &&B_label;
} while(0);
stmt;
goto *ptr;
case C: ...
在ANSI-C中有什么技巧可以做到同样的事情吗?
编辑:当然我想到了函数/宏/内联。还有别的吗?
这也与性能无关。只是为了教育目的
如何避免重复stmt代码
通过将其放入函数并调用它
而且,不,您不知道这是否会降低您的应用程序的速度,直到您分析它并发现它是瓶颈。(如果确实是这样,使用宏,或者,如果是C99,将函数内联)为什么不重构
stmt
(我假设这是一大块指令,而不是一行)到它自己的函数do_stmt()
,然后调用它呢?比如:
switch( choice ) {
case A:
do_stmt();
do_stmt_related2A;
break;
case B:
do_stmt();
do_stmt_related2B;
break;
case C: something_different();
...
}
gcc的伎俩真是太可怕了。我宁愿有可读的代码,而不是这些怪物
您应该始终假设继承您代码的程序员是一个杀人狂,他知道您住在哪里:-)无论哪种方式都会有一些代码-您可以使用重复的代码,或者使用避免重复的代码。所以我很好奇stmt有多复杂代码>代码确实是
简单、干净的解决方案是将共享部分(stmt
)移动到单独的函数中
void do_shared_stmt(void) {
stmt;
}
/* .... */
swtich(choise) {
case A:
do_shared_stmt();
do_stmt_related2A();
break;
case B:
do_shared_stmt();
do_stmt_related2B();
break;
case C:
something_different();
/* ... */
}
另一种解决方案(可能是可以接受的,取决于您的情况)是嵌套分支语句:
swtich(choise) {
case A:
case B:
stmt;
if(choise == A) {
do_stmt_related2A();
} else {̈́
do_stmt_related2B();
}
break;
case C:
something_different();
/* ... */
}
isA=false;
switch( choice ) {
case A:
isA=true;
//nobreak
case B:
stmt;
isA ? do_stmt_related2A : do_stmt_related2B;
break;
case C:
something_different();
break;
}
我喜欢我在这里附加的东西。当然,您可能自己也想到了一个嵌套的switch语句,没有什么新的。此外,它只计算一次choice
它还避免了标签地址的gcc构造,因此这里没有间接寻址。一个好的编译器应该能够很好地优化这样的东西
还要注意,my_choice
是一个intmax\u t
,因此它应该与choice
可能具有的任何类型兼容
(为
输入只是为了好玩,显然,它只适用于C99。您可以在内容周围添加一个{}来代替它,只需声明C89的我的选择
。)
使用goto指针可能会导致代码速度变慢,因为它会关闭gcc的一些其他优化(或者在我上次阅读它时)。Gcc基本上认为,要跟上可能发生的事情可能太复杂了,并假设有更多的分支指令可以针对每个goto标签,而事实并非如此。如果您坚持尝试使用此方法,我建议您尝试使用整数和另一个开关/大小写,而不是goto。Gcc希望能够理解这一点
除此之外,对于许多声明来说,这可能不值得做这些工作,或者它实际上可能工作得更好。这在很大程度上取决于stmt实际上是什么
如果stmt
确实昂贵或代码庞大,那么将stmt
重构为静态
函数可能会产生良好的结果
如果可以的话,您可以尝试的另一件事是将stmt
从开关/机箱中取出,然后始终执行它。有时这是最便宜的方法,但这确实取决于stmt
的实际功能
您可能要做的另一件事是重构所有的stmt
,do\u stmt\u related2A
,和do_stmt\u related2A
全部进入文件静态
函数,如下所示:
// args in this is just a psudocode place holder for whatever arguments are needed, and
// not valide C code.
static void stmt_f(void (*continuation)(arg_list), args) {
stmt; // This corresponds almost exactly to stmt in your code
continuation(args);
}
static void do_stmt_related2A_f(args) {
do_stmt_related2A;
}
static void do_stmp_related2B_f(args) {
do_stmt_related2B;
}
...
switch (condition) {
case A:
stmt_f(do_stmt_related2A_f, args);
break;
case B:
...
if (can_fly(choice))
{
stmt;
}
switch( choice )
{
case A:
do_stmt_related2A;
break;
case B:
do_stmt_related2B;
break;
case C:
something_different();
break;
}
stmt_f末尾对continuation函数的调用是尾部调用,很可能会变成一个跳转而不是真正的调用。因为所有这些都是静态的,所以编译器可能会看到整个值集,这些值可以是连续函数,并进行更多优化,但我不知道这一点
除非stmt非常大,否则这很可能是一个不值得的微观优化。如果您真的想知道,那么您应该编译成汇编,并尝试查看编译器对原始代码的真正作用。它很可能比你想象的做得更好
哦,最后一件你可以尝试的事情是,如果你能控制A,B,C的实际值。。然后,您可以确保具有相似前缀的值具有相邻的值。如果A和B确实彼此相邻,并且如果编译器决定需要将开关/大小写分解为几个不同的跳转表,那么它可能会将A和B放在同一个跳转表中,并且还可以看到它们具有相同的前缀,并为您提取代码。如果不共享该前缀的C与A或B不相邻,则更可能出现这种情况,但总体代码可能更糟。您只需要两个控制结构。一个用于指令执行一阶指令,另一个用于指令执行二阶指令
switch (state) {
case A:
case B:
stmt;
switch (state) {
case A:
do_stmt_related2A;
break;
case B:
do_stmt_related2B;
break;
}
break;
case C:
something_different();
...
}
还值得注意的是,开关
不太适合二阶控制结构,您可能希望使用更传统的条件分支。最后,关于goto标签指针的问题的真正答案是,这就是子程序链接的用途。如果您的指令比单个表达式更复杂,那么您可以也应该使用函数。我可能会这样做:
void do_shared_stmt(void) {
stmt;
}
/* .... */
swtich(choise) {
case A:
do_shared_stmt();
do_stmt_related2A();
break;
case B:
do_shared_stmt();
do_stmt_related2B();
break;
case C:
something_different();
/* ... */
}
void do_stmt(int choice)
{
stmt;
switch(choice)
{
case A:
do_stmt_related2A;
break;
case B:
do_stmt_related2B;
break;
}
}
/* ... */
switch(choice)
{
case A:
case B:
do_stmt(choice);
break;
case C:
something_different();
...
下面是函数调用或辅助开关语句的一种替代方法:
swtich(choise) {
case A:
case B:
stmt;
if(choise == A) {
do_stmt_related2A();
} else {̈́
do_stmt_related2B();
}
break;
case C:
something_different();
/* ... */
}
isA=false;
switch( choice ) {
case A:
isA=true;
//nobreak
case B:
stmt;
isA ? do_stmt_related2A : do_stmt_related2B;
break;
case C:
something_different();
break;
}
但是,我不能说我真的推荐它作为一种编码风格。除了将通用代码重构为子函数的常见想法外,使用标准C功能重构代码的最简单方法可能是:
if (choice == A || choice == B) {
stmt;
}
switch( choice ) {
case A:
do_stmt_related2A;
break;
case B:
do_stmt_related2B;
break;
case C:
something_different();
...
}
它清晰地表达了您想要做的事情,并且避免了开关内部的开关,这种开关会阻止某些编译器有效地优化代码。如果您需要
L1:
val
|
|-------------------
| | |
A B C
| | |
stmt stmt crap
| |
Afunk Bfunk
L2:
val
|
|-------------------
stmt |
|--------- |
| | |
A B C
| | |
Afunk Bfunk crap
// function pointer to B function
void (*func)(void) = do_stmt_related2B;
switch(choice) {
case A:
func = do_stmt_related2A; // change function pointer to A function
case B:
stmt;
func(); // call function pointer
break;
case C:
something_different();
...
}