dram.me

《C陷阱与缺陷》读书笔记(第3章)

3.1 指针与数组

C 语言中,数组名和指向该数组的指针是不完全等同的,数组名还可以用在 sizeof 中取得数组的大小,而如果将 sizeof 用在指针上则只会得到指针本身的大小了。

a[2]2[a]是等效的。连这样也可以2[a+1],不过不建议使用。

int calendar[12][31];
sizeof(calendar[12]);

的结果是数组的大小,而不是指针。

二维数组指针的定义:

int (*monthp)[31];
monthp = calendar;

如果维数不同,gcc会发出警告。

3.2 非数组的指针

这一节谈到了 strcpy 和 strcat 这两个函数的问题,当然现在有 strlcpy 和 strlcat 这两个解决方案。作者也提供了利用 malloc 和 strlen 的方案:

char *r;
r = malloc(strlen(s) + strlen(t) + 1);
if (!r) {
    complain();
    exit(1);
}
strcpy(r, s);
strcat(s, t);
...
free(s);

3.3 作为参数的数组声明

参数声明中,数组与指针完全等效,但在定义变量时,两者时不同的。而且在声明外部变量时:

extern char *hello;
extern char hello[];

这两个声明的含义是不同的。(见 4.5)

3.4 避免“举隅法”

复制指针并不同时复制所指向的数据。

3.5 空指针并非空字符串

gcc 中,如果试图对 NULL 进行读操作,会有警告信息。

3.6 边界计算与不对称边界

和书中解释的一样,gcc中给数组分配空间,是按地址递减得顺序来的,所以如下定义:int i, a[10]; a[10]其实就是 i。

将数组上界视作某序列中第一个被占用的元素,而把下界视作序列中第一个被释放的元素。比如一个储存区的初始地址为 buffer,bufptr 为一指针,则初始化只要 bufptr = buffer 就可以了。计算已用空间,则是:bufptr - buffer, *(bufptr++) = c; 这句话也就是向序列添加一个元素,比较自然。

书上讲到--n >= 0n-- > 0要高效,但试了一下,好像不同机器结果不同,所以还是用后者吧,毕竟比较容易理解。

如果定义int a[10],虽然a[10]是非法空间,但是&a[10]还是可以使用的,这用来判断指针是否到达边界比较有用。

被节最后的按列打印数据挺不错的。

3.7 求值顺序

C 语言对(&&, ||, ?:, ,) 四个操作符定义了求值顺序,象&&, !!, ?:都是有可能有一部分不求值的。

而对于赋值运算符的求值顺序则是没有规定的,所以下面的程序是不正确的:

i = 0;
while (i < n)
    y[i] = x[i++];

3.8 运算符 &&, || 和 !

不要与 & | 相混淆。

3.9 整数溢出

如何判断两个有符号整数相加是否溢出:

if ((unsigned)a + (unsigned)b > INT_MAX)
    complain();

if (a > INT_MAX - b)
    complain();

3.10 为函数 main 提供返回值

如果不给 main 提供返回值,可能默认会返回一个垃圾整数,但通常以返回 0 来表示正常返回,所以这样可能会被认为程序执行失败了。