C90中对C99某些特性的替代解决方案

在2011年末,ISO/IEC 9899标准委员会公布了C语言的最新官方标准——ISO/IEC 9899:2011,俗称C11标准。这个标准对C语言增加了轻量级的范型、原子操作接口、宽字符表达、多线程接口等诸多特性。

不过由于现在有些老旧的编译器连C99标准都没完全支持好,所以这边主要讲述如何应对C99标准中所提供的便利的语法特性而C90则没有的问题。比如,常用的C99的两个特性是不定参数的宏定义以及结构体、联合体的初始化器。

1、由于C语言函数标准接口中具有不定参数个数的函数表达方式——比如void foo(...);因此可以直接将宏定义为函数名即可。而在C99中则在宏定义时也使用不定参数的宏定义,比如:#define MY_FOO(...)    foo(__VA_ARGS__)。下面的代码例子中将会描述如何自定义调试时的信息打印接口,分别针对C99与C90的情况。在这个例子中,对于C90的情况使用了一个哑函数。该函数由于没有提供任何实现,因此在Release模式下会被编译器完全优化掉,从而不占任何运行时存储空间。

2、在C语言中,对联合体的初始化向来是个讨厌的事情。因为对联合体变量的初始化是根据该联合体第一个成员的类型来定的。比如下面的代码例子中,联合体的第一个元素类型为int,所以在C90中只能用int类型的变量对其实施初始化赋值。而在C99中则可直接指定所要初始化赋值的成员。

#include <stdio.h>

// C99 form
#ifdef DEBUG
#define MY_DEBUG_LOG(...)   printf(__VA_ARGS__)
#else
#define MY_DEBUG_LOG(...)
#endif

// C90 form
#ifdef DEBUG
#define MY_DEBUG_TRACE      printf
#else
static void MyDummyPrint(const char *s, ...) { }
#define MY_DEBUG_TRACE      MyDummyPrint
#endif

// C99 form
static const union { int i; float f; }uns[] = {
    {.i = 100}, {.f = 0.5f}, {.i = -100}, {.f = -0.25f}
};

// C90 form
static const union { int i; float f; }uns0[] = {
    100,  0x3F000000, -100, 0xBE800000
};

int main(void)
{
    MY_DEBUG_LOG("Hello, world! The value is: %d\n", 100);
    MY_DEBUG_TRACE("Hello, world! The value is: %d\n", -100);
    
    printf("In C99, uns[0] = %d, uns[1] = %f, uns[2] = %d, uns[3] = %f\n", uns[0].i, uns[1].f, uns[2].i, uns[3].f);
    printf("In C90, uns[0] = %d, uns0[1] = %f, uns0[2] = %d, uns0[3] = %f\n", uns0[0].i, uns0[1].f, uns0[2].i, uns0[3].f);
}

 

在上述代码中,MY_DEBUG_LOG宏在C99形式下与C90形式下的表现并不完全一致。可以考虑以下代码:

int a = 100;
MY_DEBUG_LOG("Hello, world! The value is: %d\n", ++a);
printf("After a increment: %d\n", a);

在Release模式下,如果是C99形式,那么最后一句printf的输出中,a的值仍然为100;而C90形式,a的值会被加1,变为101。这时,如果想让C99模式变为C90模式的话,只要直接将它变为C90的表述方式即可。然而如果想让C90模式变为C99那样对a的值不进行修改,那么需要对这个宏作如下调整:

// C90形式,并在Release模式下不对宏中的参数作任何修改
#ifdef DEBUG
#define MY_DEBUG_TRACE      printf
#else
#define MY_DEBUG_TRACE      (void)sizeof
#endif

这里利用了sizeof()操作符在编译时不对其操作数做任何计算,而仅仅取其返回类型。


当然,对于C99形式的MY_DEBUG_LOG宏定义也可以写作为:

#define MY_DEBUG_LOG(format, ...)   printf(format, ##__VA_ARGS__)

这里,需要在__VA_ARGS__的前面加##。如果不加##,那么当format后面不添加任何参数时,编译器就会报错,因为它会把format后面的逗号也被扩展到源代码中。比如:MY_DEBUG_LOG("Hello"),如果__VA_ARGS__不添加,那么会被扩展为printf("Hello",),从而引发编译错误。

而如果在__VA_ARGS__前添加了##之后,若...处的宏实参不填写,那么其前面的逗号会被忽略。

posted @ 2013-05-15 13:14  zenny_chen  Views(1100)  Comments(0Edit  收藏  举报