原文地址:http://blog.csdn.net/ariesjzj/article/details/7881049
要把一個項目的build系統從gcc移植到MSVC,困難之一在於源碼中使用了gcc extension(http://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html),這些可以通過添加編譯選項-pedantic給出warning。困難之二在於一些linux下的函數windows下沒有,或者實現上略有不同。困難之三在於對同種情況下兩種編譯器各自選用的處理行為的不同。下面各自列舉了些常見的不相容情況及修改方式。
1. 位元組對齊(byte alignment)
gcc中位元組位元組對齊如:
typedef struct foo { char a;int b;} __attribute__((packed)) foo;
msvc中可用:
#pragma pack(push, 1)typedef struct foo { char a;int b;} foo;#pragma pack(pop)
這裡1為結構成員對齊值的上限。後者gcc也作為extension支援,所以後面這樣寫兩個平台都可編譯。
gcc下設定對齊值最小值為:
typedef struct A{int b;} __attribute__((aligned(32))) A;
msvc中為:
typedef __declspec(align(32)) struct A{int b;} A;
2. 在main之前啟動並執行代碼
gcc中用__attribute__((constructor)),如:
static void __attribute__((constructor)) before(void) {printf("before\n");}
msvc中可用:
int before() { printf("%s\n", "before"); return 0; } #pragma data_seg(".CRT$XIU") int (*f) () = before;#pragma data_seg()
或者:
void foo(){printf("foo\n");}typedef void (__cdecl *PF)(void);#pragma section(".CRT$XCG", read)__declspec(allocate(".CRT$XCG")) PF f[] = {foo};
3. case range
gcc中switch語句中的數值範圍可用...表示
switch (x) {case 0x03 ... 0x10:// ...break;...}
這種用法可以寫指令碼自動轉換掉(http://blog.csdn.net/ariesjzj/article/details/7848467)。
4. designated initializer
只為結構體中的指定成員賦值。
struct foo {int i; int j; }; struct foo f = {.j = 4};
在用gcc編譯的項目中通常有大量這種用法,比如系統為每種裝置提供統一的一套介面,而對於某種裝置而言只需實現其中一部分介面,通常在註冊時只會為一部分成員賦值。把指派陳述式前統一加上結構體變數名(vi中在多行前統一加字串:Ctrl+v,選中行,I(大寫i),輸入統一要加的首碼,Esc)然後放到初始化函數中即可,或者把結構體中每個成員變數在賦值時都補上,沒有的就賦0或NULL。
對於成員比較多的結構體,則只能用指令碼自動改了,這裡是一例子:http://blog.csdn.net/ariesjzj/article/details/7944005
5. empty array
gcc extension允許空定義空數組,如果空數組是在結構體內部作為可變長度成員的頭指標那倒好辦,替換成單元素數組即可。有時候空數組是為了這種環形聲明:
int (*f[])(void);void help() { // use f[i]}int (*f[])(void) = { ... help, ...}
其中help()用到了函數指標數組f,而f的定義又用到了help(),為了打破這種雞-蛋聲明結構,在f的聲明前加上help()的聲明,變成:
void help();int (*f[])(void);void help() { // use f[i]}int (*f[])(void) = { ... help, ...}
6. void * arithematic
msvc中對於void *變數的算術運算是不允許的,gcc中的void *運算以1位元組為單位,相當於unsigned char *。所以在msvc編譯時間將void *作運算時聲明成unsigned char *或者uint8_t *就行,這樣不會改變原來的語義。
7 Arrays of Variable Length
gcc允許用變數作為數組聲明時的長度。對於這種情況,要麼定義個足夠大的數組,要麼改成動態分配的數組。
8. inline, __inline__, __inline
gcc對上面三種關鍵字都認識,但msvc處理c檔案時只認__inline。而一般用gcc的項目一般會用inline,相容性考慮好點的會用__inline__(http://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html)。如果用後者的話移植的時候會更好辦一點,比如可以在共用標頭檔中定義:
#ifdef _MSC_VER #define __inline__ __inline #endif
或者在編譯選項中都加上-D__inline__=__inline。如果原項目中用的inline關鍵字就更麻煩點,因為可能會把其它名字帶inline這個字串的也替換掉。
至於強制inline,gcc中有__attribute__((always_inline)),而msvc中有forceinline。
9 thread-local storage
gcc下:
__thread int tls_i;
msvc下:
__declspec( thread ) int tls_i;
更詳細的文檔見http://msdn.microsoft.com/en-us/library/6yh4a9k1.aspx 和 http://msdn.microsoft.com/en-us/library/2s9wt68x.aspx
10. #define 中包含#ifdef或者#pragma
msvc中如果在宏定義中有#ifdef,不會先解析#ifdef。如:
#define DEF(str1) str1char * a = DEF("a",#ifdef _WIN32"win32"#else"not win32" #endif"end\n");
gcc是先解釋#ifdef,msvc會先解釋#define,擴充成"a" #ifdef 1 "win32" #else "not win32" # endif "end\n",然後編譯就出錯了。msvc下加/E可看宏展開後的結果。
同理還有#define中包含#pragma的情況,這時需要將#pragma改為_pragma(http://msdn.microsoft.com/en-us/library/d9x1s805.aspx)
11. 取得調用者地址
gcc中__builtin_return_address(0)可以得到調用者中調用子函數的地址,準確地說是被調用者返回後要執行的那條指令的地址。
msvc中 _ReturnAddress可以達到相同的目的。
12. msvc中有對應函數,但函數名不相同
重定義下就行,如:
#ifdef _MSC_VER#define strtoll _strtoi64#define strtoull _strtoui64#define snprintf _snprintf#define popen _popen#define pclose _pclose#define strcasecmp _stricmp#define strncasecmp _strnicmp#endif
13. msvc中不提供的函數
如rint, remainder, trunc, gettimeofday, va_copy等。這些函數Mingw gcc是通過build-in函數或是靜態連結實現,而不依賴於msvcrt。這些函數只能拷貝實現了,如:
#ifdef _MSC_VER__inline int gettimeofday(struct timeval *tp, void *tzp){ time_t clock; struct tm tm; SYSTEMTIME wtm; GetLocalTime(&wtm); tm.tm_year = wtm.wYear - 1900; tm.tm_mon = wtm.wMonth - 1; tm.tm_mday = wtm.wDay; tm.tm_hour = wtm.wHour; tm.tm_min = wtm.wMinute; tm.tm_sec = wtm.wSecond; tm. tm_isdst = -1; clock = mktime(&tm); tp->tv_sec = clock; tp->tv_usec = wtm.wMilliseconds * 1000; return (0);}double trunc(double x){ if (x > 0) return floor(x); else return ceil(x);}intfesetround (int round){ unsigned short int cw; if ((round & ~0xc00) != 0) /* ROUND is no valid rounding mode. */ return 1; __asm { fnstcw cw} cw &= ~0xc00; cw |= round; __asm {fldcw cw} return 0;}__inline double rint(double dbl){ _asm { fld dbl frndint }}__inline long double rintl(long double x){ _asm { fld x frndint }}double remainder(double x, double y){ double i = rint(x/y); return x - i * y;}#ifndef va_copy#define va_copy(dst, src) ((dst) = (src))#endif#endif
要注意一些相似函數間的細微差別,嚴格按照man或者文檔上來實現,如fmod和remainder,差別在於一個的商是向0取整,一個是就近取整。
另外LibGW32C for Windows提供了一些GNU C函數的Windows實現http://gnuwin32.sourceforge.net/packages/libgw32c.htm
14 不同平台間函式宣告相同,但行為不同
有些函數兩個平台都提供,而且聲明還相同,但行為不同。這一般會在運行時導致詭異的錯誤。
如_creat函數,Mingw gcc編譯creat("a.txt", 0666)運行是OK的,MSC編譯出來的會crash,因為在兩個平台中第二個參數的解釋是不一樣的。另外如 _lseeki64,MSVC編譯出來的始終返回不正確值,導致讀檔案錯誤。
15 system const
有些常量雖然windows和linux裡都有,但定義的值不一樣,這種情況不能簡單拷貝,如S_IFMT,S_IFDIR等值。
16 global register variable
有些地方會為了最佳化把一個頻繁用到的結構放在固定寄存器中,如:
int a asm("ebp")
這就沒有統一的方法了,要case-by-case地處理。如果代碼重構比較麻煩的話就在要用到該結構的地方前面把變數放到ebp,用完了把ebp恢複出來。
17 calling convention
函數前加:
__attribute__((regparm(3)))
表示用寄存器EAX,EDX和ECX來傳參數。類似於fastcall,但MSVC中的fastcall用EDX和ECX來傳,略有不同。比較保險點的方法就是在調用和被調用端都改為棧傳參數。