这本短小但却经典的书籍,确实指出了C语言编程中的不少值得关注的陷阱。在此只是将一些常见的缺陷列举出来,并对一些容易混淆、遗忘的知识点做一个记录。在以后的程序员生涯中,确实有必要再次阅读该书。

第一章 词法“陷阱”

1.1 =不同于==

注意不要=和==混淆使用,特别要注意“笔误”的发生。

1.2 & 和 | 不同于 && 和 ||
1.3 词法分析中的“贪心法”

编译器将程序分解为符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,并判断这两个字符能否组成一个有效的符号,如果可以,继续读入下一个字符并重复该判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。

1.4 整型常量

注意若一个常量的首字符是0,该常量会被视为8进制数。

1.5 字符与字符串

注意:’ ‘表示一个字符,而“ ”则表示字符串。前者是一个整数,而后者却是一个指向无名数组起始字符的指针。

第二章 语法“陷阱”

2.1 理解函数声明

typedef简化函数的声明:
声明signal函数:

void (*signal(int,void(*)(int)))(int);

使用typedef简化函数声明:

typedef void (*HANDLER)(int);
HANDLER signal(int,HANDLER);

typedef定义了HANDLER,其是一个返回void型的函数指针,且该函数指针的参数为int型。

2.2 运算符的优先级问题

运算符的优先级可以利用口诀进行记忆,即“单算移关与,异或逻条赋”
■“单”表示单目运算符:逻辑非(!),按位取反(~),自增(++),自减(–),取地址(&),取值(*);
■“算”表示算术运算符:乘、除和求余(*,/,%)级别高于加减(+,-);
■“移”表示按位左移(<<)和位右移(>>);
■“关”表示关系运算符:大小关系(>,>=,<,<=)级别高于相等不相等关系(==,!=);
■“与”表示按位与(&);
■“异”表示按位异或(^);
■“或”表示按位或(|);
■“逻”表示逻辑运算符:逻辑与(&&)级别高于逻辑或(||);
■“条”表示条件运算符(? :);
■“赋”表示赋值运算符(=,+=,-=,*=,/=,%=,>>=,<<=,&=,^=, |=,!=);
◆逗号运算符(,) 级别最低,口诀中没有表述
◆(),[],->这些其实不算运算符,级别最高

注意:单目运算符和条件运算符、赋值运算符采用自右至左的结合方式,
其余运算符采用自左至右的结合方式。

2.3 注意作为语句结束标志的分号

注意在条件判断语句后,不能多写一个分号,这样会造成逻辑处理上的bug。
注意在返回void型的函数中的return语句后不能少写一个分号,这样可能造成返回值错误。
注意在声明结构体、类等数据类型时,不要少写分号。

2.4 switch语句

switch中的case语句结束时不要忘记break语句,确实不需要break语句的时候应明确标注,方便代码维护。

2.5 函数调用
2.6 “悬挂”else引发的问题

注意else总是和最接近的if进行结合的特性。
编程规范,应对任何一个if及else都加{}处理。

第三章 语义“陷阱”

3.1 指针与数组

1.C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。C语言中数组的元素可以是任何类型的对象。
2.对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行的操作,其实都是通过指针进行的。

3.2 非数组的指针

非数组的指针

char *r,*s,*t;
r=malloc(strlen(s)+strlen(t));
strcpy(r,s);
strcat(r,t);

这段代码有三处错误:
1. malloc之后未检查返回值是否为NULL ;
2. 注意sizeof与strlen的区别,strlen计算字符串所包括的字符数目,不包含结尾的’\0’字符,所以分配内存时应该是strlen(s)+1;
3. 分配完的内存应及时释放,避免发生内存泄漏。

3.3 作为参数的数组声明

C语言中会自动将形参中作为参数的数组声明转化为对应的指针声明。

3.4 避免“举隅法”

注意:复制指针并不同时复制指针指向的数据。

3.5 空指针并非空字符串

编程时应注意空指针和空字符串的区别。

3.6 边界计算与不对称边界

注意数组的边界声明,比较好的方法是:用第一个入界点和第一个出界点来表示一个数值范围。入界点要包含在取值范围之内,而出界点不包含在取值范围内。

