【问题标题】:Dynamic jump to label in C动态跳转到 C 中的标签
【发布时间】:2013-03-29 11:28:05
【问题描述】:

我想显示输出 - 数字 1 到 5,然后无限显示 4-5。有什么方法可以传递 i(4) 的值而不是 goto1 中的字符 i。或者是否有任何其他有效的方法来实现这一点,而无需像 switch 中那样说明所有选项(即案例 1:goto1(c1) 等)。

主要目的是跳转到在程序中计算其标签的语句。

#define goto1(i) \
goto c##i

int main(){    
    c1 : printf(" num is 1 \n");
    c2 : printf(" num is 2 \n");
    c3 : printf(" num is 3 \n");
    c4 : printf(" num is 4 \n");
    c5 : printf(" num is 5 \n");

    int i=4;
    goto1(i);
}

【问题讨论】:

  • 我推荐看看Definitive Book Guide
  • 如果你改一下标题会很有帮助,比如“动态跳转到 C 中的标签”之类的。
  • 您是在找麻烦吗 :) 或者您正试图进入黑暗时代,goto 总是很糟糕,即使您可以处理它。带有 goto 的小应用程序可能看起来不错,但大的应用程序会令人困惑,而且可能不合逻辑。 go to 总是可以组装一些硬件语言,但良好的编码 godo 似乎没用

标签: c


【解决方案1】:

如果你……喜欢冒险(或者我的意思是傻?),你可以使用 GCC 扩展 Labels as Values

6.3 标签作为值

您可以使用一元运算符“&&”获取当前函数(或包含函数)中定义的标签的地址。该值的类型为void *。该值是一个常量,可以在该类型的常量有效的任何地方使用。例如:

 void *ptr;
 /* ... */
 ptr = &&foo;

要使用这些值,您需要能够跳转到一个。这是通过计算的 goto 语句1goto *exp; 完成的。例如,

 goto *ptr;

任何void * 类型的表达式都是允许的。

使用这些常量的一种方法是初始化一个用作跳转表的静态数组:

 static void *array[] = { &&foo, &&bar, &&hack };

然后你可以选择一个带有索引的标签,像这样:

 goto *array[i];

请注意,这不会检查下标是否在边界内——C 中的数组索引从不这样做。

这样一个标签值数组的用途很像 switch 语句。 switch 语句更简洁,所以使用它而不是数组,除非问题不适合 switch 语句。

标签值的另一个用途是在线程代码的解释器中。解释器函数中的标签可以存储在线程代码中以实现超快速调度。

您不得使用此机制跳转到不同函数中的代码。如果你这样做,就会发生完全不可预测的事情。避免这种情况的最佳方法是将标签地址仅存储在自动变量中,并且永远不要将其作为参数传递。

上面例子的另一种写法是

 static const int array[] = { &&foo - &&foo, &&bar - &&foo,
                              &&hack - &&foo };
 goto *(&&foo + array[i]);

这对共享库中的代码更友好,因为它减少了所需的动态重定位次数,因此允许数据为只读。

如果包含函数被内联或克隆,则同一标签的&&foo 表达式可能具有不同的值。如果程序依赖于它们始终相同,则应使用__attribute__((__noinline__, __noclone__)) 来防止内联和克隆。如果在静态变量初始化器中使用&&foo,则禁止内联和克隆。


脚注

[1] Fortran 中的类似功能称为分配的 goto,但该名称在 C 中似乎不合适,在 C 中,人们可以做的不仅仅是将标签地址存储在标签变量中。

在任何情况下都不应将此视为使用该功能的建议。计算出的goto 最终从 Fortran 中删除;最好丢在历史的垃圾箱里。

