GCC宏扩展来调用另一个宏

我正在用C(gcc)编写一个应用程序,它进行了大量的字符串比较。 总是一个未知/动态字符串与一个长的编译时常量字符串列表。 所以我想我散列动态字符串并将结果散列与常量字符串的预计算散列进行比较。

做到这一点我有一个函数(用于动态运行时字符串)和一个宏的散列算法,以便gcc在编译期间评估散列值。

我懂了:

#define HASH_CALC(h, s) ((h) * 33 + *(s))
#define HASH_CALC1(s) (HASH_CALC(hash_calc_start, s))
#define HASH_CALC2(s) (HASH_CALC(HASH_CALC1(s), s + 1))
#define HASH_CALC3(s) (HASH_CALC(HASH_CALC2(s), s + 2))
#define HASH_CALC4(s) (HASH_CALC(HASH_CALC3(s), s + 3))
#define HASH_CALC5(s) (HASH_CALC(HASH_CALC4(s), s + 4))
//--> cut ... goes till HASH_CALC32

static const unsigned long hash_calc_start = 5381;
unsigned long hash_str (const char* c);

void func () {
    //This string is not constant ... just in this case to show something
    char dynStr = "foo";
    unsigned long dynHash = hash_str (dynStr);

    //gcc produces a cmp with a constant number as foo is hashed during compile-time
    if (dynHash == HASH_CALC3("foo")) {
    }
}

现在回答这个问题:

是否有可能创建一个扩展为HASH_CALCX(s)的宏,其中X是传递给宏的常量字符串的长度?

//Expands to HASH_CALC3("foo")
if (dynHash == HASH_CALCX("foo")) {
}
//Expands to HASH_CALC6("foobar")
if (dynHash == HASH_CALCX("foobar")) {
}

我试过这个,但它不起作用。

#define HASH_STRLEN(x) (sizeof(x)/sizeof(x[0])-1)
#define HASH_MERGE(x,y) x ## y
#define HASH_MERGE2(x,y) HASH_MERGE(x,y)
#define HASH_CALCX(s) (HASH_MERGE2(HASH_CALC, HASH_STRLEN(s))(s))

谢谢!


不幸的是,在C中,字符串分解不是一个常量表达式。 以下内容无效:

switch (x) {
case "a"[0]:
  ...
}

预处理器中的常量表达式类似。 以下内容无效:

#if "a"[0] == 'a'
  ...
#endif

如果你不需要在switch语句中使用你的哈希代码,为什么不在你的程序的初始化阶段预先计算这些值,将结果存储到变量中,然后简单地在测试中使用它们?

如果您在switch语句中需要它们,我建议将字符串拆分为字符。 在你的情况下,这会给宏如:

#define HASH_CALC(...) HASH_CALC0(5381, __VA_ARGS__, 0, 0, 0, 0, 0)
#define HASH_CALC0(p, x, ...) (x == 0 ? (p) : HASH_CALC1((p)*33+x, __VA_ARGS__)))
#define HASH_CALC1(p, x, ...) (x == 0 ? (p) : HASH_CALC2((p)*33+x, __VA_ARGS__)))
#define HASH_CALC2(p, x, ...) (x == 0 ? (p) : HASH_CALC3((p)*33+x, __VA_ARGS__)))
#define HASH_CALC3(p, x, ...) (x == 0 ? (p) : HASH_CALC4((p)*33+x, __VA_ARGS__)))

...
#define HASH_CALC64(p, x, ...) (x == 0 ? (p) : -1 /* shall never be used */)

HASH_CALC需要可变数量的字符作为参数。 例如,以下是有效的代码:

switch (x) {
case HASH_CALC('f', 'o', 'o'):
   ...
case HASH_CALC('f', 'o', 'o', 'b', 'a', 'r'):
   ...
}

你可以使用三元运算符:

#define HASH_CALCX(s)                
   (strlen(s) == 5 ? HASH_CALC5(s) : 
    strlen(s) == 4 ? HASH_CALC4(s) : 
    strlen(s) == 3 ? HASH_CALC3(s) : 
    strlen(s) == 2 ? HASH_CALC2(s) : 
    strlen(s) == 1 ? HASH_CALC1(s) : some_error)

为了这是可行的,将取决于编译器的优化将该表达式减少为单个数值常量。

更新 :使用gcc的例子。 很好,如果优化级别设置为1,但不是0。

让foox.c成为:

#include <string.h>
#include <stdio.h>

#define HASH_CALC(h, s) ((h) * 33 + *(s))
#define HASH_CALC1(s) (HASH_CALC(5381, s)) // hash_calc_start = 5381
#define HASH_CALC2(s) (HASH_CALC(HASH_CALC1(s), s + 1))
#define HASH_CALC3(s) (HASH_CALC(HASH_CALC2(s), s + 2))
#define HASH_CALC4(s) (HASH_CALC(HASH_CALC3(s), s + 3))
#define HASH_CALC5(s) (HASH_CALC(HASH_CALC4(s), s + 4))

#define HASH_CALCX(s)                
   (strlen(s) == 5 ? HASH_CALC5(s) : 
    strlen(s) == 4 ? HASH_CALC4(s) : 
    strlen(s) == 3 ? HASH_CALC3(s) : 
    strlen(s) == 2 ? HASH_CALC2(s) : 
    strlen(s) == 1 ? HASH_CALC1(s) : 0)

int main(void) {
   printf("%dn", HASH_CALCX("foo"));
   return 0;
}

然后gcc -S -O1 foox.c给出:

        .file       "foox.c"
        .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string     "%dn"
        .text
.globl main
        .type       main, @function
main:
        leal        4(%esp), %ecx
        andl        $-16, %esp
        pushl       -4(%ecx)
        pushl       %ebp
        movl        %esp, %ebp
        pushl       %ecx
        subl        $20, %esp
        movl        $193491849, 4(%esp)
        movl        $.LC0, (%esp)
        call        printf
        movl        $0, %eax
        addl        $20, %esp
        popl        %ecx
        popl        %ebp
        leal        -4(%ecx), %esp
        ret
        .size       main, .-main
        .ident      "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-54)"
        .section    .note.GNU-stack,"",@progbits

更新2 :作为次要的增强,我肯定会尝试添加一个编译时'assert'来验证只有一定长度的字符串被传递给宏,因为我很容易出错。 例如,修改上面的内容即可:

#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);}))
#define HASH_CALCX(s)  (ASSERT_zero(strlen(s) <= 5) + 
   (strlen(s) == 5 ? HASH_CALC5(s) : 
   etc

ASSERT_zero()宏与BUILD_BUG_ON_ZERO()类似,并使用'sizeof bitfield'技巧。 它会产生:

  • 编译错误,当e是错误的,或
  • 值为零。
  • 这个ASSERT_zero()不适用于C ++。 而且这个额外的检查对于VS IIRC不起作用,因为VS不把strlen("foo")当作编译时常量。


    尽管宏可以做什么是令人惊讶的,但是这是做到这一点的最佳/最可维护的方式吗?

    我倾向于将字符串放在另一个文件中,并使用perl脚本生成散列并在一些方便的数据结构中输出包含字符串和散列的c ++源文件。

    您的makefile可以设置为在字符串更改时重新运行perl脚本。

    链接地址: http://www.djcxy.com/p/76953.html

    上一篇: GCC macro expansion to call another macro

    下一篇: Benefits of using Native in Android