【问题标题】:"Local" labels in C and jump table implementationC 和跳转表实现中的“本地”标签
【发布时间】:2020-05-07 17:03:41
【问题描述】:

我正在尝试在 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:;

    }

}

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

【问题讨论】:

  • 同一个宏,还是同名的不同宏?
  • &&IO中的&&是什么意思?
  • 对于其他对此感到困惑的人:gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
  • 为什么不使用普通的switch 语句?
  • @Dr.Kameleon A switch 具有连续的情况应该吐出等效的代码,特别是如果你添加 default: __builtin_unreachable(); 试图哄编译器不要边界检查跳转表。编译器在开关上可能做得更好的一件事是,如果您为共享库编译,它可以将跳转表存储在const 存储中并作为偏移量而不是绝对标签。存储偏移量对 PIC 代码更友好(参见 Drepper 的如何编写共享库)。

标签: c macros goto dispatch jump-table


【解决方案1】:

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

标签需要在块的最开始声明

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

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

除非用__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 买不起。

【讨论】:

  • 这很好。我知道__label__。问题是:我可以声明一个数组并使用索引跳转到某个标签吗? (很像我的例子)
  • @Dr.Kameleon 是的。我已经为您的具体示例添加了一个基于 __label__ 的实现。
  • 我有一点疑问:GOTO 宏呢? (此必须采用数字值而不是标签值)
  • @Dr.Kameleon 我没有对您的 GOTO 宏的定义或使用做任何事情。它仍然需要一个数字。您的带有__label__ 声明标签的示例基本上是一个简单的switch,具有连续的cases,使用较低级别的原语(即标签作为值)实现。
【解决方案2】:

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

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

}

【讨论】:

    【解决方案3】:

    考虑将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),因为所有GOTO 和@987654326 @ 调用将能够被参数化。

    【讨论】:

    • 不需要,因为它在不同的功能中都能正常工作godbolt.org/z/aoA3XQ
    • @EricPostpischil 谢谢。无论如何,这个答案有其存在的理由,因为 1)比公认的解决方案更“通用”,2)添加第二个参数并不比在块中添加定义/#undefs 更“丑陋”。
    【解决方案4】:

    标签作为值只能在使用 gcc 编译时使用,因为它是 gcc 扩展。

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

    https://godbolt.org/z/jQvssU

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

    【讨论】:

    • 我知道它有效。但是,如果我再次使用相同的东西,就在我的代码下方,在一个单独的范围内怎么办? (顺便说一句,我目前正在尝试使用 Apple 的 Clang 将标签作为值,它也运行良好)
    • 请看我在原帖中的编辑。
    猜你喜欢
    • 1970-01-01
    • 2011-07-29
    • 1970-01-01
    • 1970-01-01
    • 2013-10-17
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多