我正在用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的宏,其中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))
谢谢!
答案 0 :(得分:2)
您可以使用三元运算符:
#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("%d\n", HASH_CALCX("foo"));
return 0;
}
然后gcc -S -O1 foox.c
给出:
.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
更新2 :作为次要增强功能,我肯定会尝试添加一个编译时'断言'来验证只有 literal 将一定长度的字符串传递给宏,因为我容易出错。例如,将上面的内容修改为:
#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
为false或此ASSERT_zero()
不适用于C ++。此额外检查不适用于VS IIRC,因为VS不将strlen("foo")
视为编译时常量。
答案 1 :(得分:1)
不幸的是,在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'):
...
}
答案 2 :(得分:1)
虽然使用宏可以做到令人惊讶,但这是最好/最可维护的方法吗?
我倾向于将字符串放在另一个文件中,并使用perl脚本生成哈希值并输出一个c ++源文件,其中包含一些方便的数据结构中的字符串和哈希值。
可以设置makefile以在更改字符串时重新运行perl脚本。