【问题标题】:C89 computed goto (again) how toC89 计算 goto(再次)如何
【发布时间】:2017-05-07 13:56:23
【问题描述】:

我需要编写一个自动机,但我遇到了计算 goto 的旧需求(ala fortran4 :))

我需要在便携式 ansi-C 中编写代码。

我想远离“不要那样做”,远离 longjmp/setjmp,远离嵌入式 ASM(),远离非 ansi-C 扩展。

有人知道怎么做吗?

【问题讨论】:

  • 好吧,所谓的“计算 goto”仅作为 C 中的非可移植扩展存在。也许有一种更简洁的方法可以解决您的问题。有什么问题?
  • 在 ansi C 中没有这样的机制。正确设计您的状态表示并使用接受其地址的函数指针。
  • 顺便说一句,如果您希望符合 ANSI(即 C89)标准,您的标题具有误导性。
  • switch 被计算转到。
  • 我不知道什么是“计算的 goto”。我听起来有点讨厌,我猜我不需要知道它?

标签: c goto


【解决方案1】:

就像我在评论中所说,尽管您请求不要使用除 goto 以外的任何东西,但标准 C 没有任何可提供的功能。

适当地设计您的状态,并将指向它的指针传递给处理函数以供它们修改。这样处理程序就可以设置下一个要调用的函数。像这样的:

struct state;
typedef void state_func(struct state*);
#define NULL_ACTION_ADDRESS (state_func*)0

struct state {
    state_func    *action;
    int            value1;
    int            value2;
};

#define INIT_STATE { initial_action, -1, -1}

state_func initial_action;
state_func handle_a;
state_func handle_b;

int main(void) {
    struct state s = INIT_STATE;

    while(s.action != NULL_ACTION_ADDRESS) {
        (*s.action)(&s);
    }

    return 0;
}

void initial_action(struct state* ps) {
    ps->action = &handle_a;
}

void handle_a(struct state* ps) {
    ps->action = &handle_b;
}

void handle_b(struct state* ps) {
    ps->action = NULL_ACTION_ADDRESS;
}

【讨论】:

    【解决方案2】:

    我想我明白了,我查看了关于这个主题的所有不同主题,我开始同意那里没有 ansi C 解决方案,但我找到了一种满足我需求的方法。我在 stackoverflow 上看到的所有解决方案都基于“获取”标签的地址,然后将其填充到表中,然后索引该表并转到,这两者都具有 gcc/clang 非 ansi 扩展或 asm 扩展。

    我又试了一次,得到了这个。

    在一个名为 cgoto.h 的包含文件中,我有这个

    #ifndef CGOTO_dcl
    #define CGOTO_dcl(N) int CGOTO_##N
    #define CGOTO_LE(l) l,
    #define CGOTO_LG(l) case l:goto l;
    #define CGOTO_def(N)                                             \
      if(0){typedef enum {N(CGOTO_LE)} N; CGOTO_##N: switch(CGOTO_##N)\
      {N(CGOTO_LG) default:CGOTO_##N=0;goto CGOTO_##N;}}
    #define CGOTO(N,i) CGOTO_##N=i; goto CGOTO_##N;
    #endif
    

    用法是这样的

    #include <stdio.h>
    #include "cgoto.h"
    int f(int x)
    { //...
      CGOTO_dcl(gtb);
      //...
    # define gtb(L) L(l0) L(l1) L(l2)
      CGOTO_def(gtb);
      //...
    
      CGOTO(gtb,x);
      l0: printf("error\n");
      return(0);
    
      //...
    
      l1:return(11);
      l2:return(22);
      l3:return(33);
    }
    
    int main()
    { printf("f(0)=%d f(1)=%d f(2)=%d,f(3)=%d\n",f(0),f(1),f(2),f(3));
    }
    

    在这个实现中,跳转的成本是 2 次跳转和一个 switch() 是顺序的,然后是可优化的。因此,与函数调用相比,这是合理的性能,比 &&label 解决方案的性能稍差,但以可移植性为代价。

    通过这种实现,标签代码(语义动作)不再局限于 switch(),因此我们可以实现具有共享语义动作的跳转表。

    索引分配给本地 goto_table_index,使函数使用此可重入(多线程),尽管优化器可以完全删除此临时分配。

    跳转表中的第一个标签是“特殊的”(在此实现中),因为它捕获索引越界,第一个标签是“错误”标签。如果您的代码是防弹的,即您无法获得越界索引,则第一个标签没有特定的语义。

    CGOTO_dcl(gtb);
    

    将跳转表 'gtb' 自己的索引声明为可重入的自动整数。

      # define gtb(L) L(l0) L(l1) L(l2)
      CGOTO_def(gtb);
    

    定义一个名为 gtb 的跳转表,可以用 L(label) 输入/删除标签,非常方便,这本质上是象征性的,即标签是有含义的名称。使用 #define 作为 switch() 情况,添加/抑制标签通常意味着 #define 重新编号是一个问题。

    #define 可以与 CGOTO_def() 分开,但将它们放在一起更有意义。 CGOTO_def() 必须放在函数局部声明之后,因为它包含一个 switch() 代码。

    一个uniq跳转表可以在函数的多个地方使用。

    CGOTO(gtb,x);
    ...
    CGOTO(gtb,y);
    

    一个标签可以进入多个跳转表

    # define gtb1(L) L(l0) L(l1) L(l2)
      CGOTO_def(gtb1);
    # define gtb2(L) L(l0) L(l4) L(l5)
      CGOTO_def(gtb2);
    

    总而言之,这可能看起来很丑陋,但是,跳转表定义通过 2 行 #define 和 CGOTO_def() 是可管理且实用的、半高性能且可移植的。

    我们回到 FTN4 :)

    干杯, 飞

    【讨论】:

    • 这是一些非常可怕的意大利面条代码。一开始就需要一个“计算的 goto”,这很可能源于混乱的程序设计,所有这些疯狂的宏元编程都不能成为任何现有问题的合理解决方案。解决原始问题的实际方法几乎可以肯定是修复原始程序设计。
    • 话虽这么说......因为我们只是把所有好的编程实践都扔到了窗外......我无法理解 setjmp/longjmp 函数会变得更糟。
    • 这确实是一个非常有趣的解决方案。 case l: goto l 为编译器提供了足够的信息,它甚至可以优化为单次跳转。对不起所有的C“精英”;他们无法理解为什么有人问这样的问题。我什至有一个拥有超过 100,000 名代表的人告诉我“你不能在标题中定义函数”。
    猜你喜欢
    • 2011-11-27
    • 1970-01-01
    • 2016-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-09
    相关资源
    最近更新 更多