C的某些语法容易让人不小心触雷,比如
从0开始的下标
很多高级语言中,定义n个元素的数组,下标范围是从1到n,但C特殊,n元素的C数组中没有下标为n的元素,只有从0到n-1的下标。所以使用C数组时不要犯这种错误:
int i, a[10];
for ( i = 1; i <= 10 ; i++ ) { a[i] = 0; } //i=10时超出数组边界
八进制or十进制常数
C编译器会把数字025当作八进制,等于十进制的21,因此在使用常整数时不要认为多个0无关紧要,特别在从其他文件或标准中拷贝一些常系数数组时,要留意预防。
未初始化变量
变量未初始化往往带来很难查找的随机错误,下面是开发中真实的例子:
typedef struct tag_PARM{ //结构体定义
char* Clip;
bool InitParsingEnable;
……
bool IsHttpStream;
}PARM;
/*结构体初始化*/
PARM spliterParm;
spliterParm.Clip = &iFilename;
spliterParm.InitParsingEnable = iParsingMode;
/*调用*/
res = Open(&spliterParm);
IsHttpStream是后期添加的一个成员变量,初始化部分忘记给它赋值,而在open函数里有:if(pParm->IsHttpStream) ……else ……
这就导致Open随机执行到不同分支。变量未初始化的主要表现就是多次运行同样程序,随机错误。未初始化的问题很常见,特别是结构体,其定义和初始化不象普通局部变量那样相距很近,所以头文件里成员变量改变后很容易遗漏对应的初始化。
浮点数比较和交换
void main()
{
float x =1.2; float y = 3.2;
if((x + y)==4.4) { printf(“correct\n”); }
else { printf(“ error \n”); }
}
结果是error。浮点数是为了能用二进制表达小数而引入的一种量化形式,转换时存在舍入误差,因此不能直接用==比较浮点数。正确做法是确定一个误差范围,通过比较两浮点数差值是否落在这个误差范围内来判定是否相等,即:
const float delta = 0.1
void main()
{
float x =1.2; float y = 3.2;
float z = x + y;
if((z < (4.4+delta))&&(z > (4.4-delta))) { printf(“correct\n”); }
else { printf(“ error \n”); }
}
同样,由于浮点数有舍入误差,也不满足交换定律,如浮点数float x=1/3,y=1/6,z=1/7; 和整数不同,x*y/z不等于x*(y/z)。
字符串结尾的隐含结束符导致内存越界
char *r = malloc(strlen(s) + strlen(t));
strcpy(r, s);
strcat(r, t);
以上实现忘记了字符串结尾隐含的空字符,strlen()返回字符串里真正的字符数,但不包括结尾的空字符。因此如果strlen(s)是n,则s需要n+1个字节空间存储。即:
char *r = malloc(strlen(s) + strlen(t) + 1);
strcpy(r, s);
strcat(r, t);
log语句中的有效操作
有人认为printf/log/trace等打印函数的唯一功能就是输出调式信息,因而随意对待它们,但是printf等函数里也能包含有效操作,如:
int a[5]={6, 7, 8, 9, 10};
int *ptr = a;
printf(“%d”, *(++ptr));
*(ptr++) += 123;
单纯把printf看做log语句,修改程序时就可能随手注释掉(printf开开关关经常发生),而printf内的有效操作也同时被删除,类似情形还包括printf内的函数调用等。因此最好把printf和有效操作放在两条语句,否则是自己挖坑。