第六章 预处理器
6.1 不能忽视宏定义中的空格
#define f (x) ((x) – 1) ≠ #define f(x)( (x) – 1)
6.2 宏并不是函数
宏定义中的所有括号,它们的作用是预防引起与优先级有关的问题。
最好把每个参数都用括号括起来
#define abs(x)x>0?x:-x
abs(a-b) 会被展开为a-b>0?a-b:-a-b
-a-b相当于(-a)-b,而不是期望的-(a-b)
整个结果表达式也应该用括号括起来,以防止当宏用于一个更大一些的表达式中可能出现的问题。
abs(a)+1展开后的结果为: a>0?a:-a+1
期望的是-a,而不是-a+1。
即使宏定义中的各个参数与整个表达式都被括号括起来,也仍然还可能有其他问题。比如,一个操作数如果在两处被用到,就会被求值两次。
使用宏的另一个危险是,宏展开可能产生非常庞大的表达式,占用的空间远远超过了编程者所期望的空间。
6.3 宏并不是语句
6.4 宏并不是类型定义
#define T1struct foo *
Typedef structfoo *T2;
试图声明多个变量:
T1 a, b;
T2 a, b;
第一个声明被扩展为:
struct foo * a,b;
第七章 可移植性缺陷
7.1 应对C语言标准变更
7.2 标识符名称的限制
ANSI C标准所能保证的知识,C实现必须能够区别出前6个字符不同的外部名称。而且,整个定义中并没有区分大写字母与其对应的小写字母。
两个函数的名称分别为print_felds与print_float,这样的命名方式就不恰当;同理,使用State与STATE这样的命名方式也不理智。
7.3 整数的大小
C语言中为编程者提供了3种不同长度的整数:short、int和long。
C语言的定义中对各种不同类型整数的相对长度作了一些规定:
3种类型的整数其长度是非递减的。对于一个特定的C语言实现来说,并不需要实际支持3种不同长度的整数,但可能不会让short型整数大于int型整数,而int型整数大于long型整数。
一个普通(int类型)整数足够大以容纳任何数组下标。
字符长度由硬件特性决定。
7.4 字符是有符号整数还是无符号整数
如果编程者关注一个最高位是1的字符其数值究竟是正还是负,可以将这个字符声明为无符号字符(unsigned char)。这样,无论是编译器,在将该字符转换为整数时都只需要将多余的位填充为0即可。而如果声明为一般的字符变量,那么在某些编译器上可能会作为有符号数处理,在另一些编译器上又会作为无符号数处理。
与此相关的一个常见错误是:如果c是一个字符变量,使用(unsigned)c就可得到与c等价的无符号整数。这是会失败的,因为在将字符c转换为无符号整数时,c将首先被转换为int型整数,而此时可能得到非预期的结果。
7.5 移位运算符
使用移位运算符的两个问题:
1. 在向右移位时,空出的位是由0填充,还是由符号位的副本填充?
2. 移位计数(即移位操作的位数)允许的取值范围是多少?
第一个问题的答案是:如果被移位的对象是无符号数,那么空出的位将被0填充。如果被移出的对象是有符号数,那么C语言实现既可以用0填充空出的位,也可以用符号位的副本填充空出的位。
第二个问题的答案是:如果被移位的对象长度是n位,那么移位计数必须大于或等于0,而严格小于n。
需要注意的是,即使C实现将符号位复制到空出的位中,有符号整数的向右移位运算也并不等同于除以2的某次幂。(-1)>>1,整个操作的结果一般不可能为0,但是(-1)/2在大多数C实现上求职结果都是0。
7.6 内存位置0
null指针并不指向任何对象。因此,除非是用于复制或比较运算,出于其他任何目的使用null指针都是非法的。
在所有的C程序中,误用null指针的效果都是未定义的。然而,这样的程序有可能在某个C语言实现上“似乎”能够工作,只有当该程序转移到另一台机器上运行时才会暴露出问题来。
7.7 除法运算时发生的截断
假定a除以b,商为q,余数为r:
q = a/b;
r = a%b;
a、b、q、r之间维持怎样的关系?
1. 最重要的一点,q*b+r == a,因为这是定义余数的关系。
2. 如果改变a的正负号,我们希望这会改变q的符号,但这不会改变q的绝对值。
3. 当b>0时,希望保证r>=0且r<b。
这三条性质并不可能同时成立。因此,C语言与其他语言在实现整数除法截断运算时,必须放弃上述三条原则中的至少一条。大多数选择放弃第三条。而要求余数与被除数的正负号相同。
然而,C语言的定义只保证了性质1,以及当a>=0且b>0时,保证|r|<|b|以及r>=0.
7.8 随机数的大小
7.9 大小写转换
7.10 首先释放,然后重新分配
7.11 可移植性问题的一个例子