《C专家编程》笔记(一)——C语言的历史、K&R C和ANSI C
1. C语言的许多特性是为了方便编译器设计者而建立的。于是C语言的语言特性有:数组下标从0而非1开始;C语言的基本数据类型直接与底层硬件相对应;auto关键字只对创建符号表入口的编译器设计者有意义;表达式中的数组名可以看作是指针;float被自动扩展为double(ANSI C中不再如此);不允许嵌套函数(简化了编译器);register关键字,为编译器设计者提供线索,却把包袱丢给了程序员。
2. C编译器不曾实现的一些功能必须通过其他途径实现(编译器实现的功能,如加减乘除、指针、取地址)。在C语言中,它们在运行时进行处理,既可以出现在应用程序代码中,也可以出现在运行时函数库(runtime library)中。
3. C语言的标准:ANSI C 1990标准, ANSI C 1999标准,直至现在ANSI C 2011年的委员会草案(Committee Draft)。
4. C语言标准的几个术语:
不可移植的代码。由编译器定义的,例如当整型数向右移位时,要不要扩展符号位。未确定的,例如参数求值的顺序。
坏代码。未定义的,例如当一个有符号整数溢出的时候该采取什么行动。约束条件,例如%操作符的整型数必须属于整型。
可移植的代码。严格遵循标准:只使用已确定的特性;不突破任何由编译器实现的限制。不产生任何依赖由编译器定义的未确定的或者未定义的特性的输出。
5. 和K&R C相比,ANSI C最重要的新特性就是“原型”,就是在对函数作前向声明的时候,增加形参类型,而非仅仅函数名和返回类型。这样,编译器就能够在编译时对函数调用中的实参和函数声明中的形参之间进行一致性检查。在K&R C中,这种检查被推迟到链接时,或者干脆不作检查。
1 /* K&R C */ 2 /* 声明 */ 3 char * strcpy(); 4 /* 定义 */ 5 char * strcpy(dst, src) 6 char *dst, *src; 7 { ... } 8 9 /* ANSI C */ 10 /* 声明 */ 11 char * strcpy(char* dst, const char* src); 12 /* 另一种形式的声明 */ 13 char * strcpy(char*, const char *); 14 /* 定义 */ 15 char * strcpy(char *dst, const char *src) 16 { ... }
6. 关键字const并不能把变量变成常量!在一个符号前加上const限定符只是表示这个符号不能被赋值。也就是它的值对于这个符号来说是只读的,但它并不能防止通过程序的内部的方法来修改这个值。const最有用的地方就是用它来限定函数的形参。(这也就是为什么不能使用const关键字修饰的整型数为数组初始化。)
7.
1 main() 2 { 3 if (-1 < (unsigned char)1) { 4 printf("-1 is less than (unsigned char)1"); 5 } else { 6 printf("-1 NOT less than (unsigned char)1"); 7 } 8 }
结果是-1 小于 (unsigned char)1。
1 main() 2 { 3 if (-1 < (unsigned int)1) { 4 printf("-1 is less than (unsigned int)1"); 5 } else { 6 printf("-1 NOT less than (unsigned int)1"); 7 } 8 }
结果是-1 不小于 (unsigned int)1。
这是因为ANSI C采用值保留原则。整型升级的时候,如果int可以完整表示源类型的所有值,那么该源类型的值就转换为int,否则转换为unsigned int。
8. 考虑这段程序:
1 #define TOTAL_ELEMENTS(array) (sizeof(array)/sizeof((array)[0])) 2 3 main() 4 { 5 int arr[] = {1,2,3,4,5,6,7,8,9}; 6 7 int d = -1, x; 8 9 if (d <= TOTAL_ELEMENTS(arr) -2) { 10 x = arr[d+1]; 11 } 12 13 }
他有什么bug吗?是的,if表达式的值不是真,x没有被赋值。为什么呢?因为TOTAL_ELEMENTS(array)宏中,sizeof的返回类型是无符号数。所以在比较中,d被提升为unsigned int,变成一个巨大的数。
9. 考虑这段程序:
1 foo(const char **p) { } 2 3 main(int argc, char** argv) 4 { 5 foo(argv); 6 }
使用gcc编译,则提示警告: [Warning] passing arg 1 of `foo' from incompatible pointer type 。
1 foo(const char *p) { } 2 3 main(int argc, char** argv) 4 { 5 foo(*argv); 6 }
而这段程序不提示警告。
我们知道,函数调用过程中,实参传递给形参,实质就是实参的值赋予给形参。
在ANSI C标准中,提出以下约束条件:
要使上述的赋值形式合法,必须满足下列条件之一:
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
所以后一段程序中,左边指针指向const char,右边指针指向char。根据限定符,左边相容右边,所以符合约束条件。
前一段程序中,左边指针指向const char*,右边指针指向*char。左边和右边是两种不同的指针类型。
在ANSI 2011年标准草案6.2.5节的29段(43页),我们看到,const float*和float* 并不相容。float * const 和float*是相容的。
所以我们可以知道,char * const和char *是相容的。于是编写如下程序:
1 foo(char * const * p) { } 2 3 main(int argc, char** argv) 4 { 5 foo(argv); 6 }
通过编译,无警告。