"C陷阱和缺陷"中的几个知识点
2016-09-15 23:44
375 查看
1.词法分析中的“贪心法”
C语言中的某些符号,例如/、*、和=,只有一个字符长,称为单字符符号。而C语言中的其他一些符号,例如/*和==,包括了多个字符,称为多字符符号。当C编译器读入一个字符'/'后又跟了一个字符'*',那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。C语言解决这个问题的方法可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是:从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。例如:a---b与表达式a
-- - b的含义相同,而与a - -- b 的含义不同。 y = x/*p中的."/*"表示注释的开始,而不是除以*p。
2.理解函数声明
例子:解释 (*(void(*)())0)(); 这个语句的含义。
虽然上面的这个语句比较复杂,但是其实构造这类表达式其实只有一条简单的规则:按照使用的方式来声明。任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。例如,声明一个int型变量a,int a;一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。例如:float (*h)();表示h是一个指向返回值为浮点类型的指针,因此(float (*)())表示一个“指向返回值为浮点类型的函数的指针的”类型转换符。此时,(*(void(*)())0)();
也就不难理解了,(void(*)())0表示将0强制转化为(void(*)())类型,然后是'*'的解引用,然后是函数调用。可以使用typedef使得上面的例子更简洁,typedef void (*funcptr)(); 那么 (*(void(*)())0)();可表示为(*(funcptr)0)();
3.变量的定义与声明要保证类型一致
如果在一个源文件中定义了char filename[] = "/etc/passwd";而在另外一个文件中声明了extern char* filename;尽管在一个语句中引用filename的值将得到指向该数组起始元素的指针,但是filename的类型是“字符数组”,而不是“字符指针”。在第二个声明中,filename被确定为一个指针。这两个队filename声明使用储存空间的方式是不同的,它们无法以一种合乎情理的方式共存。字符数组filename的内存布局如图1所示,字符指针filename的内存布局如图2所示。
图1 字符数组filename的内存布局
4000
示意图
图2 字符指针filename的内存布局示意图
4.返回整数的getchar函数
考虑下面的函数:
C语言中的某些符号,例如/、*、和=,只有一个字符长,称为单字符符号。而C语言中的其他一些符号,例如/*和==,包括了多个字符,称为多字符符号。当C编译器读入一个字符'/'后又跟了一个字符'*',那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。C语言解决这个问题的方法可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是:从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。例如:a---b与表达式a
-- - b的含义相同,而与a - -- b 的含义不同。 y = x/*p中的."/*"表示注释的开始,而不是除以*p。
2.理解函数声明
例子:解释 (*(void(*)())0)(); 这个语句的含义。
虽然上面的这个语句比较复杂,但是其实构造这类表达式其实只有一条简单的规则:按照使用的方式来声明。任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。例如,声明一个int型变量a,int a;一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。例如:float (*h)();表示h是一个指向返回值为浮点类型的指针,因此(float (*)())表示一个“指向返回值为浮点类型的函数的指针的”类型转换符。此时,(*(void(*)())0)();
也就不难理解了,(void(*)())0表示将0强制转化为(void(*)())类型,然后是'*'的解引用,然后是函数调用。可以使用typedef使得上面的例子更简洁,typedef void (*funcptr)(); 那么 (*(void(*)())0)();可表示为(*(funcptr)0)();
3.变量的定义与声明要保证类型一致
如果在一个源文件中定义了char filename[] = "/etc/passwd";而在另外一个文件中声明了extern char* filename;尽管在一个语句中引用filename的值将得到指向该数组起始元素的指针,但是filename的类型是“字符数组”,而不是“字符指针”。在第二个声明中,filename被确定为一个指针。这两个队filename声明使用储存空间的方式是不同的,它们无法以一种合乎情理的方式共存。字符数组filename的内存布局如图1所示,字符指针filename的内存布局如图2所示。
图1 字符数组filename的内存布局
4000
示意图
图2 字符指针filename的内存布局示意图
4.返回整数的getchar函数
考虑下面的函数:
相关文章推荐
- "C陷阱和缺陷"中的几个知识点
- T-SQL 2 Tips: 1.计算任意两日期之间的"周一"到"周日"分别各有几个! 2.根据出生日期计算精确年龄!
- "足球论之数据库知识点罗列"
- JavaSE易错知识点 String 的"equals"和"=="
- 世界上最短的一本书:关于"缺陷"和"满足"的寓言
- "inode、block的概念"和"几个文件系统概念:geometry、sector 、block"
- 介绍几个C++程序中关于"时间"的函数
- "一致性相等"的陷阱[转]
- 转载几个关于"Wince下定制开机自启动程序"的文章
- 浅析Java执行外部命令的几个要点(4)——支持shell的"|","`","*","?"等特殊符号
- [HotSpot VM] JVM调优的"标准参数"的各种陷阱
- Excel表格中的"="功能强大,这几个不为人知的技巧你会吗?
- "陷阱"技术探秘----动态汉化Windows技术的分析
- C语言中值得深入知识点----数组做函数参数、数组名a与&a区别、数组名a的"数据类型"
- "长按实现视图抖动和删除"功能知识点整理
- 关于"深入浅出MFC"的几个问题
- "陷阱"技术探秘
- C陷阱与缺陷(二)语法"陷阱"
- [HotSpot VM] JVM调优的"标准参数"的各种陷阱
- Golang 切片与函数参数 "陷阱"