2014-01-16 66 views
7

我正在寫一個C(gcc)中的應用程序,它做了很多字符串比較。總是一個未知/動態字符串與一個長的編譯時常量字符串列表。 所以我想我散列動態字符串並將結果散列與常量字符串的預計算散列進行比較。GCC宏擴展來調用另一個宏

這樣做我在函數(用於動態運行時字符串)和宏作爲散列算法,以便gcc在編譯期間評估散列值。

我得到這個:

#define HASH_CALC(h, s) ((h) * 33 + *(s)) 
#define HASH_CALC1(s) (HASH_CALC(hash_calc_start, s)) 
#define HASH_CALC2(s) (HASH_CALC(HASH_CALC1(s), s + 1)) 
#define HASH_CALC3(s) (HASH_CALC(HASH_CALC2(s), s + 2)) 
#define HASH_CALC4(s) (HASH_CALC(HASH_CALC3(s), s + 3)) 
#define HASH_CALC5(s) (HASH_CALC(HASH_CALC4(s), s + 4)) 
//--> cut ... goes till HASH_CALC32 

static const unsigned long hash_calc_start = 5381; 
unsigned long hash_str (const char* c); 

void func() { 
    //This string is not constant ... just in this case to show something 
    char dynStr = "foo"; 
    unsigned long dynHash = hash_str (dynStr); 

    //gcc produces a cmp with a constant number as foo is hashed during compile-time 
    if (dynHash == HASH_CALC3("foo")) { 
    } 
} 

我們的問題是:

是否有可能創造出擴大到HASH_CALCX(S),其中X是傳遞給了常量字符串的長度宏宏?

//Expands to HASH_CALC3("foo") 
if (dynHash == HASH_CALCX("foo")) { 
} 
//Expands to HASH_CALC6("foobar") 
if (dynHash == HASH_CALCX("foobar")) { 
} 

我試過這個,但它不起作用。

#define HASH_STRLEN(x) (sizeof(x)/sizeof(x[0])-1) 
#define HASH_MERGE(x,y) x ## y 
#define HASH_MERGE2(x,y) HASH_MERGE(x,y) 
#define HASH_CALCX(s) (HASH_MERGE2(HASH_CALC, HASH_STRLEN(s))(s)) 

謝謝!

+0

沒必要。 'strlen'對於字符串常量來說非常好,它返回一個編譯時常量。 – Damon

+0

@Damon Well ... #define HASH_CALCX(s)(HASH_MERGE2(HASH_CALC,strlen(s))(s))無法正常工作,因爲strlen(「」)不能擴展。 – Xatian

+0

如何定義'hash_calc_start'? '#define hash_calc_start 0'? – ericbn

回答

1

不幸的是,在C中,字符串分解不是一個常量表達式。以下內容無效:

switch (x) { 
case "a"[0]: 
    ... 
} 

預處理器中的常量表達式類似。以下是無效的:

#if "a"[0] == 'a' 
    ... 
#endif 

如果您不需要使用哈希碼在switch陳述爲什麼不要在你的程序的初始化階段預先計算的值,並將結果存儲到變量,然後只需在測試中使用它們?

如果您在switch聲明中需要它們,我建議將字符串拆分爲字符。你的情況,這將給像宏:

#define HASH_CALC(...) HASH_CALC0(5381, __VA_ARGS__, 0, 0, 0, 0, 0) 
#define HASH_CALC0(p, x, ...) (x == 0 ? (p) : HASH_CALC1((p)*33+x, __VA_ARGS__))) 
#define HASH_CALC1(p, x, ...) (x == 0 ? (p) : HASH_CALC2((p)*33+x, __VA_ARGS__))) 
#define HASH_CALC2(p, x, ...) (x == 0 ? (p) : HASH_CALC3((p)*33+x, __VA_ARGS__))) 
#define HASH_CALC3(p, x, ...) (x == 0 ? (p) : HASH_CALC4((p)*33+x, __VA_ARGS__))) 

... 
#define HASH_CALC64(p, x, ...) (x == 0 ? (p) : -1 /* shall never be used */) 

HASH_CALC預計可變數目的字符作爲參數。例如,以下是有效的代碼:

switch (x) { 
case HASH_CALC('f', 'o', 'o'): 
    ... 
case HASH_CALC('f', 'o', 'o', 'b', 'a', 'r'): 
    ... 
} 
+0

我喜歡你的答案,就像你說你的宏也可以用在switch語句中一樣。只有一個問題難以理解:爲什麼宏中的(x + 0)而不是x? – Xatian

+0

