如何写论文?写好论文?免费论文网提供各类免费论文写作素材!
当前位置:免费论文网 > 美文好词 > 优质好文 > c陷阱与缺陷

c陷阱与缺陷

来源:免费论文网 | 时间:2017-05-04 06:10 | 移动端:c陷阱与缺陷

篇一:《C陷阱与缺陷》

《C陷阱与缺陷》读书笔记 前记

2010年上半学期软件学院课程不是太多,有了很多空余时间,于是利用一个月的时间把

《C陷阱与缺陷》这本书看了一遍。正如ACCU主席Francis Glassborow所说“本书所提示

的知识,至少能够帮助你减少C代码和初级C++代码中90%的Bug”,这本书真的是C和C++

方面的经典书籍。

《C陷阱与缺陷》是由Andrew Koenig所著,高巍译。Andrew Koenig是AT&T大规模

程序研发部(前贝尔实验室)成员,不仅有着多年的C++开发,研究和教学经验,而且还亲

身参与了C++的演化和变革,对C++的变化和发展起到重要的影响。

正文

《C陷阱与缺陷》读书笔记

各章简介

第一章 记法“陷阱”:考察在程序被记法分析器分解成各个符号的过程中可能出现的问

题。

第二章 语法“陷阱”:如果没有正确理解这些语法细节,将会出现怎么的错误?

第三章 语义“陷阱”:有关语义误解的问题。

第四章 连接:有关组成C程序的若干部分的连接问题。

第五章 库函数:库函数的误用。

第六章 预处理器:与预处理器有关的内容。

第七章 可移植性缺陷:可移植性问题。

第八章 建议:有关预防性程序设计的一些建议。

第一章 词法“陷阱”

程序中的单个字符鼓励看来并没有什么意义,只有结合上下文才有意义。

编译器中负责将程序分解为一个一个符号的部分,一般称为“语法分析器”。

1.1=不同于==

while (c='' || c==' ' || c==' ')

c=getc(f);

这个循环将一直进行到文件的结束,是否死循环取决于getc的实现。如果确实需要在

条件判断部分使用赋值,应该显式地进行比较:

if ((x=y)!=0)

foo();

1.2 & 和 | 不同于 && 和 ||

1.3 语法分析中的“贪心法“

当C编译器读入一个字符后又跟了一个字符,那么编译器就必须做出判断:是将其作

为两个分别的符号对待,还是合起来作为一个符号对待。C语言对这个问题的解决方案可以

归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。

a---b 与 a -- - b 的含义相同,而与 a - -- b 的含义不同。

1.4 整型常量

如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数。

1.5 字符与字符串

C语言中的单引号和双引号含义迥异,在某些情况下如果把两者弄混,编译器并不会检

测报错,从而在运行是产生难以预料的结果。

用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字

符集中的序列值。

用双引号引起的字符串,代表的却是一个指向无名数字起始字符的指针,该数组被双引

号之间的字符以及一个额外的二进制为零的字符''初始化。

然而,某些C编译器对函数参数并不进行类型检查,特别是对printf函数的参数。因此,如果用

printf(' ');

来代替正确的

printf(" ");

则会在程序运行的时候产生难以预料的错误,而不会给出编译器诊断信息。

整型数(一般为16位或32为)的存储空间可以容纳多个字符(一般为8位),因此有个C

编译器允许在一个字符常量(以及字符串常量)中包括多个字符。也就是说,用'yes'代替"yes"

不会被该编译器检测到。后者的含义是“一次包括'y''e''s'以及空字符''的4个连续内存单元的首

地址“。前者的含义并没有准确的进行定义,但大多数编译器理解为,“一个整数值,由'y''e''s'

所代表的整数值按照特定编译器实现中定义的方式组合得到“。

(注:在Borland C++ v5.5 和 LCC v3.6中采取的做法是,忽略多余的字符,最后的整

数值即第一个字符的整数值;而在Visual C++ 6.0 和 GCC v2.95中采取的做法是,依次用后

一个字符覆盖前一个字符,最后得到的整数值即最后一个字符的整数值。)

第二章 语法“陷阱”

2.1 理解函数声明

(*(void(*)())0) (); 任何复杂表达式其实只有一条简单的规则:按照使用的方式来声明。

任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符(declarator)。声

明符从表面上看与表达式有些类似,对它求值应该返回一个声明中给定类型的结果。

因为声明符与表达式的相似,所以我们也可以在声明符中任意使用括号:

float ((f)); 这个声明的含义是:当对其求值时,((f))的类型为浮点类型,由此可以推知,f也是浮

点类型。

各种形式的声明还可以组合起来,就像在表达式中进行组合一样。因此,

float *g(),(*h)()表示*g()与(*h)()是浮点表达式。因为()结合优先级高于*,*g()也就是*(g()):g

