代码从GCC到MSVC的移植

来源:互联网 发布:up to date数据库账号 编辑:程序博客网 时间:2024/05/07 09:05

摘自:http://blog.csdn.net/ariesjzj/article/details/7881049


原文地址: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中字节字节对齐如:

  1. typedef struct foo {   
  2.     char a;  
  3.     int b;  
  4. } __attribute__((packed)) foo;  

msvc中可用:

  1. #pragma pack(push, 1)  
  2. typedef struct foo {   
  3.     char a;  
  4.     int b;  
  5. } foo;  
  6. #pragma pack(pop)    

这里1为结构成员对齐值的上限。后者gcc也作为extension支持,所以后面这样写两个平台都可编译。

 

gcc下设置对齐值最小值为: 

  1. typedef struct  A{  
  2.     int b;  
  3. } __attribute__((aligned(32))) A;  

msvc中为:

  1. typedef __declspec(align(32)) struct  A{  
  2.     int b;  
  3. } A;  


2.  在main之前运行的代码

gcc中用__attribute__((constructor)),如:

  1. static void  __attribute__((constructor)) before(void) {  
  2.     printf("before\n");  
  3. }  

msvc中可用:

  1. int before()    
  2. {    
  3.     printf("%s\n""before");    
  4.     return 0;    
  5. }       
  6.     
  7. #pragma data_seg(".CRT$XIU")     
  8. int (*f) () = before;  
  9. #pragma data_seg()    

 或者:

  1. void foo()  
  2. {  
  3.     printf("foo\n");  
  4. }  
  5.   
  6. typedef void (__cdecl *PF)(void);  
  7.   
  8. #pragma section(".CRT$XCG", read)  
  9. __declspec(allocate(".CRT$XCG")) PF f[] = {foo};  


3. case range

gcc中switch语句中的数值范围可用...表示

  1. switch (x) {  
  2.     case 0x03 ... 0x10:  
  3.         // ...  
  4.         break;  
  5.     ...  
  6. }  

这种用法可以写脚本自动转换掉(http://blog.csdn.net/ariesjzj/article/details/7848467)。

 

4.  designated initializer

只为结构体中的指定成员赋值。

  1. struct foo {  
  2.     int i;   
  3.     int j;   
  4. };     
  5. struct foo f = {  
  6.     .j = 4  
  7. };  

在用gcc编译的项目中通常有大量这种用法,比如系统为每种设备提供统一的一套接口,而对于某种设备而言只需实现其中一部分接口,通常在注册时只会为一部分成员赋值。把赋值语句前统一加上结构体变量名(vi中在多行前统一加字符串:Ctrl+v,选中行,I(大写i),输入统一要加的前缀,Esc)然后放到初始化函数中即可,或者把结构体中每个成员变量在赋值时都补上,没有的就赋0或NULL。

 

对于成员比较多的结构体,则只能用脚本自动改了,这里是一例子:http://blog.csdn.net/ariesjzj/article/details/7944005

 

5. empty array

gcc extension允许空定义空数组,如果空数组是在结构体内部作为可变长度成员的头指针那倒好办,替换成单元素数组即可。有时候空数组是为了这种环形声明:

  1. int (*f[])(void);  
  2.   
  3. void help() {  
  4.     // use f[i]  
  5. }  
  6.   
  7. int (*f[])(void) = {  
  8.     ...  
  9.     help,  
  10.     ...  
  11. }  

其中help()用到了函数指针数组f,而f的定义又用到了help(),为了打破这种鸡-蛋声明结构,在f的声明前加上help()的声明,变成:

  1. void help();  
  2.   
  3. int (*f[])(void);  
  4.   
  5. void help() {  
  6.     // use f[i]  
  7. }  
  8.   
  9. int (*f[])(void) = {  
  10.     ...  
  11.     help,  
  12.     ...  
  13. }  


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)。如果用后者的话移植的时候会更好办一点,比如可以在共用头文件中定义:

  1. #ifdef _MSC_VER  
  2. #define __inline__  __inline  
  3. #endif  

或者在编译选项中都加上-D__inline__=__inline。如果原项目中用的inline关键字就更麻烦点,因为可能会把其它名字带inline这个字符串的也替换掉。

至于强制inline,gcc中有__attribute__((always_inline)),而msvc中有forceinline。

 

9 thread-local storage

gcc下:

  1. __thread int tls_i;  

msvc下:

  1. __declspecthread ) 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。如:

  1. #define DEF(str1)  str1  
  2.   
  3. char * a = DEF("a",  
  4. #ifdef _WIN32  
  5. "win32"  
  6. #else  
  7. "not win32"  
  8.  #endif  
  9. "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中有对应函数,但函数名不相同

重定义下就行,如:

  1. #ifdef _MSC_VER  
  2. #define strtoll _strtoi64  
  3. #define strtoull _strtoui64  
  4. #define snprintf _snprintf  
  5. #define popen   _popen  
  6. #define pclose  _pclose  
  7. #define strcasecmp _stricmp  
  8. #define strncasecmp _strnicmp  
  9. #endif  


13. msvc中不提供的函数

如rint, remainder, trunc, gettimeofday, va_copy等。这些函数Mingw gcc是通过build-in函数或是静态链接实现,而不依赖于msvcrt。这些函数只能拷贝实现了,如:

  1. #ifdef _MSC_VER  
  2.   
  3. __inline int gettimeofday(struct timeval *tp, void *tzp)  
  4. {  
  5.     time_t clock;  
  6.     struct tm tm;  
  7.     SYSTEMTIME wtm;  
  8.   
  9.     GetLocalTime(&wtm);  
  10.     tm.tm_year     = wtm.wYear - 1900;  
  11.     tm.tm_mon     = wtm.wMonth - 1;  
  12.     tm.tm_mday     = wtm.wDay;  
  13.     tm.tm_hour     = wtm.wHour;  
  14.     tm.tm_min     = wtm.wMinute;  
  15.     tm.tm_sec     = wtm.wSecond;  
  16.     tm. tm_isdst    = -1;  
  17.     clock = mktime(&tm);  
  18.     tp->tv_sec = clock;  
  19.     tp->tv_usec = wtm.wMilliseconds * 1000;  
  20.   
  21.     return (0);  
  22. }  
  23.   
  24. double trunc(double x)  
  25. {  
  26.     if (x > 0) return floor(x);  
  27.     else return ceil(x);  
  28. }  
  29.   
  30. int  
  31. fesetround (int round)  
  32. {  
  33.   unsigned short int cw;  
  34.   
  35.   if ((round & ~0xc00) != 0)  
  36.     /* ROUND is no valid rounding mode.  */  
  37.     return 1;  
  38.   
  39.   __asm { fnstcw cw}  
  40.   
  41.   cw &= ~0xc00;  
  42.   cw |= round;  
  43.   __asm {fldcw cw}  
  44.   return 0;  
  45. }  
  46.   
  47. __inline double rint(double dbl)  
  48. {  
  49.     _asm  
  50.     {  
  51.         fld dbl  
  52.         frndint  
  53.     }  
  54. }  
  55.   
  56. __inline long double rintl(long double x)  
  57. {  
  58.     _asm  
  59.     {  
  60.         fld x   
  61.         frndint  
  62.     }  
  63. }  
  64.   
  65. double remainder(double x, double y)  
  66. {  
  67.     double i = rint(x/y);  
  68.     return x - i * y;  
  69. }  
  70.   
  71. #ifndef va_copy  
  72. #define va_copy(dst, src) ((dst) = (src))  
  73. #endif  
  74.   
  75. #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来传,略有不同。比较保险点的方法就是在调用和被调用端都改为栈传参数。