简体   繁体   English

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

[英]How to make gcc optimize function call with constant parameters at compile time?

I am trying to parse user input and execute some tasks according to the commands given by user.我正在尝试解析用户输入并根据用户给出的命令执行一些任务。 As, in C, switch doesn't work with strings I have decided to use switch of hash values of strings to compare which command to execute.因为,在 C 中,开关不适用于字符串我决定使用 hash 字符串值的开关来比较要执行的命令。

Now, as maintaining the list of all hashes of all available commands in something like this现在,像这样维护所有可用命令的所有哈希列表

#define EXIT 6385204799
...

is really tedious task, I was wandering if there exist way to convince gcc to evaluate the hash function with constant parameter on compile time so I could use something like this真的是一项乏味的任务,我在想是否有办法说服gcc在编译时使用常量参数评估 hash function,这样我就可以使用这样的东西

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

I know I could use for example metaprogramming but I am interested more in the solution with gcc.我知道我可以使用例如元编程,但我对 gcc 的解决方案更感兴趣。

Is it even possible?有可能吗?

#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;
}

Is it even possible? 它甚至可能吗?

GCC cannot (for C - it can for C++, see below), but Clang/LLVM (version 3.9.1) can. GCC不能(对于C - 它可以用于C ++,见下文),但Clang / LLVM(版本3.9.1)可以。 Use the -O2 switch to enable level 2 optimisations (or higher). 使用-O2开关启用2级优化(或更高)。

Proof . 证明 See the disassembly - there is no call to a hash function, no loop; 参见反汇编 - 没有调用哈希函数,没有循环; the compiler has calculated the hash at compile time. 编译器在编译时计算了哈希值。 This reduced form of your test case: 这种简化形式的测试用例:

#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;
}

Compiles to: 编译为:

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"

The line movabs rsi, 6385204799 directly loads the pre-calculated hash value into the rsi register. movabs rsi, 6385204799直接将预先计算的哈希值加载到rsi寄存器中。

However, the value will not be considered a compile-time constant for purposes of use in a case label in a switch statement. 但是,为了在switch语句中的case标签中使用,该值不会被视为编译时常量。 You need to use if ... else for comparison rather than switch . 您需要使用if ... else进行比较而不是switch

In case you are interested, with modern C++ you can achieve this type of optimisation using GCC as well as Clang/LLVM, and you can even use a switch statement: 如果你感兴趣,使用现代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;
}

This is C++14 code, you will need to use -std=c++14 to compile it (or use GCC 6+ for which this is the default). 这是C ++ 14代码,您需要使用-std=c++14来编译它(或使用GCC 6+,这是默认值)。 (Of course, the code is not idiomatic C++ - it is intended to be as close as possible to the previous example). (当然,代码不是惯用的C ++ - 它旨在尽可能接近前面的例子)。

You can build a C program which will rely on your hash function and which will generate your header definition file based on a configuration file. 您可以构建一个C程序,它将依赖于您的hash函数,并将根据配置文件生成您的头定义文件。

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

Then you can include commands.h in your program in use the enum in your switch statement. 然后,您可以在程序中包含commands.h ,以便在switch语句中使用enum。

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

Easiest way: If 4 characters are enough you could use literals like 'exit' / 'tixe' (depending on endianness) instead of a hash function. 最简单的方法:如果4个字符足够,你可以使用像'exit'/'tixe'这样的文字(取决于字节顺序)而不是哈希函数。 Note the single quotes. 请注意单引号。

Any way to make it a constant expression will be compiler dependent anyhow, so you might be able to use gcc's statement expression extension that allows macros to return a value. 任何使它成为常量表达式的方法无论如何都将依赖于编译器,因此您可以使用gcc的语句表达式扩展 ,它允许宏返回一个值。 It looks like ({int hash=5381; /*do stuff*/ hash;}) ... but you may need to #pragma GCC push_options #pragma GCC optimize ("unroll-loops") before your case statements and #pragma GCC pop_options after. 它看起来像({int hash=5381; /*do stuff*/ hash;}) ...但你可能需要在你的case语句和#pragma GCC pop_options之前#pragma GCC push_options #pragma GCC optimize ("unroll-loops") #pragma GCC pop_options之后。

One alternative is to map strings to enums and use a binary search of the alphabetized strings instead of a hash. 一种替代方法是将字符串映射到枚举,并使用字母顺序字符串的二进制搜索而不是散列。 You can use an X-macro to simplify adding and removing commands. 您可以使用X-macro来简化添加和删除命令。 In this example I used it for function prototypes and case statements as well (not necessary, just easier to work with for a simple example) 在这个例子中,我也将它用于函数原型和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

不要认为任何函数都是可能的——如果函数读取一些全局或寄存器,那么编译器会为它假设什么值?

"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM