代码从GCC到MSVC的移植
要把一个项目的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。
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) str1 char * 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等。只能拷贝实现了,如:
#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); } int fesetround (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 系统常量
有些常量虽然windows和linux里都有,但定义的值不一样,这种情况不能简单拷贝,如S_IFMT,S_IFDIR等值。