2012-03-02 31 views
9

如何創建一個C宏以獲取字符串的整數值?具體使用案例來自here。我想改變這樣的代碼:使用C預處理獲取字符串的整數值

enum insn { 
    sysenter = (uint64_t)'r' << 56 | (uint64_t)'e' << 48 | 
       (uint64_t)'t' << 40 | (uint64_t)'n' << 32 | 
       (uint64_t)'e' << 24 | (uint64_t)'s' << 16 | 
       (uint64_t)'y' << 8 | (uint64_t)'s', 
    mov = (uint64_t)'v' << 16 | (uint64_t)'o' << 8 | 
      (uint64_t)'m' 
}; 

要這樣:

enum insn { 
    sysenter = INSN_TO_ENUM("sysenter"), 
    mov  = INSN_TO_ENUM("mov") 
}; 

INSN_TO_ENUM擴展到相同的代碼。性能會一樣,但可讀性會提高很多。

我懷疑,在這種形式可能不是因爲C預處理器的無力字符串處理的是可能的,所以這也將是一個不優選的,但可以接受的解決方案(可變參數宏):

enum insn { 
    sysenter = INSN_TO_ENUM('s','y','s','e','n','t','e','r'), 
    mov  = INSN_TO_ENUM('m','o','v') 
}; 
+0

在這一點上,我會寫我自己的預處理器。試圖按照自己的方式去做就會變得非常痛苦。用你最喜歡的腳本語言編寫一個簡單的腳本。假設你的枚舉在一段時間內是相對靜態的(即你不是日復一日地編輯它們),那麼我認爲這將是方便和可讀的。 – 2012-03-02 15:13:04

+0

@DavidHeffernan:我已經考慮過了,但作爲最後的手段。如果它可以作爲一個宏來實現,我們就具有可讀性的優點,並且能夠抽象/隱藏這個特定的實現細節。國際海事組織的優勢勝過通過「預處理」產生的選項。 – 2012-03-02 15:18:48

+0

我不確定宏是否可以處理變量參數,或者只是接受它來傳遞它。看到我的答案爲1的論點之一。 – Shahbaz 2012-03-02 15:24:14

回答

4

這是一個編譯時純C解決方案,您認爲這是可接受的。您可能需要延長它以獲得更長的助記符。我會繼續考慮想要的(即INSN_TO_ENUM("sysenter"))。有趣的問題:)

#include <stdio.h> 

#define head(h, t...) h 
#define tail(h, t...) t 

#define A(n, c...) (((long long) (head(c))) << (n)) | B(n + 8, tail(c)) 
#define B(n, c...) (((long long) (head(c))) << (n)) | C(n + 8, tail(c)) 
#define C(n, c...) (((long long) (head(c))) << (n)) | D(n + 8, tail(c)) 
#define D(n, c...) (((long long) (head(c))) << (n)) | E(n + 8, tail(c)) 
#define E(n, c...) (((long long) (head(c))) << (n)) | F(n + 8, tail(c)) 
#define F(n, c...) (((long long) (head(c))) << (n)) | G(n + 8, tail(c)) 
#define G(n, c...) (((long long) (head(c))) << (n)) | H(n + 8, tail(c)) 
#define H(n, c...) (((long long) (head(c))) << (n)) /* extend here */ 

#define INSN_TO_ENUM(c...) A(0, c, 0, 0, 0, 0, 0, 0, 0) 

enum insn { 
    sysenter = INSN_TO_ENUM('s','y','s','e','n','t','e','r'), 
    mov  = INSN_TO_ENUM('m','o','v') 
}; 

int main() 
{ 
    printf("sysenter = %llx\nmov = %x\n", sysenter, mov); 
    return 0; 
} 
+0

這樣編譯看起來可能會做正確的事情,但GCC無法識別'%Lx'中的L,並且希望將該num視爲32位int(可能是因爲我正在編譯x86架構)。 – 2012-03-02 16:37:07

+0

哎唷!我的錯。 '%L'是'long double',應該是'%llx',固定。儘管如此,'printf()'只是用於單元測試:) – 2012-03-02 16:44:56

+1

我喜歡堆棧溢出,因爲每天我都會學習一些新東西! 'head/tail(h,t ...)'非常好!我從來不知道你可以將'...'附加到這樣的參數之一 – Shahbaz 2012-03-02 17:54:47

2

編輯:這個答案可能會有幫助,所以我不刪除它,但沒有具體回答這個問題。它將字符串轉換爲數字,但不能放入枚舉中,因爲它不會在編譯時計算數字。

