老早就想写这篇了,但一直没空,现在抽个空写写吧。
Motivation
编译期字符串Hash的想法源于一个常见做法,字符串比较。比如在KlayGE的effect系统中,有很多if (“cull_mode” == state_name)这样的比较。加速这个操作的方法之一就是hash。但是如果改成if (0x8F1E5E5F == state_name_hash)又非常不直观。如果能有一个编译期的hash方法,先称之为CT_HASH,就可以if(CT_HASH(“cull_mode”) == state_name_hash),既保证直观,又保证开发效率。
尝试
要在编译期做事情首先都会想到template,但是C++的template没法提供字符串的操作,就算boost::mpl::string,也必须把一个一个字符分开塞给它才行,就像这样:
boost::mpl::string<‘c’, ‘u’, ‘l’, ‘l’, ‘_’, ‘m’, ‘o’, ‘d’, ‘e’>
仍然不直观,所以template这条路走不通。
剩下的就是宏了。
inline size_t HASH_FUNCTION(size_t seed, char ch)
{
return seed ^ (ch + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}#define HASH_RECURSE_00(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_01(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_01(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_02(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_02(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_03(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_03(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_04(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_04(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_05(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_05(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_06(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_06(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_07(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_07(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_08(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_08(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_09(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_09(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_10(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_10(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_11(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_11(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_12(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_12(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_13(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_13(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_14(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_14(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_15(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_15(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_16(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_16(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_17(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_17(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_18(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_18(seed, str) (*(str + 1) == 0 ? HASH_FUNCTION((seed), *(str)) : HASH_RECURSE_19(HASH_FUNCTION((seed), *(str)), (str + 1)))
#define HASH_RECURSE_19(seed, str) HASH_FUNCTION((seed), *(str))#define CT_HASH(str) HASH_RECURSE_00(0, (str))
这里的HASH_FUNCTION来自于boost,其本身是否出现冲突的情况不属于本片讨论的范围。值得注意的是,如果HASH_FUNCTION用宏的方式来写,那么HASH_RECURSE到9个之后就会造成编译器heap溢出,必须写成函数的形式。
编译期?
这样的CT_HASH是编译期的吗?是也不是。在Release下,看汇编的结果可以发现
cout << CT_HASH(“cull_mode”) << endl;
被编译成了
00DC1103 mov eax,dword ptr [__imp_std::endl (0DC2054h)]
00DC1108 mov ecx,dword ptr [__imp_std::cout (0DC2058h)]
00DC110E push eax
00DC110F push 8F1E5E5Fh
00DC1114 mov dword ptr [ebp-18h],0Fh
00DC111B mov dword ptr [ebp-1Ch],ebx
00DC111E mov byte ptr [ebp-2Ch],bl
00DC1121 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0DC2048h)]
00DC1127 mov ecx,eax
00DC1129 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0DC2044h)]
也就是说CT_HASH(“cull_mode”)被编译成了常量8F1E5E5Fh。看似成功了,但是对于另一个更严格的常量测试
switch (state_name_hash)
{
case CT_HASH(“cull_mode”):
…
}
却失败了。也就是说CT_HASH(“cull_mode”)其实并不是编译期常量,而是优化器产生的。
结论
想要做一个编译期字符串Hash的尝试HLL地失败了:(,只能做到“优化期”常量的效果。如果各位看客有何高见的话不妨讨论讨论!
Comments