第一章 穿越时空的迷雾
1. ANSI C中最重要的就是增加了原型。
何谓原型:原型就是函数申明的扩展,这样不仅函数名和返回类型已知,所有的类型参数也是已知的。这就允许编译器在参数的使用和申明之间检查一致性。
2. 赋值
ANSI C标准中规定,要使得赋值形式合法,必须满足下列的条件之一:
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
这样,
Char *cp;
Const char *cpp;
//Cpp = cp; 1
// cp = cpp; 2
Cp是一个指向字符的指针,cpp是一个指向const char的指针,所以,
式1:Cpp 指向cp,cpp拥有cp的全部限定符,这样是可以的;
式2:cp指向cpp,cp不拥有cpp的限定符const,所以这样赋值的话,编译器会发出警告;
3. “安静的改变”究竟有多安静?
安静的改变主要聚焦在算数转换,这是非常隐蔽的。
就说一点,unsigned int(如sizeof的返回值) 和 int之间进行算数比较的时候,int会被升级为unsinged int。毫无疑问,这是一个地雷。
书中给了一个例子:
int array[] = {23,34,12,17,204,99,16};
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0])
main()
{
int d = -1,x;
if ( d < TOTAL_ELEMENTS - 2)
X = array[d + 1];
}
d与TOTAL_ELEMENTS – 2比较的时候,d自动转型为unsigned int,产生一个非常巨大的正整数,致使表达式为假。
要修正这个地雷,只需要改为d < (int)TOTAL_ELEMENTS – 2
第二章 这不是bug,而是语言特性;
本章主要分析了C语言的缺陷,分为如下三类:“多做之过”、“少做之过”、“误做之过”。
1. “多做之过”,就是语言中存在某些不应该存在的特性:如switch语句、相邻字符串常量的自动连接、缺省的全局作用域等等。其中,作者提到:“运行时检查与C语言设计理念相违背。按照C语言的理念,程序员应该知道自己正在干什么,并且保证自己的所作所为是正确的”。Switch语句有个fall thought特性,但事实上,97%的情况下,这个特性就会不用,而是程序员在每一个分支后面加上break语句,防止fall though的发生。
2. “误做之过”,就是语言中有误导性质获知不适当的特性,这主要跟C语言的简介有关,很多运算符都重载了。
3. “少做之过”:语言应该提供但是没有提供的特性。
如:允许返回一个指向局部变量的指针,这回产生难以发现的bug。当包含自动变量的函数或者代码退出的时候,它们占用的内存会被回收,它们的内容肯定会被下一个所调用的函数所覆盖,但是原先自动变量的地址有可能被立即覆盖,也有可能稍后覆盖,造成难以发现的bug。
第三章 C语言的申明
1.”在调用函数的时候,参数从右到左依次压入到堆栈里”,作者对这个说法有异议,认为“参数在传递的时候,应该首先尽可能地放到寄存器中”。Int型变量i和一个只包含int的结构体在参数传递的时候可能不同,前者一般传入到寄存器中,而结构参数则很有可能传递到堆栈中。
2.优先级规则:
A. 申明从它的名字开始读取,然后按照优先级顺序依次读取。
B. 优先级从高到低依次是:
1. 申明中被括号括起来的那部分;
2. 后缀操作符:
括号()表示这是一个函数,而方括号[]表示一个数组;
3. 前缀操作符:星号*表示“指向……的指针“
C. 如果const和(或)volatile关键字的后面紧跟类型说明符(如int,long等)那么它作用于类型说明符。在其它情况下,const和(或)volatile关键字作用于它左边紧邻的星号指针
熟读了上面的规则,下面的就是小case了:
const int *p1; p1是一个指针,指向了一个const整数;
int const *p2; 同上;
int * const p3; p3是一个const指针,指向了一个int整数;
const int * const p4; p4是一个const指针,指向了一个const整数;
int const * const p5; 同上
书中还有更变态的例子:
char * const * (*next)( ); next是一个指针,它指向了一个函数,这个函数没有形参,并且返回了一个指针,这个指针指向了一个指向char类型的const指针。
char *(* c[10])(int **p): c是一个数组,有10个元素,每一个元素都是一个指针,指针指向了一个函数,这个函数的形参是一个指向指针的指针,该函数返回了一个指向字符的指针。
void (*signal (int sig, void (*func)(int)))(int);
signal是一个函数,这个函数的一个形参是整数,另一个形参是一个函数指针,这个函数指针指向了一个接受整数的形参,没有返回值;signal函数返回了一个指针,指向了一个函数,该函数接受的形参是一个整数,没有返回值。
熟悉了书中给出的规则,再复杂的申明都是浮云。