在循环中利用操作数组元素非常容易出现越界问题,需要特别注意。

3.7 求值顺序

在C语言中,定义了||、&&、?:、, 的求值顺序。

C语言规定,必须首先对左侧操作数求值,然后根据需要对右侧操作数求值;
对于逻辑操作符,根据需要利用了短路性质,即:
A&&B, 当A为false时,不去计算B的值而直接返回false;当A为true时,计算B的值。
A || B,当A为true时, 不去计算B的值而直接返回true;当A为false时,计算B的值。
比如:

if(count!=0 && (a/count)>2)
{
}

先计算左侧的表达式判断count是否等于0,count不为0才计算a/count。在这个例子中,求值顺序至关重要,否则会引发除0异常。
再比如一个例子:

char *p;
if(p!=NULL && *p)
{
}

首先判断p是否是空指针,然后判断是否是空字符串。

对于?:这个条件运算符,比如:a?b:c

操作数a先被求值,根据a的值再求操作数b或者操作数c的值,只计算其中一个值。

逗号运算符,首先对左侧操作数求值,然后该值被”丢弃”,在对右侧操作数求值。

C语言中其他所有运算符对其操作数求值的顺序是未定义的。特别指出,赋值运算符并不保证任何求值顺序。
例如:

i=0;
while(i<n)
{y[i++]=x[i];
}

在这个例子中,左侧操作数中的i++有可能比x[i]先执行,也有可能比x[i]后执行。

3.8 运算符&&、|| 和 !

注意逻辑与和位与、逻辑或和位或的区别。
逻辑运算有短路性质,而位运算则没有此性质。

3.9 整数溢出

在进行算术运算时,应考虑整数溢出的影响。如果溢出对结果有影响,就应该进行判断。

3.10 为函数main提供返回值

main函数如果未声明返回值,默认返回整数值。这个返回值用来告知操作系统执行结果,返回0表示成功,返回非0表示失败。
大部分情况下这样做没有问题。但如果系统关注这个执行结果,就必须明确返回一个有意义的值。

第四章 连接

4.1 什么是连接器

连接器不懂C语言,编译器的责任是把C源程序“翻译”成对连接器有意义的形式,然后连接器把由编译器或汇编器生成的若干个目标模块,整合成一个被称为载入模块或可执行文件的实体,该实体能够被操作系统直接执行。

连接器通常把目标模块看成由一组外部对象组成的。每个外部对象代表着机器内存中的某个部分,并通过一个外部名词来识别。因此,程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部对象。

连接器读入目标模块和库文件,同时生成载入模块。对每个目标模块中的每个外部对象,连接器都要检查载入模块,看是否已有同名的外部对象。如果没有,连接器就将该外部对象添加到载入模块中;如果有,连接器就要开始处理命令冲突。

4.2 声明与定义

使用extern关键字进行声明。每个外部变量只能定义一次。

4.3 命名冲突与static修饰符

使用static关键字,将变量定义为只能在本文件中使用的变量,可以有效减少与外部同名变量之间的命名冲突。

4.4 形参、实参与返回值
4.5 检查外部类型

保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任。而且,“相同类型”应该是严格意义上的相同。
如:一个文件中的

char filename[] ="/etc/passwd";

与另一个文件中的

extern char* filename;

类型就不同。

4.6 头文件

第五章 库函数

5.1 返回整数的getchar函数
5.2 更新顺序文件

C语言中调用fread和fwrite函数交替对文件进行操作时,应该在两个函数调用之间调用一次fseek。

5.3 缓冲输出与内纯分配
int main()
{char buf[512];setbuf(stdout,buf);while((c=getchar())!=EOF)putchar(c);
}

以上代码是错误的。
原因在于,程序调用库函数setbuf,通知输入/输出的数据首先缓存在buf中,main函数返回时,buf被释放,控制权交给操作系统之前C运行时库要进行清理工作,会引用已经被释放的buf。
解决办法:1.将缓冲数组成为静态数组;2.把buf声明完全移到main函数之外。

5.4 使用errno检测错误

