GCC macro expansion to call another macro

I am writing an application in C (gcc) which does a lot of string-comparison. Always one unknown/dynamic-string with a long list of compile-time constant strings. So I figured I hash the dynamic string and compare the resulting hash with precomputed hashes of the constant strings.

The do this I have the hash-algorithm in a function (for the dynamic runtime-strings) and as a macro so that gcc evaluates the hash during compile-time.

I got this:

#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")) {
    }
}

Now to the question:

Is it possible to create a macro which expands to HASH_CALCX(s) where X is the length of the constant string passed to the macro?

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

I tried this but it does not work.

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

Thank you!


Unfortunately, in C, string decomposition is not a constant expression. The following is invalid:

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

Constant expressions in preprocessor are similar. The following is invalid:

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

If you do not need to use your hash codes in switch statements why not to precompute those values during an initialization phase of your program, store results into variables and then simply use them in tests?

If you need them in switch statements I suggest to split strings into characters. In your case this would give macros like:

#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 */)

Macro HASH_CALC expects variable number of characters as arguments. For example, the following is valid code:

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

You could used the ternary operator:

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

For this to be viable would depend the compiler's optimization reducing this expression to a single numerical constant.

Update : Worked-out example using gcc. Fine if the optimization level is set to 1, but not for 0.

Let foox.c be:

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

Then gcc -S -O1 foox.c gives:

        .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

Update 2 : As a minor enhancement, I would definitely try to add a compile-time 'assert' to verify that only literal strings of a certain length are passed to the macro, because I am error-prone. For example, modify the above to read:

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

The ASSERT_zero() macro is similar to BUILD_BUG_ON_ZERO(), and uses the 'sizeof bitfield' trick. It yields either:

  • a compile error, when e is false, or
  • the value zero.
  • This ASSERT_zero() doesn't work for C++. And this extra check won't work for VS IIRC, because VS doesn't regard strlen("foo") as a compile-time constant.


    Although it is amazing what it is possible to do with macros, is that the best / most maintainable way to do this?

    I'd be inclined to put the strings in another file, and use a perl script to generate the hashes and output a c++ source file containing the strings and the hashes in some convenient data structure.

    Your makefile could be set up to rerun the perl script whenever the strings are changed.

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

    上一篇: 在另一个控制器中包含控制器

    下一篇: GCC宏扩展来调用另一个宏