原文地址: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 支持,所以后面这样写两个平台都可编译。

也可以如下修改

  1. #ifdef __GNUC__
  2. #define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
  3. #endif
  4. #ifdef _MSC_VER
  5. #define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop))
  6. #endif

然后可以这样使用:

  1. PACK(struct myStruct
  2. {
  3. int a;
  4. int b;
  5. });

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. #pragma data_seg(".CRT$XIU")
  7. int (*f) () = before;
  8. #pragma data_seg()

或者:

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

3. case range

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

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

这种用法可以写脚本自动转换掉。

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。

对于成员比较多的结构体,则只能用脚本自动改了

5. empty array

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

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

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

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

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

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

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

9 thread-local storage

gcc 下:

  1. __thread int tls_i;

msvc 下:

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

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

原文:https://blog.csdn.net/jinzhuojun/article/details/7881049