C89 computed goto (again) how to

2019-07-08 20:17发布

I need to code an automata, and I bumped into this old need of a computed goto (ala fortran4 :) )

I need to code this in a portable ansi-C.

I want to stay away from the "don't do that", away from longjmp/setjmp, away from embedded ASM(), away from non ansi-C extensions.

Does anyone know how to do this?

标签: c goto
2条回答
太酷不给撩
2楼-- · 2019-07-08 20:43

I think I got it, I reviewed all the various threads on this topics and I started to agree that that there where no ansi C solutions, yet I found an way to do this that fit my needs. All solution I saw on stackoverflow where based on the idea to 'get' the addr of a label, then stuff it into a table, then index this table and goto, this is both with gcc/clang non ansi extension or the asm extension.

I gave it another try tonite and got this.

In an include file named cgoto.h I have this

#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

The usage is like this

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

In this implementation, the cost of jumping is 2 jumps and a switch() that is sequential, then optimisable. So this is reasonably performing compared to function call, a little less performing than &&label solution at the cost of portability.

With this implementation, labels code (semantic actions) are not confined into a switch() so we can implement jump table with shared semantic actions.

The index is assigned to a local goto_table_index, making the function using this re-entrant (multi threadable), though the optimiser can remove altogether this temp assignment.

The 1st Label in a jump table is 'special' (on this implementation) in the sense that it catch index out of bound, the first label is the 'error' label. If your code is bullet proof, i.e there is no way you can get an out of bound index, then the 1st label has not particular semantic.

CGOTO_dcl(gtb);

Declare the jump table 'gtb' own index as an auto integer so reentrant.

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

Define a jump table named gtb, labels can be entered/removed with L(label) so it is pretty convenient, and this is symbolic by nature, i.e the labels are name with a meaning. With #define as a switch() case, labels addition/suppression often mean #define renumbering that is a problem.

The #define can be separated from the CGOTO_def() but it make more sense to keep them together. The CGOTO_def() though got to be placed after the function local declaration as it contain a switch() that is code.

A uniq jump table can be used in multiple place in the function.

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

A label may be entered in multiple jump table

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

So all in all, this may looks ugly, yet, the jump table definition though 2 line the #define and the CGOTO_def() is manageable and practical, semi performant, and portable.

We are back to FTN4 :)

Cheers, Phi

查看更多
冷血范
3楼-- · 2019-07-08 20:51

Like I said in a comment, despite your plea to not use anything other than goto, standard C has nothing to offer.

Design your state appropriately, and pass a pointer to it to the handler functions for them to modify. That way the handler can setup the next function to call. Something like this:

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;
}
查看更多
登录 后发表回答