避免重复代码

发布于 2024-09-07 12:30:14 字数 589 浏览 3 评论 0原文

假设我有:

  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 中做同样的事情吗? 编辑:当然我想到了函数/宏/内联。但还有别的吗? 这也与性能无关。仅用于教育目的。 ;)

let's say I have:

  switch( choice ) {
  case A:   
     stmt;
     do_stmt_related2A;
  break;

  case B:
     stmt;
     do_stmt_related2B;
  break;

  case C: something_different();
   ...
  }

How could I avoid duplicating stmt code?

But is there any workaround?
gcc extension label as value looks quite good for such situation.

   switch( choice ) {
     do {
     case A:  ptr = &&A_label;
     break;
     case B:  ptr = &&B_label;
     } while(0);
              stmt;
              goto *ptr;
     case C: ...

Is there any trick that can do the same in ANSI-C?
Edit: Of course I have thought of function/macro/inline. But anything else?
It's not about performance either. Just for educational purpose. ;)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

玻璃人 2024-09-14 12:30:14

为什么不将 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 的把戏真的很可怕。我宁愿拥有可读的代码,也不愿拥有这样的怪物。

你应该始终假设继承你代码的程序员是一个杀人狂,他知道你住在哪里:-)

Why don't you just refactor stmt (I'm assuming this is a big chunk of instructions rather than a single line) into its own function do_stmt() and call it? Something like:

switch( choice ) {
    case A:
        do_stmt();
        do_stmt_related2A;
        break;
    case B:
        do_stmt();
        do_stmt_related2B;
        break;
    case C: something_different();
        ...
}

That gcc trick is truly hideous. I would rather have readable code over such monstrosities any day.

You should always assume that the programmer that inherits your code is a homicidal maniac who knows where you live :-)

暗藏城府 2024-09-14 12:30:14

如何避免重复 stmt 代码?

通过将其放入一个函数中并调用它。

而且,不,您知道这是否会减慢您的应用程序,直到您对其进行分析并发现它是瓶颈。 (如果确实如此,请使用宏,或者如果是 C99,则使函数内联。)

How could I avoid duplicating stmt code?

By putting it into a function and call that.

And, no, you do not know whether this will slow down your application until you profiled it and found it to be the bottleneck. (And in case it really is, use a macro or, if that's C99, make the function inline.)

纵山崖 2024-09-14 12:30:14

除了将通用代码重构为子函数的普遍观点之外,使用标准 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();
    ...
}

它清楚地表达了您想要执行的操作,并且避免了 switch-in-a-switch这会阻止某些编译器有效地优化代码。

Aside from the common sentiment to refactor the common code into a sub-function, the simplest way to refactor your code using standard C functionality is probably:

if (choice == A || choice == B) {
    stmt;
}

switch( choice ) {
  case A:   
    do_stmt_related2A;
    break;

  case B:
    do_stmt_related2B;
    break;

  case C:
    something_different();
    ...
}

It cleanly expresses what you want to do, and it avoids the switch-inside-a-switch that prevents some compilers from optimizing the code effectively.

灯角 2024-09-14 12:30:14

无论哪种方式都会有一些代码 - 您可以有重复的代码,或者避免重复的代码。所以我很好奇 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();
/* ... */
}

There will be some code either way - you can have duplicated code, or code to avoid duplication. So I'm curious as to how complex the stmt; code really is.

The simple, clean solution is to move the shared part (stmt) into a seperate function.

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();
/* ... */
}

Another solution (that might be acceptable, depending on your situation) is to nest branching statements:

swtich(choise) {
case A:
case B:
  stmt;
  if(choise == A) {
    do_stmt_related2A();
  } else {̈́
    do_stmt_related2B();
  }
  break;
case C:
  something_different();
/* ... */
}
相守太难 2024-09-14 12:30:14

我可能会做类似的事情:

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();
...

I'll probably do something like that :

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();
...
不语却知心 2024-09-14 12:30:14

我会选择类似我在这里附加的内容。当然,您自己可能有一个嵌套 switch 语句的想法,这没什么新鲜的。另外,它的作用是仅评估 choice 一次。

它还避免了标签地址的 gcc 构造,因此这里没有间接寻址。一个像样的编译器应该能够很好地优化这样的事情。

另请注意,my_choice 是一个 intmax_t,因此它应该与 choice 可能具有的任何类型兼容。

(放入 for 只是为了好玩,而且显然只适用于 C99。您可以用一个额外的 {} 来替换它,并只声明 my_choice C89。)

