C语言自身引出的问题
1,第一类错误:语言做了它不应该做的事
2,第二类错误:没有做它应该做的事
3,第三类错误:完全把事情的意思搞错了
手边的启发:
有一个“L”的NUL代表ASCII码串的结束,大多数字符串处理函数都不需要用户考虑NUL结束符的储存,语句本应写成 malloc(strlen(str)+ 1)
有两个“LL”的NULL代表空指针
第一类错误:
这里主要包括开关语句,邻近串文字自动拼接和缺省全局范围等容易引起的错误特性。
1,开关语句
case和default的次序是任意的,不过习惯上总是把default放在最后。
如果所有的匹配都不成功而又没有default,开关语句就什么也不做,C语言几乎没有运行时检查的,它唯一的检查是对无效指针的间接引用。运行时检查违背了c语言的设计原则:程序员总是对的,他们清楚所干的一切。
无效指针危害很大,用无效指针去操作内存很容易引起错误,在虚拟内存机制下,当进程用指针来引用不属于它自身地址空间的数据时,计算机会马上终止这一进程。
开关语句本身有一些问题,其中之一是各种情况之间过于松散。你可以在switch后的花括号后面定义一个局部变量,却不能给它赋值,因为开关语句是从case后的语句执行。
手边的启发:
需要临时储存单元,请定义一个块。C语言常常将几个语句组成一个块。
开关语句的另一个问题是,其语句体中的所有语句都可以有标号,可以通过goto语句语句转到这样的语句去执行
开关语句中,break是跳出最近的循环体或开关语句。就是它导致当时美国整个网络瘫痪。
2,邻近文字串的连接
自动连接意味着如果相邻文字串之间没有逗号将不会引起编译错误,没有逗号将被理解成这两个串连在一起。
3,函数的缺省可见性
函数名缺省是全局可见的(可以被其他文件中的函数调用)。在实际编程中,几乎所有人不写储存类型说明符,也就是使用了缺省的全局可见性。大多数情况下都限制函数的使用范围。
第二类错误:
缺乏对实参的标准处理过程及未能把lint包含在编译程序中。
1,未能将参数中的开关和文件名区分开来。
2,空格的作用,如果在行末尾敲入了反斜杠后不是马上敲回车,而是敲一个或多个空格在回车就会出问题。
最大匹配约定,按照从左到右尽可能多的字符的方式进行解释。
3,返回指针的函数
变量是在函数被调用时分配的,并在函数结束时被销毁。在堆上分配的变量(malloc、new),它的生命周期由我们决定。
函数中buffer是一个局部自动数组,而自动变量在程序返回调用者时会被删除。这里,你返回的是指向这种变量的指针,指针所指的内容在函数结束后已不可知了。在C语言中,自动变量存放在栈中,一旦包含自动变量的函数或块结束运行,储存这些变量的空间将被释放,可以重新其他函数调用。数据肯定会被其他数据覆盖。
对此问题的解决方法:
1,使用全局数组
char *func()
{
my_global_array[i] =
return my_global_array;
}
这样做的缺点在于任何函数都可以修改这样的全局数组,而且每次调用此函数时,都把数组以前的内容覆盖了。
2,使用静态数组
char *func()
{
static char buffer[20];
return buffer;
}
使用这种方法可以防止别的函数访问此串,函数只有得到指向串的指针时才能改动它。不过后一次的调用仍然会覆盖前一次内容。和全局数组一样,静态数组在不用时,也占用内存,这对内存资源来说是一种浪费。
3,显式分配内存来储存返回值
char *func()
{
char *s = malloc(120);
return s;
}
这种方法具有静态数组的优点,同时,又在每一次调用都分配新的储存空间,下一次调用不会覆盖前一次的调用结果。其不利之处在于,程序员自己参与内存管理,程序月复杂,管理也越复杂。释放了还在使用的内存或者不释放不再使用的内存。
4,让调用者分配一块内存来存放返回值。为了安全调用者还应提供这块内存空间的大小。
void func (char * result, int size)
{
strncpy(result, "hello world", size);
}
buffer = malloc(size);
func(buffer, size);
free (buffer);
第三类错误:
主要包括由于追求简洁而引起的问题(一部分重载引起的)和 由运算符优先级引起的问题。