GCC macro extension to call another macro

I am writing an application in C (gcc) that does a lot of string comparison. There is always one unknown / dynamic line with a long list of constant compile-time lines. Therefore, I decided that I was using a dynamic string and comparing the resulting hash with the previously computed hashes of constant strings.

I do this, I have a hash algorithm in the function (for dynamic execution strings) and as a macro, so gcc evaluates the hash at compile time.

I got it:

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

Thanks!

+7
c gcc macros
source share
3 answers

Unfortunately, in C, line decomposition is not a constant expression. The following is not valid:

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

Constant expressions in the preprocessor are similar. The following is not valid:

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

If you do not need to use your hash codes in switch , why not precompute these values ​​during the initialization phase of your program, save the results in variables, and then simply use them in tests?

If you need them in switch , I suggest splitting strings into characters. In your case, this will 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 */) 

The HASH_CALC macro expects a variable number of characters as arguments. For example, a valid code:

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

You can use 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, compiler optimization will depend on reducing this expression to a single numeric constant.

Update : run example using gcc. Well, 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("%d\n", HASH_CALCX("foo")); return 0; } 

Then gcc -S -O1 foox.c gives:

  .file "foox.c" .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "%d\n" .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 small improvement, I would definitely try adding a β€œclaim” compilation time to make sure that only character strings of a certain length are passed to the macro, because I'm 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 

ASSERT_zero() macro is similar to BUILD_BUG_ON_ZERO (), and uses the "SizeOf bit simulate. This gives either:

  • compilation error when e is false, or
  • zero value.

This ASSERT_zero() does not work for C ++. And this extra check will not work for VS IIRC because VS does not consider strlen("foo") as a compile-time constant.

+2
source share

While wondering what you can do with macros, is this the best / most convenient way to do this?

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

Your makefile can be configured to re-run the perl script every time the lines change.

+1
source share

All Articles