typedef enum { one = 1, two, three } number;

int main(int argc, char** argv) {

  number choice = (number)argc;

  for (intmax_t _0 = 0, my_choice = choice; !_0; ++_0)
    switch(my_choice) {
    case one:;
    case two:;
      {
        printf("Do whatever you want here\n");
        switch (my_choice) {
        case one:
          printf("test 1\n");
          break;
        case two:
          printf("test 2\n");
          break;
        }
        printf("end special\n");
      }
      break;
    default:;
      printf("test %d, default\n", choice);
    }
}

I'd go for something like what I append here. Sure, you probably had the idea yourself to have a nested switch statement, nothing new. What this does in addition is that it evaluates choice only once.

It also avoids the gcc construct of addresses of labels, so there is no indirection, here. A decent compiler should be able to optimize such a thing quite well.

Observe also, that my_choice is an intmax_t so this should be compatible with any type choice could have.

(Putting in the for is just for the fun, and would only work with C99, obviously. You could replace it with an extra {} around the stuff and just a declaration of my_choice for C89.)

typedef enum { one = 1, two, three } number;

int main(int argc, char** argv) {

  number choice = (number)argc;

  for (intmax_t _0 = 0, my_choice = choice; !_0; ++_0)
    switch(my_choice) {
    case one:;
    case two:;
      {
        printf("Do whatever you want here\n");
        switch (my_choice) {
        case one:
          printf("test 1\n");
          break;
        case two:
          printf("test 2\n");
          break;
        }
        printf("end special\n");
      }
      break;
    default:;
      printf("test %d, default\n", choice);
    }
}
愚人国度 2024-09-14 12:30:14

使用 goto 指针可能会导致代码变慢,因为它关闭了 gcc 的一些其他优化(或者我上次阅读它时会关闭)。 Gcc 本质上认为它可能太复杂而无法跟上可能发生的情况,并假设更多的分支指令可以针对已经 && 的每个 goto 标签。事实并非如此。如果您坚持尝试使用此方法,我建议您尝试使用整数和另一个 switch/case 而不是 goto。 Gcc 希望能够理解这一点。

除此之外,对于许多语句来说,这可能不值得付出努力,或者实际上可能按原来的方式工作得更好。它确实在很大程度上取决于 stmt 实际是什么。

如果 stmt 确实是昂贵的或大的代码,将 stmt 重构为 static 函数可能会产生良好的结果。

如果可以的话,您可以尝试的另一件事是将 stmt 从 switch/case 中提升出来,然后执行始终执行它。有时这是最便宜的方法,但这确实取决于 stmt 实际做什么。

您可以做的另一件事是重构所有 stmtdo_stmt_lated2A
,和 do_stmt_lated2A 全部放入文件 static 函数中,如下所示:

// 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:
    ...

stmt_f 末尾对延续函数的调用是尾调用,很可能会变成跳转而不是一个真正的电话。因为所有这些都是静态的,所以编译器可能会看到整个值集,这些值可能是延续函数并进行更多优化,但我不知道。

除非 stmt 非常大,否则这很可能是不值得的微优化。如果您确实想知道,那么您应该编译为程序集,并尝试查看编译器对原始代码的实际作用。它很可能比你想象的做得更好。

哦,您可以尝试的最后一件事是,如果您控制 A、B、C.. 可以采用的实际值,那么您可以确保具有相似前缀的值具有相邻的值。如果 A 和 B 确实彼此相邻,并且编译器决定需要将 switch/case 分解为几个不同的跳转表,那么它可能会将 A 和 B 放在同一个跳转表中,并且还会看到它们有相同的前缀并为您提升该代码。如果 C(不共享该前缀)不与 A 或 B 相邻,则这种情况更有可能发生,但您的整体代码可能会更糟。

Using goto pointers will likely result in slower code because it shuts off some of gcc's other optimizations (or would the last time I read up on it). Gcc essentially decides that it could be way too complicated to try to keep up with what might be going on and assumes that many more branch instructions can target every goto label that has been && than is really the case. If you insist on trying to use this method I suggest that you attempt to use an integer and another switch/case rather than the goto. Gcc will like be able to make sense of that.

Aside from that, for many statements this may not be worth the work or it may actually work better the way it is. It really does depend a lot on what stmt actually is.

Refactoring stmt into a static function will likely yield good results if stmt is indeed expensive or big code.

