1:strlen 的陷阱
strlen在标准库中的原型是
size_t strlen(char const *string);
问题出在size_t上,它是在stddef.h中定义的无符号整数类型。
由于无符号整数类型的运算不会出现负数,所以就导致如下陷阱:
strlen(x) > strlen(y) ;
不等价于
strlen(x) - strlen(y) >0;
举例说:
strlen(x) 值为 3, strlen(y) 值为4,
那么 strlen(x) >= strlen(y) 为假,这是显而易见的,也是你熟知的。
但是 strlen(x) -strlen(y) >=0 永远为真,不论x、y长度如何变化。
原因是x、y为无符号整数,运算结果也是无符号整数,所以值永远不会小于0.
那么strlen(x)-strlen(y) 到底是多少呢?你可能会大跌眼镜,答案是:
如果你是32位int的机器(当前几乎所有pc都是),那么结果是 2^32,也就是最大的unsigned int值(用十进制表示就是4294967296)!!!
-----------------------------------
如果上面语句出现在if中,那么if语句总为真。
解决方案是强制转换为int。
如 if((int)(strlen(x)-strlen(y))>0) {...};
值得一说的是,你或许会觉得标准库这个设计坑爹(很容易用错),想自己实现一个strlen。
我的建议是不要这么做,因为你基本上不大会如愿。标准库函数有时是用汇编实现的,以便充分利用机器的特殊指令,以求最高效的速度。
几乎可以肯定的是,你自己写的函数肯定不会比标准库快(虽然可能更方便)。复用软件,尤其是标准库,乃是上上之策。
2:strcpy的陷阱
strcpy的标准库原型是
char *strcpy(char *dst,char const *src);
(注意:dst是字符数组,不能是指向静态常量空间中字符串的指针。否则,调用strcpy函数会出错,因为常量不能修改。)
陷阱是,如果dst没有足够空间容纳src,src仍将被赋值。结果是超出的字符部分将覆盖dst后面的存储空间。原先写在dst后面的存储变量就丢失了。
原因是strcpy函数无法判断dst字符数组的长度。
-----------------
解决方法:保证dst有足够的空间容纳src。
3:strcmp的陷阱
strcmp的标准库原型是
int strcmp(char const *s1,char const *s2);
如果s1、s2相同则返回0,s1大于s2,返回正数,s1小于s2,返回负数
确切来说,这个陷阱与上面不同,不是库实现的问题,而是程序员容易用错的问题:
if(strcmp(a,b)){};
粗略一看,意思就是“如果a等于b,if为真”。。。
仔细一看,表达意思恰恰相反。
一个好的建议是,在if中,永远不要尝试用0表示假,而是用FALSE宏。
这里还要注意:我没说定义TRUE宏,因为TRUE宏导致的问题,可能你气死都难以发现。
比如,定义TRUE 为1,看定义毫无问题。但如果这么用
if(strcmp(a,b) == TRUE),就悲剧了。因为strcmp除了返回0,还可能返回所有可能的int值,而不是只有1.
这个小心了,尤其是爱用TRUE的同学,给个建议,永远要避免用TRUE,而是用“非FALSE”。
就像一个笑话,“世界上除了恶人,就是非恶人!”,而不能说“世界上除了恶人,就是善人”,因为多数人是“平凡人”。
4:strcat 的陷阱
char *strcat(char *dst, char const *src);
简而言之吧,你必须考虑dst能够存储自身原来的字符串以及src。
----------------------
插一段:上面的3个函数都有一个重载版本,分别对应
char *strncpy(char *dst, char const *src, size_t len);
char *strncat(char *dst, char const *src, size_t len);
int strncmp(char const *s1, char const *s2, size_t len);
不过,如果不是特别必须传入一个长度参数,我极度不建议使用这3个函数(因为相比于刚才的3个函数,你又多一个可怕的参数),除非你非常非常清楚地知道这3个函数可能的后果。
这3个函数的设计缺陷引起的后果有时是灾难性的,可能直接导致程序崩溃。这不能不说是标准库的一个遗憾(或者说是C语言特性先天注定的)。
比如说,下面这段代码:
char buffer[SIZE];
strncpy( buffer, name, SIZE);
buffer[SIZE-1]='\0';
如果name和SIZE值大于buffer长度,而且没有第三句代码,那么新的buffer字符串将没有结束符。如果使用strncpy的返回值初始化字符串或者计算buffer长度,一旦strlen函数一直找结束符、而越过程序不该访问的内存地址,那么程序将直接崩溃。
而strncat的实现更让人崩溃,直接向dst复制len个字符以及一个结束符,才不管dst是否有足够空间。
因此,个人建议,用这些函数时,谨慎谨慎再谨慎。
5:strstr 的陷阱
在字符串中查找字符串
char *strstr(char const *s1, char const *s2);
在s1中查找s2第一次出现的位置,如果找到,返回指向该位置的指针。
如果找不到,返回NULL指针。
如果s2是空字符串,返回s1。(这里是陷阱)
因为,代码:
char *s1="abc";
char *s2="abc";
char *s3="";
此时 strstr(s1,s2) 等于strstr(s1,s3),也都等于s1。
不过,相对上面的几个陷阱来说,这个陷阱主要是因为大家一般不会使用s2为空字符串的情况,导致遗忘这个细节。
---------------------
题外话,老实说,从07年开始编程,我就一直觉得C语言不适合初学者。奇怪的是多数学校都是从C语言开始教学生。这导致了一个结果,即使学生学了4年的C,一个可以肯定的事实是,不见得有多少人真正吃透了C,导致仍然可能写出漏洞百出,还有可能包含极度隐蔽的错误的程序。一方面,C的不足之处真的太多,毕竟设计的比较早,另一方面,C真的不容易学透,就是学皮毛也比学C#、Java、PHP、Python等难入门(这里面C指针的“贡献”几乎是坐一望二)。
我个人推荐的学习路线,想做网站的,学学PHP、HTML、Javascript等,想做真正程序员的,学学C#、Java、Python等就OK,想做高手的,C/C++是必经之路,最好汇编也懂。对多数程序员,就没必要研究C、C++了(虽然说,C++多数情况可以规避指针,但学C++而不用指针,还不如去学C#和Java!)。
-----------------------------------------------------------------
6:struct 的陷阱
看下面的声明:
typedef struct {
int a;
self_ref *b;
int c;
} self_ref;
这个陷阱还是比较明显的。因为知道结构定义的最后,才给出结构名称,所以在struct 内部,self_ref 尚未定义。
解决方法是,加一个结构标签。
typedef struct self_ref_tag {
int a;
struct self_ref_tag *b;
int c;
} self_ref;
关于结构标签的技巧还有另外一个应用,即互相声明中使用,如下:
struct B;
struct A {
struct B *b;
};
struct B {
struct A *a;
};
在A声明之前先声明B的不完整声明。