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:
e
is false, or 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宏扩展来调用另一个宏