Another thing you might try if you can would be to hoist stmt out of the switch/case and just execute always execute it. Sometimes this is the cheapest way to go, but that really does depend on what stmt actually does.

Another thing you might do is refactor all of stmt , do_stmt_related2A
, and do_stmt_related2A all into file static functions, like this:

// 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:
    ...

The call to the continuation function at the end of stmt_f is a tail call and will most likely become a jump rather than a real call. Because all of these are static it is possible that the compiler would see the entire set of values which could be continuation functions and optimize some more, but I don't know that.

Unless stmt is very big it is very likely that this is a micro-optimization that just isn't worth it. If you really want to know then you should compile to assembly and try to see what the compiler really does with your original code. It may very well do a better job than you think on it.

Oh, one last thing you might try is if you control what actual values A, B, C.. can take then you could make sure that the ones with similar prefixes have adjacent values. If A and B really are next to each other and if the compiler decides that it needs to break up the switch/case into a few different jump tables then it will likely put A and B in the same jump table and also see that they have the same prefix and hoist that code out for you. This is more likely if C, which does not share that prefix, is not adjacent to A or B, but then your overall code could be worse.

謌踐踏愛綪 2024-09-14 12:30:14

您只需要两个控制结构。一个用于指示一阶指令的执行,第二个用于指示二阶指令的执行。

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();
        ...
}

还值得注意的是 switch 不太适合二阶控制结构,您可能希望使用更传统的条件分支。最后,关于 goto-label-pointer 问题的真正答案是,这就是子例程链接的用途。如果您的指令比单个表达式更复杂,那么您可以而且应该使用函数。

You simply need two control structures. One for dictating execution of the first order instructions, a second for the second order instructions.

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();
        ...
}

It's also worth noting that switch is less suitable for the second order control structure, you likely want to use a more traditional conditional branch. Lastly, the real answer to your question regarding goto-label-pointer is that this is what subroutine linkage is for. If your instructions are more complex than a single expression, then you can and should use a function.

难以启齿的温柔 2024-09-14 12:30:14

这是函数调用或辅助 switch 语句的一种替代方法:

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;
} 

但是,我不能说我真的推荐它作为一种编码风格。

Here's one alternate to function calls or secondary switch statements:

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;
} 

However, I can't say I really recommend it as a coding style.

酒与心事 2024-09-14 12:30:14

如果您使用的是 gcc,一种选择是嵌套函数。这些函数可以访问父函数的所有变量。

例如:

void foo(int bar) {

    int x = 0;
    void stmt(void) { //nested function!!
        x++;
        if (x == 8) {
            x = 0;
        }
    }

    switch( bar ) {
    case A:   
        stmt();
        do_stmt_related2A;
    break;

    case B:
        stmt();
        do_stmt_related2B;
    break;

    case C:
        something_different();
        ...
    break;
    }
}

由于这是一个 gcc 扩展,因此显然不应该将其用于可移植的代码。但它玩起来很有趣:)并且在某些情况下使代码更具可读性。

如果您有一个可执行堆栈,您甚至可以创建一个指向嵌套函数的指针。它很像 lambda 函数,除了可怕 - 或者非常有趣,这取决于你的观点。 (与所有指向“本地”对象的指针一样,当父函数退出时,该指针将变得无效!小心!)

If you're using gcc, one option is nested functions. These are functions that have access to all the variables of the parent function.

For example:

void foo(int bar) {

    int x = 0;
    void stmt(void) { //nested function!!
        x++;
        if (x == 8) {
            x = 0;
        }
    }

    switch( bar ) {
    case A:   
        stmt();
        do_stmt_related2A;
    break;

    case B:
        stmt();
        do_stmt_related2B;
    break;

    case C:
        something_different();
        ...
    break;
    }
}

Since this is a gcc extension, it should obviously not be used for code intended to be portable. But it can be fun to play with :) and in certain circumstances makes the code much more readable.

If you have an executable stack you can even create a pointer to a nested function. It's a lot like a lambda function, except horrible - or horribly fun, depending on your perspective. (Like all pointers to "local" objects, the pointer becomes invalid when the parent function exits! Beware!)

花开雨落又逢春i 2024-09-14 12:30:14

回到不同 case-value 的原始含义。情况A、B、C分别代表什么?如果 A 和 B 应该执行部分相同的代码,那么它们应该有一些相似之处。找到它们并用另一种方式表达它们。

