如何仅使用 C 预处理器计算字符串文字的哈希值?
How to calculate the hash of a string literal using only the C preprocessor?
如果可能,我需要仅使用 C 预处理器来获取字符串校验和或散列(或类似的东西)。
用例如下:我在内存非常有限且 cpu 的嵌入式设备上进行错误记录。我想定义一个 LogError()
宏,它将 hash(__FILE__)
和 __LINE__
插入循环缓冲区(作为 16 位数字)。但是 hash(__FILE__)
需要编译成常量;如果实际文件名在程序中存储为字符串会占用太多内存。可以使用任何方法计算哈希值。
可以 #define FILE_ID
在每个文件的顶部加上一些唯一的编号,并在记录时使用它,但这不是首选的解决方案,它有一点维护成本。有没有更好的方法?
您对预处理器的要求过高。
最好只构建每个文件,并将其名称定义为预处理器的 16 位校验和
宏,正如@n.m建议的那样。
此解决方案的一个微不足道的困难是将您的手放在
16 位校验和实用程序。 GNU cksum
工具只是 32 位的。
但是 FreeBSD 有一个更好的,可以让你选择 16 位或 32 位。如果你的
开发系统是 Debian 的衍生产品
您可以通过以下方式获取:
sudo apt-get install freebsd-buildutils
然后运行:
dpkg-query -L freebsd-buildutils
查看 FreeBSD cksum
的安装位置。就我而言,它是:
/usr/bin/freebsd-cksum
但您可能会发现不同。
您通过传递选项指示 FreeBSD cksum
生成 16 位校验和
-o 1
。你可以查看它的手册页。
生成定义文件名的预处理器宏时要小心
校验和,您将校验和 定义为 16 位无符号整数 ,因为那是
你想要什么。如果它以普通十进制数字形式出现,例如,
它默认为 signed int,这可能会让您大吃一惊。
这是 GNU 解决方案的草图 make
:
main.c
#include <stdio.h>
#include <stdint.h>
int main(int argc, char **argv)
{
printf("%hu\n",FILE_CHKSUM);
return 0;
}
生成文件
.phony: all clean
SRCS = main.c
OBJS = $(SRCS:.c=.o)
# Take the 1st word of the output of `echo <filename> | freebsd-cksum -o 1`
FILE_CHKSUM = $(word 1,$(shell echo $(1) | freebsd-cksum -o 1))
all: prog
%.o:%.c
$(CC) $(CPPLAGS) $(CFLAGS) -DFILE_CHKSUM='((uint16_t)$(call FILE_CHKSUM,$<))' -c -o $@ $<
prog: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
clean:
rm -f $(OBJS) prog
运行宁make
:
cc -DFILE_CHKSUM='((uint16_t)3168)' -c -o main.o main.c
cc -o prog main.o
运行 prog
:
./prog
3168
问题“如何仅使用 C 预处理器计算字符串文字的哈希值?”
是有效的,但是我认为您通过包含有关 __FILE__
和日志记录 ID 的详细信息来添加红鲱鱼。
这意味着任何回答的人都需要解决您描述的问题,或者回答有关使用预处理器散列字符串的问题 (在您的特定情况下这可能不是一个好的解决方案!).
碰巧,__FILE__
扩展为变量,而不是文字字符串(至少是 GCC),因此您需要将文件名定义为常量。例如,您可以使用构建系统为每个传递一个定义。
正如其他人所指出的那样,您可以计算哈希值并通过构建系统将其传递进来,尽管这避免了有关对 sting 文字进行哈希处理的问题。
无论如何,当我搜索使用预处理器进行散列时,这个问题出现了,none 的答案涵盖了这个,所以这里是一个涵盖字符串散列部分的答案。
这是可能的,尽管很冗长
/**
* Implement compile-time string hashing on string literals.
*
* This macro implements the widely used "djb" hash apparently posted
* by Daniel Bernstein to comp.lang.c some time ago. The 32 bit
* unsigned hash value starts at 5381 and for each byte 'c' in the
* string, is updated: ``hash = hash * 33 + c``. This
* function uses the signed value of each byte.
*
* note: this is the same hash method that glib 2.34.0 uses.
*/
#define SEED 5381
#if 0
// correct but causes insane expansion
# define _SH(e, c) (((e) << 5) + (e) + (unsigned char)(c))
#elif defined(__GNUC__)
// Use statement-expression extension
# define _SH(e, c) ({ unsigned int _e = (unsigned int)(e); (_e << 5) + _e + (unsigned char)c; })
#else
// use an inline function, the compiler will be able to optimize this out.
static inline unsigned int _SH(unsigned int e, unsigned char c)
{
unsigned int _e = (unsigned int)e;
return (_e << 5) + _e + (unsigned char)c;
}
#endif
#define _SH_1(a) _SH(SEED, (a)[0])
#define _SH_2(a) _SH(_SH_1(a), (a)[1])
#define _SH_3(a) _SH(_SH_2(a), (a)[2])
#define _SH_4(a) _SH(_SH_3(a), (a)[3])
#define _SH_5(a) _SH(_SH_4(a), (a)[4])
#define _SH_6(a) _SH(_SH_5(a), (a)[5])
#define _SH_7(a) _SH(_SH_6(a), (a)[6])
#define _SH_8(a) _SH(_SH_7(a), (a)[7])
#define _SH_9(a) _SH(_SH_8(a), (a)[8])
#define _SH_10(a) _SH(_SH_9(a), (a)[9])
#define _SH_11(a) _SH(_SH_10(a), (a)[10])
#define _SH_12(a) _SH(_SH_11(a), (a)[11])
#define _SH_13(a) _SH(_SH_12(a), (a)[12])
#define _SH_14(a) _SH(_SH_13(a), (a)[13])
#define _SH_15(a) _SH(_SH_14(a), (a)[14])
#define _SH_16(a) _SH(_SH_15(a), (a)[15])
#define _SH_17(a) _SH(_SH_16(a), (a)[16])
#define _SH_18(a) _SH(_SH_17(a), (a)[17])
#define _SH_19(a) _SH(_SH_18(a), (a)[18])
#define _SH_20(a) _SH(_SH_19(a), (a)[19])
#define _SH_21(a) _SH(_SH_20(a), (a)[20])
#define _SH_22(a) _SH(_SH_21(a), (a)[21])
#define _SH_23(a) _SH(_SH_22(a), (a)[22])
#define _SH_24(a) _SH(_SH_23(a), (a)[23])
#define _SH_25(a) _SH(_SH_24(a), (a)[24])
#define _SH_26(a) _SH(_SH_25(a), (a)[25])
#define _SH_27(a) _SH(_SH_26(a), (a)[26])
#define _SH_28(a) _SH(_SH_27(a), (a)[27])
#define _SH_29(a) _SH(_SH_28(a), (a)[28])
#define _SH_30(a) _SH(_SH_29(a), (a)[29])
#define _SH_31(a) _SH(_SH_30(a), (a)[30])
#define _SH_32(a) _SH(_SH_31(a), (a)[31])
// initial check prevents too-large strings from compiling
#define STRHASH(a) ( \
(void)(sizeof(int[(sizeof(a) > 33 ? -1 : 1)])), \
(sizeof(a) == 1) ? SEED : \
(sizeof(a) == 2) ? _SH_1(a) : \
(sizeof(a) == 3) ? _SH_2(a) : \
(sizeof(a) == 4) ? _SH_3(a) : \
(sizeof(a) == 4) ? _SH_3(a) : \
(sizeof(a) == 5) ? _SH_4(a) : \
(sizeof(a) == 6) ? _SH_5(a) : \
(sizeof(a) == 7) ? _SH_6(a) : \
(sizeof(a) == 8) ? _SH_7(a) : \
(sizeof(a) == 9) ? _SH_8(a) : \
(sizeof(a) == 10) ? _SH_9(a) : \
(sizeof(a) == 11) ? _SH_10(a) : \
(sizeof(a) == 12) ? _SH_11(a) : \
(sizeof(a) == 13) ? _SH_12(a) : \
(sizeof(a) == 14) ? _SH_13(a) : \
(sizeof(a) == 15) ? _SH_14(a) : \
(sizeof(a) == 16) ? _SH_15(a) : \
(sizeof(a) == 17) ? _SH_16(a) : \
(sizeof(a) == 18) ? _SH_17(a) : \
(sizeof(a) == 19) ? _SH_18(a) : \
(sizeof(a) == 20) ? _SH_19(a) : \
(sizeof(a) == 21) ? _SH_20(a) : \
(sizeof(a) == 22) ? _SH_21(a) : \
(sizeof(a) == 23) ? _SH_22(a) : \
(sizeof(a) == 24) ? _SH_23(a) : \
(sizeof(a) == 25) ? _SH_24(a) : \
(sizeof(a) == 26) ? _SH_25(a) : \
(sizeof(a) == 27) ? _SH_26(a) : \
(sizeof(a) == 28) ? _SH_27(a) : \
(sizeof(a) == 29) ? _SH_28(a) : \
(sizeof(a) == 30) ? _SH_29(a) : \
(sizeof(a) == 31) ? _SH_30(a) : \
(sizeof(a) == 32) ? _SH_31(a) : \
(sizeof(a) == 33) ? _SH_32(a) : \
0)
// last zero is unreachable
// only for comparison
unsigned int strhash_func(const void *str)
{
const signed char *p;
unsigned int h = 5381;
for (p = str; *p != '[=10=]'; p++) {
h = (h << 5) + h + (unsigned int)*p;
}
return h;
}
/* -------------------------------------------------------------------- */
#include <stdio.h>
#define TEST_STR1 "Hello World"
#define TEST_STR2 "Testing 123"
int main(void)
{
unsigned int A = STRHASH(TEST_STR1);
unsigned int B = STRHASH(TEST_STR2);
printf("String hash: const %u <- '%s'\n", STRHASH(TEST_STR1), TEST_STR1);
printf("String hash: const %u <- '%s'\n", STRHASH(TEST_STR2), TEST_STR2);
printf("String hash: dyn %u <- '%s'\n", strhash_func(TEST_STR1), TEST_STR1);
printf("String hash: dyn %u <- '%s'\n", strhash_func(TEST_STR2), TEST_STR2);
#if defined(__GNUC__)
printf("Is this known at compile time?, answer is: %d\n", __builtin_constant_p(A));
#endif
}
请注意,出于某种原因 Clang 5.0 会打印 answer is: 0
,但仔细检查实际上确实知道编译时的值,__builtin_constant_p
似乎不像 GCC 那样工作。
如何在 CMake 中执行此操作?我不知道如何为每个源文件编写 运行 脚本,并将结果作为 -DFILE_CHEKSUM
的值
使用 STRHASH 的方法有一个很大的限制 - 它最多需要 32 个字符作为输入,当我有更长的名称时,它就无法编译。我认为在构建之前计算它并将其作为 -D 传递是更好的方法,但我无法在 cmake 中做到这一点:(
如果可能,我需要仅使用 C 预处理器来获取字符串校验和或散列(或类似的东西)。
用例如下:我在内存非常有限且 cpu 的嵌入式设备上进行错误记录。我想定义一个 LogError()
宏,它将 hash(__FILE__)
和 __LINE__
插入循环缓冲区(作为 16 位数字)。但是 hash(__FILE__)
需要编译成常量;如果实际文件名在程序中存储为字符串会占用太多内存。可以使用任何方法计算哈希值。
可以 #define FILE_ID
在每个文件的顶部加上一些唯一的编号,并在记录时使用它,但这不是首选的解决方案,它有一点维护成本。有没有更好的方法?
您对预处理器的要求过高。
最好只构建每个文件,并将其名称定义为预处理器的 16 位校验和 宏,正如@n.m建议的那样。
此解决方案的一个微不足道的困难是将您的手放在
16 位校验和实用程序。 GNU cksum
工具只是 32 位的。
但是 FreeBSD 有一个更好的,可以让你选择 16 位或 32 位。如果你的 开发系统是 Debian 的衍生产品 您可以通过以下方式获取:
sudo apt-get install freebsd-buildutils
然后运行:
dpkg-query -L freebsd-buildutils
查看 FreeBSD cksum
的安装位置。就我而言,它是:
/usr/bin/freebsd-cksum
但您可能会发现不同。
您通过传递选项指示 FreeBSD cksum
生成 16 位校验和
-o 1
。你可以查看它的手册页。
生成定义文件名的预处理器宏时要小心 校验和,您将校验和 定义为 16 位无符号整数 ,因为那是 你想要什么。如果它以普通十进制数字形式出现,例如, 它默认为 signed int,这可能会让您大吃一惊。
这是 GNU 解决方案的草图 make
:
main.c
#include <stdio.h>
#include <stdint.h>
int main(int argc, char **argv)
{
printf("%hu\n",FILE_CHKSUM);
return 0;
}
生成文件
.phony: all clean
SRCS = main.c
OBJS = $(SRCS:.c=.o)
# Take the 1st word of the output of `echo <filename> | freebsd-cksum -o 1`
FILE_CHKSUM = $(word 1,$(shell echo $(1) | freebsd-cksum -o 1))
all: prog
%.o:%.c
$(CC) $(CPPLAGS) $(CFLAGS) -DFILE_CHKSUM='((uint16_t)$(call FILE_CHKSUM,$<))' -c -o $@ $<
prog: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
clean:
rm -f $(OBJS) prog
运行宁make
:
cc -DFILE_CHKSUM='((uint16_t)3168)' -c -o main.o main.c
cc -o prog main.o
运行 prog
:
./prog
3168
问题“如何仅使用 C 预处理器计算字符串文字的哈希值?”
是有效的,但是我认为您通过包含有关 __FILE__
和日志记录 ID 的详细信息来添加红鲱鱼。
这意味着任何回答的人都需要解决您描述的问题,或者回答有关使用预处理器散列字符串的问题 (在您的特定情况下这可能不是一个好的解决方案!).
碰巧,__FILE__
扩展为变量,而不是文字字符串(至少是 GCC),因此您需要将文件名定义为常量。例如,您可以使用构建系统为每个传递一个定义。
正如其他人所指出的那样,您可以计算哈希值并通过构建系统将其传递进来,尽管这避免了有关对 sting 文字进行哈希处理的问题。
无论如何,当我搜索使用预处理器进行散列时,这个问题出现了,none 的答案涵盖了这个,所以这里是一个涵盖字符串散列部分的答案。
这是可能的,尽管很冗长
/**
* Implement compile-time string hashing on string literals.
*
* This macro implements the widely used "djb" hash apparently posted
* by Daniel Bernstein to comp.lang.c some time ago. The 32 bit
* unsigned hash value starts at 5381 and for each byte 'c' in the
* string, is updated: ``hash = hash * 33 + c``. This
* function uses the signed value of each byte.
*
* note: this is the same hash method that glib 2.34.0 uses.
*/
#define SEED 5381
#if 0
// correct but causes insane expansion
# define _SH(e, c) (((e) << 5) + (e) + (unsigned char)(c))
#elif defined(__GNUC__)
// Use statement-expression extension
# define _SH(e, c) ({ unsigned int _e = (unsigned int)(e); (_e << 5) + _e + (unsigned char)c; })
#else
// use an inline function, the compiler will be able to optimize this out.
static inline unsigned int _SH(unsigned int e, unsigned char c)
{
unsigned int _e = (unsigned int)e;
return (_e << 5) + _e + (unsigned char)c;
}
#endif
#define _SH_1(a) _SH(SEED, (a)[0])
#define _SH_2(a) _SH(_SH_1(a), (a)[1])
#define _SH_3(a) _SH(_SH_2(a), (a)[2])
#define _SH_4(a) _SH(_SH_3(a), (a)[3])
#define _SH_5(a) _SH(_SH_4(a), (a)[4])
#define _SH_6(a) _SH(_SH_5(a), (a)[5])
#define _SH_7(a) _SH(_SH_6(a), (a)[6])
#define _SH_8(a) _SH(_SH_7(a), (a)[7])
#define _SH_9(a) _SH(_SH_8(a), (a)[8])
#define _SH_10(a) _SH(_SH_9(a), (a)[9])
#define _SH_11(a) _SH(_SH_10(a), (a)[10])
#define _SH_12(a) _SH(_SH_11(a), (a)[11])
#define _SH_13(a) _SH(_SH_12(a), (a)[12])
#define _SH_14(a) _SH(_SH_13(a), (a)[13])
#define _SH_15(a) _SH(_SH_14(a), (a)[14])
#define _SH_16(a) _SH(_SH_15(a), (a)[15])
#define _SH_17(a) _SH(_SH_16(a), (a)[16])
#define _SH_18(a) _SH(_SH_17(a), (a)[17])
#define _SH_19(a) _SH(_SH_18(a), (a)[18])
#define _SH_20(a) _SH(_SH_19(a), (a)[19])
#define _SH_21(a) _SH(_SH_20(a), (a)[20])
#define _SH_22(a) _SH(_SH_21(a), (a)[21])
#define _SH_23(a) _SH(_SH_22(a), (a)[22])
#define _SH_24(a) _SH(_SH_23(a), (a)[23])
#define _SH_25(a) _SH(_SH_24(a), (a)[24])
#define _SH_26(a) _SH(_SH_25(a), (a)[25])
#define _SH_27(a) _SH(_SH_26(a), (a)[26])
#define _SH_28(a) _SH(_SH_27(a), (a)[27])
#define _SH_29(a) _SH(_SH_28(a), (a)[28])
#define _SH_30(a) _SH(_SH_29(a), (a)[29])
#define _SH_31(a) _SH(_SH_30(a), (a)[30])
#define _SH_32(a) _SH(_SH_31(a), (a)[31])
// initial check prevents too-large strings from compiling
#define STRHASH(a) ( \
(void)(sizeof(int[(sizeof(a) > 33 ? -1 : 1)])), \
(sizeof(a) == 1) ? SEED : \
(sizeof(a) == 2) ? _SH_1(a) : \
(sizeof(a) == 3) ? _SH_2(a) : \
(sizeof(a) == 4) ? _SH_3(a) : \
(sizeof(a) == 4) ? _SH_3(a) : \
(sizeof(a) == 5) ? _SH_4(a) : \
(sizeof(a) == 6) ? _SH_5(a) : \
(sizeof(a) == 7) ? _SH_6(a) : \
(sizeof(a) == 8) ? _SH_7(a) : \
(sizeof(a) == 9) ? _SH_8(a) : \
(sizeof(a) == 10) ? _SH_9(a) : \
(sizeof(a) == 11) ? _SH_10(a) : \
(sizeof(a) == 12) ? _SH_11(a) : \
(sizeof(a) == 13) ? _SH_12(a) : \
(sizeof(a) == 14) ? _SH_13(a) : \
(sizeof(a) == 15) ? _SH_14(a) : \
(sizeof(a) == 16) ? _SH_15(a) : \
(sizeof(a) == 17) ? _SH_16(a) : \
(sizeof(a) == 18) ? _SH_17(a) : \
(sizeof(a) == 19) ? _SH_18(a) : \
(sizeof(a) == 20) ? _SH_19(a) : \
(sizeof(a) == 21) ? _SH_20(a) : \
(sizeof(a) == 22) ? _SH_21(a) : \
(sizeof(a) == 23) ? _SH_22(a) : \
(sizeof(a) == 24) ? _SH_23(a) : \
(sizeof(a) == 25) ? _SH_24(a) : \
(sizeof(a) == 26) ? _SH_25(a) : \
(sizeof(a) == 27) ? _SH_26(a) : \
(sizeof(a) == 28) ? _SH_27(a) : \
(sizeof(a) == 29) ? _SH_28(a) : \
(sizeof(a) == 30) ? _SH_29(a) : \
(sizeof(a) == 31) ? _SH_30(a) : \
(sizeof(a) == 32) ? _SH_31(a) : \
(sizeof(a) == 33) ? _SH_32(a) : \
0)
// last zero is unreachable
// only for comparison
unsigned int strhash_func(const void *str)
{
const signed char *p;
unsigned int h = 5381;
for (p = str; *p != '[=10=]'; p++) {
h = (h << 5) + h + (unsigned int)*p;
}
return h;
}
/* -------------------------------------------------------------------- */
#include <stdio.h>
#define TEST_STR1 "Hello World"
#define TEST_STR2 "Testing 123"
int main(void)
{
unsigned int A = STRHASH(TEST_STR1);
unsigned int B = STRHASH(TEST_STR2);
printf("String hash: const %u <- '%s'\n", STRHASH(TEST_STR1), TEST_STR1);
printf("String hash: const %u <- '%s'\n", STRHASH(TEST_STR2), TEST_STR2);
printf("String hash: dyn %u <- '%s'\n", strhash_func(TEST_STR1), TEST_STR1);
printf("String hash: dyn %u <- '%s'\n", strhash_func(TEST_STR2), TEST_STR2);
#if defined(__GNUC__)
printf("Is this known at compile time?, answer is: %d\n", __builtin_constant_p(A));
#endif
}
请注意,出于某种原因 Clang 5.0 会打印 answer is: 0
,但仔细检查实际上确实知道编译时的值,__builtin_constant_p
似乎不像 GCC 那样工作。
如何在 CMake 中执行此操作?我不知道如何为每个源文件编写 运行 脚本,并将结果作为 -DFILE_CHEKSUM
的值使用 STRHASH 的方法有一个很大的限制 - 它最多需要 32 个字符作为输入,当我有更长的名称时,它就无法编译。我认为在构建之前计算它并将其作为 -D 传递是更好的方法,但我无法在 cmake 中做到这一点:(