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

时间:2017-01-12 23:16:01

标签: c gcc

我正在尝试解析用户输入并根据用户给出的命令执行某些任务。因为,在C中,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;
}

3 个答案:

答案 0 :(得分:5)

  

甚至可能吗?

GCC不能(对于C - 它可以用于C ++,见下文),但Clang / LLVM(版本3.9.1)可以。使用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" 开关启用2级优化(或更高)。

Proof。参见反汇编 - 没有调用哈希函数,没有循环;编译器在编译时计算了哈希值。这种简化形式的测试用例:

movabs  rsi, 6385204799

编译为:

rsi

case行直接将预先计算的哈希值加载到switch寄存器中。

但是,为了在if ... else语句中的switch标签中使用,该值不会被视为编译时常量。您需要使用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 ++,您可以使用GCC和Clang / LLVM实现这种类型的优化,甚至可以使用-std=c++14语句:

{{1}}

这是C ++ 14代码,您需要使用{{1}}来编译它(或使用GCC 6+,这是默认值)。 (当然,代码不是惯用的C ++ - 它应该尽可能接近前面的例子。)

答案 1 :(得分:1)

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

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

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

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

答案 2 :(得分:0)

最简单的方法: 如果4个字符就足够了,你可以使用像'exit'/'tixe'这样的文字(取决于字节顺序)而不是哈希函数。请注意单引号。

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

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

#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
相关问题