下面的例子使这一点更加清楚。假设 A、B 和 C 是 3 种不同的动物,A 和 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;
  }

这样,如果稍后将第三个选项 D 添加到您的应用程序中,则忘记“stmt”重复代码的可能性会稍微小一些。

如果您确实想阻止函数调用(对 can_fly),并且 A、B 和 C 是数值常量,则可以使用位掩码。在此示例中,我们使用一位来指示动物可以飞行:

if (choice & BIT_CAN_FLY)
   {
   stmt;
   }

switch( choice )
  {
  case A:   
     do_stmt_related2A;
     break;
  case B:
     do_stmt_related2B;
     break;
  case C:
     something_different();
     break;
  }

Go back to the original meanings of the different case-values. What do cases A, B and C represent? If A and B should execute partially the same code, they should have some similarities. Find them and express them in another way.

The following example makes this clearer. Suppose that A, B and C are 3 different kinds of animals, and the duplicated code for A and B is actually typical for animals that can fly. You could then write your code like this:

if (can_fly(choice))
   {
   stmt;
   }

switch( choice )
  {
  case A:   
     do_stmt_related2A;
     break;
  case B:
     do_stmt_related2B;
     break;
  case C:
     something_different();
     break;
  }

That way, if a 3rd choice D is added later to your application, the chances of forgetting the "stmt" duplicated code is slightly smaller.

If you really want to prevent a function call (to can_fly), and A, B and C are numerical constants, you could make use of bitmasks. In this example, we use one bit to indicate the animal can fly:

if (choice & BIT_CAN_FLY)
   {
   stmt;
   }

switch( choice )
  {
  case A:   
     do_stmt_related2A;
     break;
  case B:
     do_stmt_related2B;
     break;
  case C:
     something_different();
     break;
  }
甜心小果奶 2024-09-14 12:30:14

您可以轻松地将 stmt 代码放入函数中,然后从您的案例或任何地方调用该函数,您可以根据需要多次调用该函数。

You can easily just put your stmt code into a function and call that function from your case or wherever, you can call a function how ever many times you want.

汹涌人海 2024-09-14 12:30:14

我只看到你的逻辑有两种可能的安排。

L1:
 val
 |
 |-------------------
 |        |         |
 A        B         C
 |        |         |
 stmt     stmt      crap
 |        |
 Afunk    Bfunk

L2:
 val
 |
 |-------------------
 stmt               |
 |---------         |
 |        |         |
 A        B         C
 |        |         |
 Afunk    Bfunk     crap

在 L1 中,使用 stmt 的内联函数,如果可以分支两次,则使用 L2。如果您正在谈论 src 代码重复(而不是二进制文件中的重复),则只需内联 func 即可解决您的问题。

i see only two possible arrangements of ur logic.

L1:
 val
 |
 |-------------------
 |        |         |
 A        B         C
 |        |         |
 stmt     stmt      crap
 |        |
 Afunk    Bfunk

L2:
 val
 |
 |-------------------
 stmt               |
 |---------         |
 |        |         |
 A        B         C
 |        |         |
 Afunk    Bfunk     crap

In L1, use inline function for stmt or use L2 if branching twice is ok. If you are talkin about src code duplication(as opposed to duplication in the binary), just inline func will solve ur problem.

江南烟雨〆相思醉 2024-09-14 12:30:14

其他人已经给出了这个答案,但我想用正式的语言来表述。

如果性能不是您关心的问题,那么您已经正确识别了一种典型的“代码味道”,即 重复代码

最基本的重构之一称为提取方法。学习它,生活它,热爱它。识别重复代码并消除它的过程应该成为您每分钟工作流程的一部分。

Others have already given this answer, but I wanted to state it in formal terms.

If performance isn't your concern, then you've correctly identified one of the typical "code smells" which is duplicate code.

One of the most basic refactorings is called Extract Method. Learn it, live it, love it. The process of identifying duplicate code and eliminating it should be part of your minute-by-minute workflow.

偏爱自由 2024-09-14 12:30:14

您可能应该忽略以下一种可能性(但这很有趣):

// 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();
    ...
}

可能会或可能不会比其他解决方案更高效/更小/可读/建议/有趣。这是解决这个问题的一种有趣的方法,没有人提到过。

Here's a possibility that you should probably ignore (but that's kind of interesting):

// 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();
    ...
}

May or may not be more efficient/smaller/readable/advisable/fun than the other solutions. Just an interesting way to tackle this problem that no one else has mentioned.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文