@xatian:這是一個騙局。有64個宏最多消耗64個參數。但是,如果參數較少,則剩餘的宏會將空字符串作爲參數。所以'x'可能是空的,會給語法錯誤,因爲表達式會是'(== 0?...)'+ 0,這會給出'((+0)== 0?...) 。它不是100%乾淨:)。這讓我覺得你可能不需要在HASH_CALC參數中輸入尾部0。 – Marian

+0

我編輯了原文,以避免那些醜陋的'(x + 0)'。 – Marian

2

你可以使用三元運算符:

#define HASH_CALCX(s)    \ 
    (strlen(s) == 5 ? HASH_CALC5(s) : \ 
    strlen(s) == 4 ? HASH_CALC4(s) : \ 
    strlen(s) == 3 ? HASH_CALC3(s) : \ 
    strlen(s) == 2 ? HASH_CALC2(s) : \ 
    strlen(s) == 1 ? HASH_CALC1(s) : some_error) 

對於這是可行的,將取決於編譯器的優化減少這種表達一個單一的數值不變。

更新:使用gcc的工作示例。罰款,如果優化級別設置爲1,而不是0

讓foox.c是:

#include <string.h> 
#include <stdio.h> 

#define HASH_CALC(h, s) ((h) * 33 + *(s)) 
#define HASH_CALC1(s) (HASH_CALC(5381, s)) // hash_calc_start = 5381 
#define HASH_CALC2(s) (HASH_CALC(HASH_CALC1(s), s + 1)) 
#define HASH_CALC3(s) (HASH_CALC(HASH_CALC2(s), s + 2)) 
#define HASH_CALC4(s) (HASH_CALC(HASH_CALC3(s), s + 3)) 
#define HASH_CALC5(s) (HASH_CALC(HASH_CALC4(s), s + 4)) 

#define HASH_CALCX(s)    \ 
    (strlen(s) == 5 ? HASH_CALC5(s) : \ 
    strlen(s) == 4 ? HASH_CALC4(s) : \ 
    strlen(s) == 3 ? HASH_CALC3(s) : \ 
    strlen(s) == 2 ? HASH_CALC2(s) : \ 
    strlen(s) == 1 ? HASH_CALC1(s) : 0) 

int main(void) { 
    printf("%d\n", HASH_CALCX("foo")); 
    return 0; 
} 

然後gcc -S -O1 foox.c給出:

 .file  "foox.c" 
     .section .rodata.str1.1,"aMS",@progbits,1 
.LC0: 
     .string  "%d\n" 
     .text 
.globl main 
     .type  main, @function 
main: 
     leal  4(%esp), %ecx 
     andl  $-16, %esp 
     pushl  -4(%ecx) 
     pushl  %ebp 
     movl  %esp, %ebp 
     pushl  %ecx 
     subl  $20, %esp 
     movl  $193491849, 4(%esp) 
     movl  $.LC0, (%esp) 
     call  printf 
     movl  $0, %eax 
     addl  $20, %esp 
     popl  %ecx 
     popl  %ebp 
     leal  -4(%ecx), %esp 
     ret 
     .size  main, .-main 
     .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-54)" 
     .section .note.GNU-stack,"",@progbits 

更新2:作爲未成年人增強,我肯定會嘗試添加一個編譯時'assert'來驗證只有文字字符串通過了一定的長度到宏,因爲我很容易出錯。例如,修改上面的讀取:

#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) 
#define HASH_CALCX(s) (ASSERT_zero(strlen(s) <= 5) + \ 
    (strlen(s) == 5 ? HASH_CALC5(s) : \ 
    etc 

ASSERT_zero()宏類似於BUILD_BUG_ON_ZERO(),並使用「的sizeof位字段」特技。它產生任一:

  • 編譯錯誤,當e是假的,或
  • 零值。

這個ASSERT_zero()不適用於C++。而這個額外的檢查對於VS IIRC不起作用,因爲VS不認爲strlen("foo")是編譯時常量。

+0

我可以證實這一點 - >產生一個編譯時常量。所以,如果沒有更好的答案(解決方案),我可以使用它的有效解決方法...迄今爲止,謝謝! (我實際上甚至不喜歡HASH_CALC1/HASH_CALC2/HASH_CALC3的東西,這樣我會得到更多的怪物宏) – Xatian

1

雖然它是驚人的什麼,可以用宏做的,就是最好的/最易維護的方式做到這一點?

我傾向於將字符串放在另一個文件中,並使用perl腳本生成散列並在某些方便的數據結構中輸出包含字符串和散列的C++源文件。

您的makefile可以設置爲在字符串更改時重新運行perl腳本。