在调用库函数是,我们应该首先检测作为错误指示的返回值,确定程序执行已经失败。然后,再检查errno,来搞清楚错误原因。

5.5 库函数signal

第六章 预处理器

6.1 不能忽视宏定义中的空格
6.2 宏并不是函数

C语言中的宏的本质是编译之前的文本替换。因此大多数宏定义都用括号括起来,防止在文本替换之后由于运算符优先级问题使得运算顺序与预期不一致。
尽量避免在宏定义中使用++和–操作符,或者在宏定义中改变指针的值。
宏展开可能产生非常庞大的表达式,占用的空间远远超过了编程者所期望的空间。

6.3 宏并不是语句

若assert语句被定义为如下的宏:

#define assert(e) if (!e) assert_error(_FILE_,_LINE_)

那么,在将assert应用于条件语句中时,可能会照成else悬挂。
assert实际上不是类似于一个语句,而是类似一个表达式。

#define assert(e) ((void) ((e)|| _assert_error(_FILE_,_LINE_)))
6.4 宏并不是类型定义

将宏定义与类型定义typedef区分开来

第七章 可移植性缺陷

7.1 应对C语言标准变更
7.2 标识符名称的限制
7.3 整数的大小

在不同的编译器上,整数的长度并不完全相同,可能是2个字节,也可能是4个字节,甚至可能是8个字节,移植代码的时候要注意。
一个建议就是自己在代码中定义整数的类型,这样即能统一,又方便修改。

7.4 字符是有符号整数还是无符号整数

把一个char型变量转化为int变量,编译器会自动进行符号位扩展。尤其当字符变量的最高位为1时,要特别注意这个问题。

unsigned int a = 0;
int b = 0;
int d=0;
char c = 0x80;
a = c; //c将首先被转换为int类型,由于存在符号位扩展,a=0xffffff80
b = c; //c将首先被转换为int类型,由于存在符号位扩展,b=0xffffff80
d = (unsigned char)c;//c是无符号类型,不会进行符号位扩展,d=0x00000080
7.5 移位运算符

当操作数是有符号数,>>将以符号位填充空出的位;当操作数是无符号数,>>将以0填充空出来的位。

在C语言中,移位位数必须在大于等于0小于n(移位对象的位数),否则编译器会报错。
有符号数中的 负数 向右移位运算并不等同于除以2的某次幂。对于非负数,移位运算相比除法,将大大提高运行速度。

7.6 内存位置0

null指针并不指向任何对象。因此,除非是用于赋值或比较运算,出于其他任何目的的使用null指针都是非法的。

7.7 除法运算时发生截断
7.8 随机数的大小
7.9 大小写转换
7.10 首先释放,然后重新分配

malloc函数,realloc函数与free函数的使用方法。

7.11 可移植性问题的一个例子

参考:
《C陷阱与缺陷》

