C语言 陷阱

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的不完整声明。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值