代码从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等值。