《C陷阱与缺陷》 阅读总结相关推荐

  1. 【读书】《非暴力沟通》

    得益于十点读书,在2月中完成开年来的第二本书籍阅读.本书作者马歇尔.卢森堡博士是国际性缔造和平组织非暴力沟通中心(CNVC)的创始人和教育服务主管,马歇尔.卢森堡博士由于在促进人类和谐共处方面的突出成 ...

  2. 《非暴力沟通》读书笔记

    <非暴力沟通>读书笔记 [本书作者] 马歇尔·卢森堡,卡尔·罗杰斯的弟子,同时其思想深受"圣雄"甘地和存在主义哲学大师马丁·布伯的影响. [本书要解答的问题] 是什么使 ...

  3. 摘录与感想:非暴力沟通

    摘录与感想:非暴力沟通 1.观察2.感受3.需要4.请求 首先,留意发生的事情.我们此刻观察到什么?不管是否喜欢,只是说出人们所做的事情.要点是,清楚地表达观察结果,而不判断或评估.接着,表达感受,例 ...

  4. 《非暴力沟通》:有些话真的可以好好说

    本文结构 - 前言 - 非暴力沟通简介 - 01 观察和评价 - 02 体会表达感受 - 03 感受背后的需求 - 04 提出请求 - 需求驱动 - 好好说话的力量 本文共计:3000字4图 预计阅读 ...

  5. 《非暴力沟通》- 笔记

    非暴力沟通的核心:当我们情绪受伤的时候,都是某些需求没有满足.你现在最要做的是发现需求,而不是发泄情绪.情绪是双刃剑,说出去很爽快,但会造成不好的后果. 非暴力沟通的步骤: 先说事实 再说感受 再说自 ...

  6. 2016年第7本:非暴力沟通

    周首送我的这本书<非暴力沟通>(NVC,Nonviolent Communication),是马歇尔·卢森堡博士发明的一种沟通方式,全书强调了四要素,共8个字:观察.感受.需要.请求.就是 ...

  7. 《非暴力沟通》听书心得

    沟通很多时候也是一门艺术,不是吗? 1.  一句话总结 非暴力沟通方法可以概况为四个字:观.感.需.求 观(观察).感(感受).需(需求).求(请求) 2.   精髓含义 观:仔细观察当下,而不要和& ...

  8. 【读书笔记】非暴力沟通

    文章目录 背景 理论 感受 反省 总结 推荐 背景 这个季度看了几本书,比如<一个人的朝圣>.<呼兰河传>.<元红>等等,女友也推荐了一本书给我,书名是<非暴 ...

  9. 读《非暴力沟通》马歇尔·卢森堡

    前言 非暴力生活的一个关键就是:感激生活的赐予,而不贪心 为了清晰的表达感受,我们编制了以下的词汇表 表达我们的需要得到满足时的感受 兴奋/喜悦/欣喜/甜蜜/精力充沛/兴高采烈/ 感激/感动/乐观/自 ...

  10. 《非暴力沟通》读后感

    最近几天偶然了发现桌角的kindle,才发现原来已经好久没有碰过她了.打开封盖,还有百分之60+的电量,着实让我一惊. 为了不让花出去的钱白白吃土,于是每天晚上睡觉前,就打开kindle看一会儿, 最 ...

最新文章

  1. iOS 7 — navigationController is setting the contentInset and ContentOffset of my UIScrollView
  2. 计算机能思考吗?图1专题6:“人脑是计算机吗?”
  3. Codeforces#363 Div2
  4. (转载)Android进阶2之Activity之间数据交流(onActivityResult的用法)
  5. 点标记 高德地图_打尽渣男渣女的查岗神器?高德家人地图实测
  6. easyUI 添加排序到datagrid
  7. Java初学者推荐的几本书
  8. html5设置视频显示第一帧,如何检测HTML5视频何时播放第一帧?
  9. 生成对抗网络(GAN)的发展史
  10. Python中星号、下画线、斜线含义汇总
  11. windows下vbs脚本隐藏控制台
  12. 我国计算机辅助翻译专业,我国翻译硕士专业之计算机辅助翻译课程调查.pdf
  13. SQL注入 时间延时注入语句
  14. 怎么把图片进行压缩?分享几种压缩图片的方法
  15. 学Python爬虫,就得从爬高清美图开始!
  16. 新冠治愈之旅和未来的时光
  17. 汇编语言 从键盘输入一系列以$为结束符的字符串,然后对其中的非数字字符计数,并显示出计数结果
  18. win10 你的手机
  19. 聚美优品正式退市:陈欧下一个将为谁代言?
  20. 【教你区分】TVS二极管和稳压二极管的不同

热门文章

  1. 2021年进销存管理软件商户门店使用热度前十名排行榜
  2. Mysql数据库Sql语句更改主键设置
  3. 世界各个地区WIFI 2.4G及5G信道一览表(附无线通信频率分配表)
  4. (已更新)娱乐微信小程序源码下载-多玩法安装简单
  5. 计算机运维项目管理,工程项目管理软件的运维分为几个阶段
  6. 利用Visio DIY自己的示意图
  7. 日历2017 年终总结新年工作汇报PPT模板免费下载_PPTX图片设计素材_包图网888pic.com...
  8. ideaIU-2019.2.4版安装
  9. GAN(生成对抗神经网络 )的一点思考
  10. MRI脑影像分析从哲学到技术:一文搞懂VBM预处理基本原理(全网最详细解析)