1.1 PRE30-C 不要通过连接创建通用字符名称
C语言支持自定义通用字符来用于标识符、字符常量、字符串常量,以表示不在基本字符集中的字符(一个字节表示)。
表示方法:\Unnnnnnnn,8位16进制的数值对应的本地支持的多字节编码对应的字符,\unnnn表示4位16进制对应的本地的字符;
如果通用字符名称由通过多个字符通过字符连接生成,将产生未定义行为;
不相容代码示例:
#define assign(uc1, uc2, val) uc1##uc2 = val
void func(void)
{
int \u0401;
/*.....*/
assign(\u04, 01, 4);
/*....*/
}
相容代码解决示例:
#define assign(ucn, val) ucn = val
void func(void)
{
int \u0401;
/*.....*/
assign(\u0401, 4);
}
1.2 PRE31-C 避免不安全宏参数的副作用
不安全的类函数的宏扩展会造成其参数计算超过一次或者完全不计算(本质原因是因为宏是直接替代而不是传形参),在调用不安全宏时,参数不要包含赋值、自增、自减、volitile访问、输入输出或者其他有副作用的表达式(函数调用也可能带来副作用)
不相容代码示例1:
#define ABS(x) ((x > 0) ? x : (-x))
void func(int n)
{
/*Validate that n is within the desired rang */
int m = ABS(++n);
/*....*/
}
扩展为:m = ((++n > 0) ? (++n) : -(++n));
其中n递增了两次;
相容解决方案:
1、在宏执行前先进行递增运算,并标注该宏不安全
#define ABS_UNSAFE(x) ((x > 0)? x : (-x)) //UNSAFE
void func(int n)
{
/*Validate that n is within the desired rang */
++n;
int m = ABS(n);
/*....*/
}
2、采用内联或者静态函数代替宏
static inline int iabs(x)
{
return ((x > 0) ? x : (-x));
}
void func(int n)
{
/*Validate that n is within the desired rang */
int m = iabs(++n);
/*....*/
}
注:采用z这种方法参数的值有了限制,采用宏则没有限制
3、采用泛型选择既有宏又有内联函数(实质上是用宏调用不同的内联函数),列出所有类型的内联函数和宏所有支持的内联函数
#include <complex.h>
#include <math.h>
static inline long long llabs(long long v)
{
return v < 0 ? -v : v;
}
static inline long labs(long v)
{
return v < 0 ? -v : v;
}
static inline int llabs(int v)
{
return v < 0 ? -v : v;
}
static int sabs(short v)
{
return v < 0 ? -v : v;
}
static inline int scabs(signed char v)
{
return v < 0 ? -v : v;
}
#defien ABS(v) _Generic(v, signed char : scabs, \
short : sabs, \
int : iabs ,\
long : labs,\
long long : llabs\
float : fabsf,\
double : fabs,\
long double : fabsl,\
double complex : cabs,\
float complex : cabsf,\
long double complex : cabsl)(v)
void func(int n)
{
/*Validate n is within the desired rang*/
int m = ABS(++n); //宏中先算,在传如函数形参
}
注:泛型选择在C11中推出,在C99和更早版本的C语言标准中不可用
4、采用GCC编译器时可用__typeof扩展声明宏操作数并为其赋值相同类型的一个临时值,在临时值上进行计算保证操作数只计算一次。加上GCC的另一个扩展—(语句表达式 statement express),使得表达式出现在应该出现的地方
#define ABS(x) __extension__ ({ __typeof (x) tmp = x; \
tmp < 0 ? -tmp : tmp;})
不相容的代码示例2(assert()):
assert()类函数宏是在代码中增加的方便诊断机制,assert函数表达式取决于类对象宏NDEBUG的定义,如果其未定义,则assert()被定义为判断表达式的真假,如果为0(在标准的C库中)则调用abort()函数;如果定义了NDEBUG,assert()则被定义为((void ) 0),此时不会产生副作用;
#include <assert.h>
#include <stddef.h>
void process(size_t index)
{
assert(index++ > 0); //side effect
}
相容解决方案:
#include <assert.h>
#include <stddef.h>
void process(size_t index)
{
assert(index > 0); //No side effect
index++;
}
PRE32-C.不要在类函数的宏调用中使用预处理命令
类函数的宏调用时参数中不能包含预处理指令(GCC缺陷),如#define、#ifdef和#include。此行为为未定义行为;
此规则也是用于未知是否是宏的函数参数中对预处理指令的使用。例如标准的库函数memcpy()、printf()、和assert()可能就是以宏来实现的;
不相容代码示例
该示例中,程序员使用预处理命令指定memcpy()的平台指定参数,如果memcpy()采用宏实现,代码会造成未定义行为:
#include <string.h>
void func(const char *src)
{
/*validate the source string ; calculate size*/
char *dest;
/*malloc() destination string*/
memcpy(dest, src,
#ifdef PLATFORM1
12
#else
14
#endif
);
}
相容解决方案:
#include <string.h>
void func(const char *src)
{
/*validate the source string ; calculate size*/
char *dest;
/*malloc() destination string*/
#ifdef PLATFORM1
memcpy(dest, src, 12);
#else
memcpy(dest, src, 14);
#endif
}