【讨论】:

    【解决方案2】:

    您要跳台吗?如果你使用 gcc:它有一个跳转表机制。

    #include <stdio.h>
    
    int main()
    {
        unsigned char data[] = { 1,2,3,4,5,4,5,0 };
        // data to "iterate" over, must be 0-terminated in this example
    
        void *jump_table[] = { &&L00, &&L01, &&L02, &&L03, &&L04, &&L05 };
        // you should fill this with all 256 possible values when using bytes as p-code
    
        unsigned char *p = data;
    
        begin:
            goto *jump_table[ *p ];
    
        L00:
            return 0; // end app
        L01:
            printf("num %i\n", (int)*p);
            goto next;
        L02:
            printf("num %i\n", (int)*p);
            goto next;
        L03:
            printf("num %i\n", (int)*p);
            goto next;
        L04:
            printf("num %i\n", (int)*p);
            goto next;
        L05:
            printf("num %i\n", (int)*p);
            goto next;
        L06:
        L07:
        // ...
        LFF:
            goto next;
    
        next:
            ++p;            // advance the data pointer to the next byte
            goto begin;     // start over
    
        return 0;
    }
    

    这种方法的优点是省去了大的 switch 语句。

    【讨论】:

      【解决方案3】:

      为什么不这样做呢?

      #include <stdio.h>
      #include <stdlib.h>
      
      int main(void)
      {
          printf(" num is 1 \n");
          printf(" num is 2 \n");
          printf(" num is 3 \n");
      
          for (;;){
              printf(" num is 4 \n");
              printf(" num is 5 \n");
          }
      
          /* Not reachable, but will silence any compiler warnings about main
           * not returning a value. */
          return EXIT_SUCCESS;
      }
      

      【讨论】:

      • 很抱歉,但我希望使用 goto 本身或任何其他分支语句(如果可用)来执行此操作。主要目的是跳转到在程序中计算其标签的语句。
      • @ceedee 你能解释一下你为什么要使用goto吗?在这种情况下,如果 for 循环就足够了,为什么还要使用 goto
      • 主要目的是跳转到在程序中计算其标签的语句。我对满足我要求的任何其他选择持开放态度。谢谢..
      • 您可能正在寻找 C 标准中不存在的计算 goto,但 gcc 有一个扩展:gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Labels-as-Values.html 但是,我不建议使用它,除非您真的知道你在做什么。
      • @ceedee:转到标签仅在编译时存在。您不能跳转到名称在运行时计算的标签(至少在标准 C 中不是)。您需要做类似switch (i) { case 1: goto c1; case 2: goto c2; ... } 之类的操作,但这太愚蠢了!
      【解决方案4】:

      既然你想用错误的方式(又名。创意),你考虑过蹦床吗?

      #include <stdio.h>
      
      typedef void (*generic)(void);
      typedef generic (*continuation)(void);
      
      generic first(void);
      generic second(void);
      
      int main(void) {
          continuation fubar = first;
          for (;;) {
              fubar = (continuation) fubar();
          }
      }
      
      generic first(void) {
          printf(" num is 1 \n"
                 " num is 2 \n"
                 " num is 3 \n");
          return (generic) second;
      }
      
      generic second(void) {
          printf(" num is 4 \n"
                 " num is 5 \n");
          return (generic) second;
      }
      

      继续使用函数指针的想法(看看我在那里做了什么?Giggity!),您可以使用函数指针数组:

      #include <stdio.h>
      
      typedef size_t (*function)(size_t);
      
      size_t first(size_t);
      size_t second(size_t);
      
      int main(void) {
          function function[] = { first, first, first, first, second };
          size_t index = 0;
      
          for (;;) {
              index = function[index](index);
          }
      }
      
      size_t first(size_t index) {
          printf(" num is %d \n", ++index);
          return index;
      }
      
      size_t second(size_t index) {
          printf(" num is %d \n", index+1);
          return index-1;
      }
      

      【讨论】:

      • 这很好,但我认为这对 OP 没有帮助。
      • @OliCharlesworth:当然可以。要我演示一下吗?
      【解决方案5】:

      开关不会完成同样的事情吗?

      int main()
      {
          int i = 1;
          while (1)
          {
              switch (i)
              {
                  case 1:
                      printf(" num is 1 \n");
                  case 2:
                      printf(" num is 2 \n");
                  case 3:
                      printf(" num is 3 \n");
                  case 4:
                      printf(" num is 4 \n");
                  case 5:
                      printf(" num is 5 \n");
                  default:
                      break;
              }
      
              // code to calculate i
              i = 4;
              // end code to calculate i
          }
          return 0;
      }
      

      【讨论】:

      • 切换速度很慢。它被翻译成(伪汇编)if(i==0)...else if(i==1)...else ... if(i==10000)。跳转表更快。即使从像myfunctionarray[i]() 这样的数组调用函数也会更快。您仍然必须定义可能 10000 个函数并将它们插入到数组中;-) 普通的旧 goto 可能最好地完成任务。 (看看 tinyscheme,这是一个大而慢的 switch 语句的绝妙例子。)
      猜你喜欢
      • 1970-01-01
      • 2019-04-11
      • 2013-12-06
      • 2017-08-09
      • 2020-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多