1.这关语言特性何事
分析编程语言缺陷的一种方法就是把所有缺陷归于3类:不该做的做了(多做之过);该做的没做(少做之过);该做但做得不合适(误做之过)。
一个‘L’的NUL和两个‘L’的NULL——牢记下面的话,它有助于回忆指针和ASCII零码的正确术语:一个‘L’的NUL用于结束一个ACSII字符串;两个‘L’的NULL用于表示什么也不指向(空指针)。
2.多做之过
“多做之过”,就是语言中存在某些不应该存在的特性。这些特性包括容易出错的switch语句、相邻字符串常量的自动连接和缺省全局作用域。
2.1 由于存在fall through,switch语句会带来麻烦
switch(表达式){
case 常量表达式:零条或多条语句
default:零条或多条语句
case 常量表达式:零条或多条语句
}
default可以出现在case列表的任何位置,它在其他的case均无法匹配时被选中执行.
如果没有default,而且所有case均不匹配,那整条switch语句便什么都不做也不会报运行错误.
在C语言中,几乎从来不进行运行时错误检查——对进行解除引用操作的指针进行有效性检查大概是唯一的例外.
MS-DOS的运行时检查
无效的指针可能成为程序员的噩梦,人们很容易用一个无效的指针来引用内存。在所有的虚拟内存体系结构里,一旦一个指针进行解除引用操作时所引用的内存地址超过了虚拟内存地址空间,操作系统就会终止这个进程。但MS-DOS并不支持虚拟内存,即使内存访问失败,它也无法立即捕捉到这种情况。
然而MS-DOS中可以动点小脑筋,在程序结束之后检测解除引用空指针的情况。具体方法是在进入程序前,保存内存地址零的值,在程序结束时,系统检查这个地址的值与原先的是否相同,如果不同,基本可以肯定你的程序使用了空指针来访问内存,运行时系统会打印出一条"null pointer assignment(空指针赋值)"信息。
switch(2):{
case 1:
printf("1");
break;
case 2:
printf("2");
break;
default:
printf("default");
}
如果没有break语句,switch将会fall through.
2.2粉笔也成了可用的硬件
ANSI C引入的另一个新特性是相邻的字符串常量被自动合并成一个字符串的约定。这就省掉了过去在书写多行信息时必须在行末加“\”的做法,后续的字符串可以出现于每行的开头。然而这种自动合并意味着字符串数组在初始化时,如果不小心漏掉了一个逗号,编译器将不会发出错误信息,而是悄无声息地把两个字符串合并在一起。
2.3太多的缺省可见性
定义C函数时,在缺省情况下函数的名字是全局可见的。可以在函数的名字前加个冗余的extern关键字,也可以不加,效果是一样的。这个函数对于链接到它所在的目标文件的任何东西都是可见的。如果想限制对这个函数的访问,就必须加个static关键字。
3.误做之过
C语言中属于“误做之过”的特性,就是语言中有误导性质或是不适当的特性。这些特性有些跟C语言的简洁有关(部分与符合的过度复用有关),有些则与操作符的优先级有关。
3.1 骆驼背上的重载
符号 意义
static 在函数内部,表示该变量的值在各个调用间一直保持延续性
在函数这一级,表示该函数只对本文件可见
extern 用于函数定义,表示全局可见(属于冗余)
用于变量,表示它在其他地方定义
void 作为函数的返回类型,表示不返回任何值
在指针声明中,表示通用指针的类型
位于参数列表中,表示没有参数
* 乘法运算符
用于指针,间接引用
在声明中,表示指针
& 位的AND操作符
取地址操作符
= 赋值符
== 比较运算符
<= 小于等于运算符
<<= 左移复合赋值运算符
< 小于运算符
#include指令的左定界符
() 在函数定义中,包围形式参数表
调用一个函数
改变表达式的运算次序
将值转换为其它类型(强制类型转换)
定义带参数的宏
包围sizeof操作符的操作数(如果它是类型名)
3.2 有些运算符的优先级是错误的
运算符的优先级 、结合性。
3.3 早期gets()中的Bug导致了interbet蠕虫
gets函数正式的任务是从流中读入一个字符串,但是gets()函数并不检查缓存区的空间,事实上它也无法检查缓冲区的空间,当字符数量超过了缓冲区的空间,gets()将会继续写入堆栈中,这就覆盖了堆栈原先的内容。
fgets()函数对读入的字符数设置了一个限制,这样就不会超出缓冲区范围。但gets()函数并没从ANSI C标准中拿到。
4.少做之过
4.1 用户名中若有字母f,便不能收到邮件
if(argv[argc-1][0]=='-'||(argv[argc-2][1]=='f'))
readmail(argc,argv);
else
readmail(argc,argv);
修复"f"
if(argv[argc-1][0]=='-'||(argv[argc-2][0]=='-')||(argv[argc-2][0]=='-'))
readmail(argc,argv);
4.2 空格——最后的领域
“\”字符可用于对一些字符进行“转义”;编译器的错误处理;
4.3 C++的另一种注释形式
C++并未对C的绝大多数缺陷予以修正,但它还是采用了一种方法来避免这种容易发生意外的注释形式。
4.4 编译器日期被破坏
症状就是表示日期的字符串被破坏。解决这个问题有几种方案:1)返回一个指向字符串常量的指针;2)使用全局声明的人数组;3)使用静态数组;4)显式分配一些内存,保存返回的值;5)也许最好的解决方案就是要求调用者分配内存来保存函数的返回值。
4.5 lint程序绝不应该被分离出来
lint程序作为一个独立的工具通常意味着把lint程序束之高阁。
5.轻松一下——有些特性确实就是Bug
错误符号,替代符号的不合理使用,书写不规范等编译器可正运行,却造成很严重的后果的问题。