c-如何在编译时使用常量参数进行gcc优化函数调用



我正在尝试解析用户输入,并根据用户给出的命令执行一些任务。由于在C中,switch不适用于字符串,我决定使用字符串哈希值的switch来比较执行哪个命令。

现在,在这个中维护所有可用命令的所有哈希列表

#define EXIT 6385204799
...

这真的是一项乏味的任务,我在想是否有办法说服gcc在编译时用常量参数来评估哈希函数,这样我就可以使用类似于的东西

switch(hash(command)){
case hash("exit"): exit();
// I know, case labels must be compile time constants
// but that should be fulfilled in my case
}

我知道我可以使用例如元编程,但我对gcc的解决方案更感兴趣。

这可能吗?

#include <stdio.h>

unsigned long hash(const char *str)
{
unsigned long hash = 5381;
int c;
while ((c = *str++))
hash = ((hash << 5) + hash) + c;
return hash;
}
int main( int argc, char **argv ) {
char *command=NULL;
size_t size=0;
printf("Enter string:");
getline(&command, &size, stdin);
printf("%ld",hash("exit")); // I want this to evaluate in compile time
printf("%ld",hash(command)); // and this not
return 0;
}

这可能吗?

GCC不能(对于C-它可以用于C++,见下文),但Clang/LLVM(版本3.9.1)可以。使用-O2开关启用2级优化(或更高级别)。

证明。请参阅反汇编-没有对哈希函数的调用,没有循环;编译器已在编译时计算出散列。这个简化形式的测试用例:

#include <stdio.h>
static unsigned long hash(const char *str)
{
unsigned long hash = 5381;
int c;
while ((c = *str++))
hash = ((hash << 5) + hash) + c;
return hash;
}
int main( int argc, char **argv ) {
size_t size=0;
printf("%ld",hash("exit")); // I want this to evaluate in compile time
return 0;
}

编译为:

main:                                   # @main
# BB#0:
push    rax
#DEBUG_VALUE: main:argc <- %EDI
#DEBUG_VALUE: main:argv <- %RSI
#DEBUG_VALUE: main:size <- 0
movabs  rsi, 6385204799
mov     edi, .L.str
xor     eax, eax
call    printf
xor     eax, eax
pop     rcx
ret
.L.str:
.asciz  "%ld"

线路movabs rsi, 6385204799直接将预先计算的散列值加载到rsi寄存器中。

但是,为了在switch语句的case标签中使用,该值不会被视为编译时常数。您需要使用if ... else而不是switch进行比较。

如果您感兴趣,使用现代C++,您可以使用GCC和Clang/LLVM实现这种类型的优化,甚至可以使用switch语句:

#include <cstdio>
static constexpr unsigned long hash(const char *str)
{
unsigned long hash = 5381;
int c = *str;
while ((c = *str++))
hash = ((hash << 5) + hash) + c;
return hash;
}
int main( int argc, char **argv ) {
size_t size=0;
printf("%ld",hash("exit")); // I want this to evaluate in compile time
switch((unsigned long)5 /* i.e. some number */) {
case hash("exit"):
// etc
;
}
return 0;
}

这是C++14代码,您需要使用-std=c++14来编译它(或者使用GCC 6+,这是默认值)。(当然,该代码不是惯用的C++——它旨在尽可能接近前面的示例)。

您可以构建一个C程序,该程序将依赖于hash函数,并将基于配置文件生成头定义文件。

配置文件:EXIT "exit"->进程配置文件->头文件(commands.h):#define EXIT 6385204799

然后,您可以在程序中使用switch语句中的enum来包含commands.h

switch(hash(command)){
case EXIT: exit();

最简单的方法:如果4个字符就足够了,可以使用诸如"exit"/"tixe"(取决于endianness)之类的文字,而不是哈希函数。注意单引号。

任何使其成为常量表达式的方法都将依赖于编译器,因此您可以使用gcc的语句表达式扩展,该扩展允许宏返回值。看起来像({int hash=5381; /*do stuff*/ hash;})。。。但您可能需要在您的案例陈述之前#pragma GCC push_options#pragma GCC optimize ("unroll-loops"),然后#pragma GCC pop_options

一种替代方法是将字符串映射到枚举,并使用按字母顺序排列的字符串的二进制搜索,而不是散列。可以使用X宏来简化添加和删除命令。在这个例子中,我也将它用于函数原型和用例语句(没有必要,只是对于一个简单的例子来说更容易使用)

#include <string.h>
#define MYXMACRO(OP) 
OP(bar) 
OP(exit) 
OP(foo)
#define AS_ENUM(x,...) MYENUM_##x,
enum { MYXMACRO(AS_ENUM) MYENUMCOUNT };
#undef AS_ENUM
#define AS_STRING(x,...) #x,
const char* mystrings[]= { MYXMACRO(AS_STRING) };
#undef AS_STRING
#define AS_PROTOTYPES(x,...) void do_##x(void);
MYXMACRO(AS_PROTOTYPES)
void do_default(void);
#undef AS_PROTOTYPES
int mybsearch(const char *command){
size_t bot=0, top=MYENUMCOUNT, i=((bot+top)>>1)&(~1);
int cmp;
for (; bot<=top && i<=MYENUMCOUNT; i=((bot+top)>>1)&(~1)){
cmp=strcmp(command,mystrings[i]);
if (! cmp) return i; //match found
else if (cmp>0) bot=i+1;
else top=i-1;
}
return -1;
}
void do_command(const char * command){
#define AS_CASE(x,...)  case MYENUM_##x : do_##x(__VA_ARGS__);break;
switch(mybsearch(command)){
MYXMACRO(AS_CASE)
default: do_default();
}
}
#undef MYXMACRO

相关内容

最新更新