C陷阱与缺陷整理一

1.词法分析中的“贪心法”

C语言的某些符号,例如/、*和=,只有一个字符长,称为单字符符号。而C语言中的其他符号,例如/*和==,以及标识符等都包含了多个字符,称为多字符符号。当C编译器读入一个字符‘/‘后又跟了一个字符‘*‘,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号来对待。C语言对这个问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。

但是需要注意的是,除了字符串与字符常量,符号的中间不能嵌有空白(空格符、制表符和换行符)。注意下面两个例子:

a---b等价于a -- - b

a---b不等价于a - -- b

y = x/*p 不能实现预期的目的,可以修改为y = x/(*p)或y = x/ *p

2.用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为0的字符‘\0‘初始化。

3.‘yes‘这种写法的字符,根据编译器的不同会得到不同的处理:大多数编译器会理解为一个整数值,由‘y‘、‘e‘、‘s‘所代表的整数值按照特定编译器实现中定义的方式组合得到;Borland C++v5.5和LCC v3.6采取的做法是忽略多余的字符,最后的整数值即第一个字符的整数值;在Visual C++6.0和GCC v2.95中采取的做法是,依次用后一个字符覆盖前一个字符,最后得到的整数值即最后一个字符的整数值。

4.任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。声明符从表面上看与表达式有些类似,对它求值应该返回一个声明中给定的类型的结果。

float ff();

这个声明的含义:表达式ff()求值的结果是一个浮点数,也就是说,ff是一个返回值为浮点类型的函数。

float *pf;

这个声明的含义:*pf是一个浮点数,也就是说pf是指向一个浮点数的指针。

float *g();

这个声明的含义:*g()是一个浮点数,g()等价于上面的pf一样理解,g是一个函数,函数的值就是函数的返回值,所以g()返回的值应该是上例中的pf一致的类型,也就是一个浮点型的指针。

float (*h)();

这个声明的含义:(*h)()是一个浮点数,然而(*h)()是一个函数,所以h就是一个函数指针,h指向的这个函数返回一个浮点型的数。

5.强制类型转换符的构建

从声明变量出发,去掉声明中的变量名和声明末尾的分号,然后将剩余的部分用一个括号括起来即可。

float (*h)();-->(float (*)())

后面的强制类型转换符的含义:返回值为浮点类型的函数的指针。

现在来分析(*(void(*)())0)()的含义

很明显在常数0之前是一个强制类型转换符:(void(*)()),这个强制类型转换符的含义是:返回值类型为void类型的函数指针。所以这整个表达式的含义就是,将0转换为一个返回值类型为void类型的函数指针,然后通过*运算符取得这个0所指向的函数,然后调用它。使用typedef来实现这个功能更为清晰:

typedef void (*funcptr)();

(*(funcptr)0)();

复杂一点的一个例子:

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

含义:首先把signal(int, void(*)(int))看做一个整体,这样的话这个整体应该代表的是一个函数指针,外层通过*运算符调用这个指针指向的函数。然而signal(int, void(*)(int))本身就比较好理解了,signal是一个函数,接收的参数第一个为int类型的值,另一个参数为一个函数指针。然而既然这个整体表示一个函数指针,前面也提到过函数本身代表一个值的话其实也就是说该函数的返回值是一个什么样的值,所以signal函数返回一个函数指针,该指针类型为void (*)(int)。那么void
(*signal(int, void(*)(int)))(int)声明的signal如何调用呢?其实用法很简单:

signal(int m, void(*ptr)(int);就可以实现调用,上面这个声明方式的用处在于使得这个函数的返回值变得更加有意义,调用者可以定义一个void (*)(int)类型的指针来接收signal函数的返回值。

这个功能同样可以由typedef来实现,同样会变得更为清晰:

typedef void (*HANDLER)(int);

HANDLER signal(int, HANDLER);

6.当需要为一个变量添加符号的时候可以采用下面的这种写法:

int a = 3;

a = -a;

7.C语言的初始化例表中允许多书写一个逗号,这有什么作用?

分析:这是为了词法分析器的方便,这样书写之后,每一个用来初始化的变量都是以逗号结束,编译器处理起来更为方便。

8.定义一个数组a之后,a除了作为运算符sizeof的参数之外,其他的所有情形中a都代表的是a中下标为0的元素的指针,这个在多维数组的情况下仍然适用,因为在C语言中多维数组是通过一维数组模拟出来,只不过一维数组的每个元素都可以是其他的各种类型的变量,当然也可以是另一个数组。所以如下的例子:

int a[3];

*a = 2;

*(a+1) = 3;

分别操作的是a数组中的第0个元素和第1个元素,但是这样操作起来往往比较麻烦,所以现在常用的写法a[0]、a[1]、a[2]是上面的写法的简写形式。*(a+1)等价于*(1+a),而*(a+1)的简写形式为a[1],而*(1+a)的简写形式为1[a],所以a[1]等价于1[a]写法,很多编译器都不会报错,但是不推荐第二种写法!!

9.下面的例子输出会是多少?

int a[2][3];

printf("%d, %d", sizeof(a+1), sizeof(a[1]));

分析:先不说这个输出结果为多少,首先来一步步分析需要输出的内容,sizeof(a+1)编译器在碰到这句话的时候,会把a当做一个指针来处理,因为数组+1没有意义,只有数组的指针+1才有意义,所以这里测试出来的仍然只是一个指针的大小,那么如果是32位的PC的话输出就会是4。再来分析sizeof(a[1]),这个测的是一维数组a的第2个单元的大小,然而第二个单元里面存放的是什么呢?第二个单元里面存放的是一个3个单元的数组,所以这个表达式测试出来的值为12。

答案:4, 12

10.malloc()函数分配内存失败的时候会返回一个空指针,在使用malloc的时候一定要判断返回的值是否为空指针,否则会产生意想不到的后果。

时间: 2024-10-25 21:09:24

C陷阱与缺陷整理一的相关文章

C陷阱和缺陷整理四

1.assert宏的定义 #define assert(e) \ ((void)((e) || _assert_error(__FILE__, __LINE__))) 库里面对这个宏做了这样的定义,当宏参数(或表达式)e为真的时候由||运算符的运算规则会执行_assert_error(__FILE__, __LINE__)从而打印一条报警信息.所以整个表达式的最终会变为(void)0或者(void)1这种形式,这种形式确实有点奇怪? 系统这样定义的目的是当一个值被转换为void类型之后,没有一个

C陷阱与缺陷整理二

1.在C语言中,我们没有办法将一个数组作为函数参数传递,如果我们使用数组名作为参数,这个时候数组名立刻会被转换为指向该数组的第一个元素的指针. 关于这一点的理解可以向前深入一步,比如定义的数组为int a[3],那么a作为参数传递之后会变为int *类型:如果定义的数组为int a[3][4],那么a作为参数传递之后被变为int (*)[4]:如果定义的数组为int a[3][4][5],那么a作为参数传递之后会变为int (*)[4][5]:后续的以此类推.为什么可以这样呢?因为C语言中的多维

C陷阱与缺陷整理三

1.大多数C语言的实现都通过函数main的返回值来告诉操作系统该函数的执行是成功还是失败.典型的处理方案是,返回值为0代表程序执行成功,返回值非0则表示程序执行失败.如果一个程序的main函数并不返回任何值,那么有可能看上去执行失败.所以建议我们的C程序的main函数应该如下编写: int main() { return 0; } 当然如果main函数需要接受参数的话将参数声明加上更加完美. 2.一个C程序可能是由多个分别编译的部分组成,这些不同的部分通过一个通常叫做连接器的程序合并为一个整体.

读书笔记--C陷阱与缺陷(七)

第七章 1.null指针并不指向任何对象,所以只用于赋值和比较运算,其他使用目的都是非法的. 误用null指针的后果是未定义的,根据编译器各异. 有的编译器对内存位置0只读,有的可读写. 书中给出了一种判断编译器如何处理内存0的代码: 1 #include <stdio.h> 2 int main() 3 { 4 5 char *p; 6 p=NULL; 7 printf("location 0 contains: %d\n", *p); 8 9 return 0; 10

阅读《C陷阱与缺陷》的知识增量

看完<C陷阱与缺陷>,忍不住要重新翻一下,记录一下与自己的惯性思维不符合的地方.记录的是知识的增量,是这几天的流量,而不是存量. 这本书是在ASCI C/C89订制之前写的,有些地方有疏漏. 第一章 词法陷阱 1.3 C语言中解析符号时使用贪心策略,如x+++++y将被解析为x++ ++ +y,并编译出错. 1.5 单引号引起的一个字符代表一个对应的整数,对于采用ASCII字符集的编译器而言,'a'与0141.97含义一致. 练习1.1 嵌套注释(如/*/**/*/)只在某些C编译器中允许,如

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

<C陷阱与缺陷>读书笔记 1.编译器中的词法分析器负责将程序分解为一个个符号.C语言中,符号之间的空白 (包括Space ,Tab , Enter) 都将被忽略,但一个符号的中间不能有空白,否则可能被解释成为另一个或几个符号. 2.编译器将程序分解成符号的方法是从左到右逐个字符读入,如果该字符可能会组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分:如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已经不再可能组成一个有意义的

读书笔记--C陷阱与缺陷

要参与C语言项目,于是作者只好重拾C语言(之前都是C++,还是C++方便). 看到大家都推荐看看  C陷阱与缺陷(C traps and pitfalls),于是好奇的开始了这本书的读书之旅. 决定将书中重要的知识点和易错点记录下来方便自己复习和他人学习~~不多说了,下面开始. 第一章:词法陷阱 在C语言中,符号(程序文字)之间的空白(包括空格符.制表符.换行符)将被忽略.书中举了一例: 1 if (x > big) big = x; 2 可以写成: 3 if 4 ( 5 x 6 > 7 bi

《C陷阱与缺陷》学习笔记(一)

前言和导读 "得心应手的工具在初学时的困难程度往往超过那些容易上手的工具."比较认同这句话.我至今觉得自己其实还是个刚入了门的初学者. 第一章 "词法"陷阱 由于之前学过编译原理,对编译器词法分析(主要是符号识别过程)比较了解,理解起来不困难. 在讲到"="和"=="."|"和"||"."&"和"&&"时,联想起以前见过一些

C陷阱与缺陷 1

1,符号之间的空白被忽略 符号中间不能嵌入空白 2,词法分析中的贪心法 a---b 和 a-- -b相同 和 a- --b不同 1 a=b/*p //根据贪心法 /*被解释成 注释符,便不再往下读,直到匹配 */ 1 a=b/ *p//*号优先级大于 / 故为b除以 p所指向的值 根据编程规范,方便阅读,减少出错的可能 1 a=b/(*p) 3,理解函数的声明 4,运算符的优先级 5,分号的使用 1 1 2 if() a; 1 2 if(); a; 2 1 2 3 return; a; b; 1