4

我正在尝试在 C 中创建一个基于宏的跳转表。

这是一些示例代码:

#include "stdio.h"

#define GOTO(X) static void* caseArg[] = {&&I0, &&R0, &&S0, &&F0, &&G0, &&H0}; \
    goto *caseArg[X];

#define FINISH() goto caseEnd;

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

    GOTO(1);

    I0: printf("in I0\n"); FINISH();
    R0: printf("in R0\n"); FINISH();
    S0: printf("in R0\n"); FINISH();
    F0: printf("in R0\n"); FINISH();
    G0: printf("in R0\n"); FINISH();
    H0: printf("in R0\n"); FINISH();

    caseEnd:;

}

可能的标签(I0、R0 等)必须相同。

问题是:我希望能够在同一源文件的不同范围部分中使用相同的宏。但是,编译器抱怨标签已定义。

我想要达到的目标:

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

     { // scope 1 

        GOTO(1);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;

    }

    { // scope 2

        GOTO(4);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;

    }

}

有任何想法吗?任何可能的解决方法?

4

4 回答 4

8

您需要__label__扩展(至少在 gcc、clang 和 tinycc 中),它允许您将标签范围限定为块。

标签需要在块的开头声明

__label__ I0, R0, S0, F0, G0, H0;

(连续__label__ I0; __label__ R0; ...的或两种形式的混合也可以。)。

除非用 声明了 scope-local,否则__label__C 标签的作用域是它们的封闭函数。

你的例子__label__

#include "stdio.h"

#define GOTO(X) static void* const caseArg[] = {&&I0, &&R0, &&S0, &&F0, &&G0, &&H0}; \
    goto *caseArg[X];

#define FINISH() goto caseEnd;

#define DECL_LBLS() __label__ I0, R0, S0, F0, G0, H0, caseEnd

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

    {   DECL_LBLS();

        GOTO(2);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in S0\n"); FINISH();
        F0: printf("in F0\n"); FINISH();
        G0: printf("in G0\n"); FINISH();
        H0: printf("in H0\n"); FINISH();
        caseEnd:;
    }

    {   DECL_LBLS();

        GOTO(1);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in S0\n"); FINISH();
        F0: printf("in F0\n"); FINISH();
        G0: printf("in G0\n"); FINISH();
        H0: printf("in H0\n"); FINISH();
        caseEnd:;
    } 
}

https://gcc.godbolt.org/z/63YSkG

在这种特殊情况下,这种基于本地标签的跳转表似乎比普通的旧switch.

于 2020-01-21T15:51:31.280 回答
1

这是一个非常丑陋的解决方案,但是如果您愿意为范围添加额外的前缀定义,您可以通过串联来做到这一点

#include "stdio.h"

// Helpers
#define CONCAT(a, b) CONCAT2(a, b)
#define CONCAT2(a, b) a ## b

// Label redirection
#define I0 CONCAT(PREFIX, I0)
#define R0 CONCAT(PREFIX, R0)
#define S0 CONCAT(PREFIX, S0)
#define F0 CONCAT(PREFIX, F0)
#define G0 CONCAT(PREFIX, G0)
#define H0 CONCAT(PREFIX, H0)
#define caseEnd CONCAT(PREFIX, caseEnd)

#define GOTO(X) static void* caseArg[] = {&&I0, &&R0, &&S0, &&F0, &&G0, &&H0}; \
    goto *caseArg[X];

#define FINISH() goto caseEnd;

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

     { // scope 1 
        #define PREFIX SCOPE1
        GOTO(1);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;
        #undef PREFIX

    }

    { // scope 2
        #define PREFIX SCOPE2
        GOTO(4);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;
        #undef PREFIX
    }

}
于 2020-01-21T16:06:56.797 回答
0

考虑向scope宏添加参数。

它会导致这样的事情:

#include "stdio.h"

#define GOTO(scope,X) static void* caseArg[] = {&&scope##_I0, &&scope##_R0, &&scope##_S0, &&scope##_F0, &&scope##_G0, &&scope##_H0}; \
    goto *caseArg[X];

#define FINISH(scope) goto scope##_caseEnd;

int main(int argc, char** argv)
{
  {
    GOTO(SCOPE_1, 1);

    SCOPE_1_I0: printf("in I0\n"); FINISH(SCOPE_1);
    SCOPE_1_R0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_S0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_F0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_G0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_H0: printf("in R0\n"); FINISH(SCOPE_1);

    SCOPE_1_caseEnd:;
  }
  {
    GOTO(SCOPE_2, 3);

    SCOPE_2_I0: printf("in I0\n"); FINISH(SCOPE_2);
    SCOPE_2_R0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_S0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_F0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_G0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_H0: printf("in R0\n"); FINISH(SCOPE_2);

    SCOPE_2_caseEnd:;
  }
}

这不是最佳的,但它适用于每个编译器。

请注意,如果要调用的块具有与问题中所示示例相同的模式,您甚至可以定义另一个要调用的宏作为FULL_MACRO(scope,X),因为所有GOTOFINISH调用都可以参数化。

于 2020-01-21T15:45:10.747 回答
-1

gcc仅当使用gcc 扩展编译时,才能使用标签作为值。

所以它可以100%正常工作。

https://godbolt.org/z/jQvssU

在此处输入图像描述

回答第二个问题 - 是的,您可以使用不同的功能:https ://godbolt.org/z/aoA3XQ

于 2020-01-21T15:41:02.750 回答