是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出h是一个函数指针,

h所指向函数的返回值为浮点类型。

一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到

了:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封

装”起来即可。例如,因为下面的声明:

float (*h)();

表示h是一个指向返回值为浮点类型的函数的指针,因此,(float (*)())表示一个“指向返

回值为浮点类型的函数的指针”的类型转换符。

(*fp)(); -> (*0)(); -> (*(void (*)())0)();

2.2 运算符的优先级问题

优先级最高者其实并不是真正意义上的运算符,包括:数组下标,函数调用操作符各结

构成员选择操作符。他们都是自左于右结合,因此 a.b.c的含义是(a.b).c。

() [] -> . 单目运算符的优先级仅次于前述运算符。在所有的真正意义上的运算符中,它们的优先

级最高。单目运算符是自右至左结合。因此*p++会被编译器解释成*(p++)。

! ~ ++ == = (type) * & sizeof

优先级比单目运算符要低的,接下来就是双目运算符。在双目运算符中,算术运算符的

优先级最高,移位运算符次之,关系运算符再次之,接着是逻辑运算符,赋值运算符,最后

是条件运算符。

* / %

+ -

<< >>

< <= > >=

== !=

&

^

|

&&

||

?:

我们需要记住的最重要的两点是:

1.任何一个逻辑运算符的优先级低于任何一个关系运算符。

2.移位去处符的优先级比算术运算符要低,但是比关系运算符要高。

2.3 主义作为语句结束标志的分号

2.4 关于switch语句

case ' ':

linecount++;

case ' ':

case '':

.......

2.5 函数调用

f();是一个函数调用语句,而f; 计算函数f的地址,却并不调用该函数。

2.6 “悬挂”else引发的问题

if (x == 0)

if (y == 0) error();

else{

z = x + y;

f(&z); }

第三章 “语义”陷阱

3.1 指针和数组

C语言中的数组值得注意的地方有以下两点:

1.C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。然而,C语言中数组的元素可以是任何类型的对象,当然也就可以是另外一个数组。

(注:C99标准允许变长数组(VLA)。GCC编译器中实现了变长数组,但细节与C99标准不完全一致。)

2.对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕他们看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。

很多程序设计语言中都内建有索引运算,在C语言中索引运算是以指针算术的形式来定义的。

如果一个指针指向的是数组中的一个元素,那么我们只要给这个指针加1,就能够得到指向该数组中下一个元素的指针。同样地,如果我们给这个指针减1,得到就是指向该数组中前一个元素的指针。

int calendar[12][31];

int *p;

则p = calendar; 是非法的。因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用calendar名称会将其转换为一个指向数组的指针;而p是一个指向整型变量的指针,这个语句试图将一种类型的指针赋值给另一种类型的指针。

要构造一个指向数组的指针的方法:

int calendar[12][31]; int (*monthp)[31]; monthp = calendar; 这样,monthp将指向数组calendar的第一个元素,也就是数组calendar的12个有着31个元素的数组类型元素之一。

3.2 非数组的指针

在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符('')的内存区域的地址。

假定我们有两个字符串s和t,我们希望将这两个字符串连接成单个字符串t。

考虑: char *r,*malloc(); r = mallor(strlen(s) + strlen(t)); strcpy(r,s); strcat(r,t); 这个例子的错误有3点: 1,malloc函数有可能无法提供请求的内存。 2,显式地分配了内存必须显式地释放内存。 3,malloc函数并未分配足够的内存。 正确是方法: char *r,*malloc();

r = malloc(strlen(s) + strlen(t) + 1);

if(!r) {

complain();

exit(1); } strcpy(r,s); strcat(r,t); /*一段时间之后*/ free(r);

3.3 作为参数的数组声明

在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会立刻被转换为指向该数组第1个元素的指针。

因此,将数组作为函数参数毫无意义。所以,C语言中会自动地将作为参数的数组声明转换为相应的指针声明。

3.4 避免“举x法”

需要记住的是,复制指针并不同时复制指针所指向的数据。

3.5 空指针并非空字符串

出了一个重要的例外情况,在C语言中将一个整型转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊的情况就是常数0,编译器保证由0转换而来的指针不等于任何有效的指针。

#define Null 0 需要记住的重要一点是,当常数0被转换为指针使用时,这个指针绝对不能被解除引用(dereference)。 换句话说,当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。

3.6 边界计算与不对称边界

在所有常见的程序设计错误中,最难于察觉的一类是“栏杆错误”,也常被称为“差一错误”(off-by-one error)。

避免“栏杆错误”的两个通用原则:

(1) 首先考虑最简单情况下的特例,然后将得到的结果外推。

(2) 仔细计算边界,绝不掉以轻心。