那麼,因爲你的整數是64位,你只有任何字符串的前8個字符擔心。因此,你可以寫的東西8次,確保你不出去約束字符串:

#define GET_NTH_BYTE(x, n) (sizeof(x) <= n?0:((uint64_t)x[n] << (n*8))) 
#define INSN_TO_ENUM(x)  GET_NTH_BYTE(x, 0)\ 
          |GET_NTH_BYTE(x, 1)\ 
          |GET_NTH_BYTE(x, 2)\ 
          |GET_NTH_BYTE(x, 3)\ 
          |GET_NTH_BYTE(x, 4)\ 
          |GET_NTH_BYTE(x, 5)\ 
          |GET_NTH_BYTE(x, 6)\ 
          |GET_NTH_BYTE(x, 7) 

它所做的基本上是檢查在每個字節不管是在字符串的限制,如果是,則給你相應的字節。

注意:這隻適用於文字字符串。

如果你希望能夠爲任何字符串轉換,你可以給字符串的長度與它:

#define GET_NTH_BYTE(x, n, l) (l < n?0:((uint64_t)x[n] << (n*8))) 
#define INSN_TO_ENUM(x, l)  GET_NTH_BYTE(x, 0, l)\ 
           |GET_NTH_BYTE(x, 1, l)\ 
           |GET_NTH_BYTE(x, 2, l)\ 
           |GET_NTH_BYTE(x, 3, l)\ 
           |GET_NTH_BYTE(x, 4, l)\ 
           |GET_NTH_BYTE(x, 5, l)\ 
           |GET_NTH_BYTE(x, 6, l)\ 
           |GET_NTH_BYTE(x, 7, l) 

因此,例如:

int length = strlen(your_string); 
int num = INSN_TO_ENUM(your_string, length); 

最後,還有一種方法以避免給出長度,但它要求編譯器從左到右實際計算INSN_TO_ENUM的短語。 我不知道這是否是標準:

static int _nul_seen; 
#define GET_NTH_BYTE(x, n) ((_nul_seen || x[n] == '\0')?(_nul_seen=1)&0:((uint64_t)x[n] << (n*8))) 
#define INSN_TO_ENUM(x)  (_nul_seen=0)| 
           (GET_NTH_BYTE(x, 0)\ 
           |GET_NTH_BYTE(x, 1)\ 
           |GET_NTH_BYTE(x, 2)\ 
           |GET_NTH_BYTE(x, 3)\ 
           |GET_NTH_BYTE(x, 4)\ 
           |GET_NTH_BYTE(x, 5)\ 
           |GET_NTH_BYTE(x, 6)\ 
           |GET_NTH_BYTE(x, 7)) 
+0

這不是一個編譯時常量但是 - 是嗎? – 2012-03-02 15:15:57

+0

您可以使用C++ 11'constexpr'完成這項工作,它也適用於數組 - 至少使用gcc-4.6 – hirschhornsalz 2012-03-02 15:20:36

+0

這不適用於'enum',因爲條件賦值只能在運行時使用。 – 2012-03-02 15:21:18

1

如果你能在最近的編譯器使用C++ 11

constexpr uint64_t insn_to_enum(const char* x) { 
    return *x ? *x + (insn_to_enum(x+1) << 8) : 0; 
} 

enum insn { sysenter = insn_to_enum("sysenter") }; 

會工作,在編譯時計算的常數。

0

一些遞歸模板魔法可能會訣竅。如果編譯時已知常量,則不創建代碼。

如果你在憤怒中使用它,可能要密切關注你的構建時間。

// the main recusrsive template magic. 
template <int N> 
struct CharSHift 
{ 
    static __int64 charShift(char* string) 
    { 
     return string[N-1] | (CharSHift<N-1>::charShift(string)<<8); 
    } 
}; 

// need to provide a specialisation for 0 as this is where we need the recursion to stop 
template <> 
struct CharSHift<0> 
{ 
    static __int64 charShift(char* string) 
    { 
     return 0; 
    } 
}; 

// Template stuff is all a bit hairy too look at. So attempt to improve that with some macro wrapping ! 
#define CT_IFROMS(_string_) CharSHift<sizeof _string_ -1 >::charShift(_string_) 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    __int64 hash0 = CT_IFROMS("abcdefgh"); 

    printf("%08llX \n",hash0); 
    return 0; 
} 
+1

感謝您的回答,但這是一個C項目,所以我對C解決方案更感興趣,而不是C++。 – 2012-03-02 16:19:11