用第一个入界点和第一个出界点来表示一个数值范围 能够降低这类错误发生的可能性。比如整数x满足边界条件x>=16且x<=37我们可以说x>=16且x<38,这里下界是“入界点”,即包括在取值范围之中;而上界是“出界点”,即不包括在取值范围之中。

另一种考虑不对称边界的方式是,把上界视作某序列中第一个被占用的元素,而把下界视作序列中第一个被释放的元素。

3.7 求值顺序

C语言中只有四个运算符(&&, ||, ?: 和 ,)存在规定的求值顺序。运算符&&和运算符||首先对左侧操作数求值,只在需要时才对右侧操作数求值。运算符?:有三个操作数: 在a?b:c中,操作数a首先被求值,根据a的值首先被求值,根据a的值再求操作数b或c的值。而逗号运算符,首先对左侧操作数求值,然后该值被“丢弃”,再对右侧操作数求值。

3.8 运算符&&, || 和 !

运算符&和运算符&&不同,运算符&两侧的操作数必须被求值。

3.9 整数溢出

C语言中存在两类整数算术运算,有符号运算与无符号运算。在无符号算术运算中,没

篇二:C与指针_C陷阱与缺陷_C专家编程

《C和指针》

《C专家编程》

《C陷阱与缺陷》

《C语言编程要点》

《编程精粹--Microsoft编写优质无错C程序秘诀》

总 结

说明:总结的知识点主要源于上面的4本书,《编程精粹--Microsoft编写优质无错C程序秘诀》这本书未做总结,该书有清晰版的pdf格式的电子版。

--wuliming

--2007-04-25

指针和数组相关概念

*************************************************

字符与字符串的区别

指针与数组1

指针与数组2

指针和数组的相同与不同

用malloc为字符串分配存储空间时的注意事项

作为常数的数组声明(c缺陷与陷阱3.3节.在其它部分有包含该节的知识点,了解or略过) 字符串常量

用字符串常量初始化指针和数组

二维数组下标操作的相关概念

指向一维、二维数组的指针

array_name和&array_name的异同

数组作为函数的参数时,不能通过sizeof运算符得到该数组的大小

用strlen()求字符串的长度

?char **? 和 ?const char **?的兼容性问题

空指针相关的问题

NULL和NUL的区别

未初始化的指针和NULL指针的区别

理解函数的声明

函数参数的传值调用

函数指针

作为函数参数的多维数组

强制类型转换相关概念

可变参数相关问题

malloc()、calloc()、realloc()

在程序退出main()函数之后,还有可能执行一部分代码吗?

总线错误和段错误相关概念

数字和字符串之间转换相关的函数

*************************************************

怎样判断一个字符是数字、字母或其它类别的符号?

怎样将数字转换为字符串?

怎样将字符串转换为数字?

字符串以及内存操作相关函数

************************************************* 字符串拷贝和内存拷贝函数:

strcpy

strncpy

memcpy

memmove

memccpy

bcopy

字符串和内存数据比较函数:

strcmp

strcasecmp

strncasecmp

memcmp

strcoll

bcmp

连接字符串的函数:

strcat

strncat

查找字符/字符串的函数:

strstr

strchr

strrchr

memchr

其它相关的函数:

index

rindex

strlen

strdup

memset

bzero

strspn

strcspn

strpbrk

strtok

数据结构及算法相关函数

qsort()

bsearch()

lsearch(线性搜索)

lfind(线性搜索)

srand(设置随机数种子)

OTHER

************************************************* 什么是标准预定义宏?

断言 assert(表达式) 相关概念

连接运算符“##”和字符串化运算符"#"有什么作用?

注释掉一段代码的方法

Typedef相关概念

= 不同于 ==

词法分析中的“贪心法”

运算符的优先级问题

变量的存储类型及初始化相关概念

左值和右值相关的概念

变量的值和类型相关的概念

怎样删去字符串尾部的空格?

怎样删去字符串头部的空格?

怎样打印字符串的一部分?

结构的自引用

结构的存储分配

边界计算与不对称边界

整数溢出

返回整数的getchar函数

更新顺序文件

随机数的相关概念

用递归和迭代两种办法解fibonacci

字符与字符串的区别(c缺陷与陷阱1.5节

)

#include <stdio.h> int main() { char ch = 'abcdefghijklmnopqrstuvwxyz'; char str[] = "abcdefghijklmnopqrstuvwxyz"; printf("-----%c-----\n%s\n",ch, str ); return 0; } 编译该程序可以通过,但是会产生警告;输出结过为: -----z-----

篇三:C陷阱和缺陷部分笔记


c陷阱与缺陷》由:免费论文网互联网用户整理提供;
链接地址:http://www.csmayi.cn/meiwen/30929.html
转载请保留,谢谢!
相关文章