C陷阱与缺陷(三)

第三章 语义陷阱

3.1 指针与数组

C语言中只有一维数组,而且数组的大小必须字编译期就作为一个常数确定下来。数组中的元素可以是另外一个数组。
任何一个数组下标运算都等同于一个对应的指针运算。
int a[3];
除了a被用作运算符sizeof的参数这一情形,在其它所有情形中数组名a都代表指向数组a中下标为0的元素的指针,sizeof(a)是整个数组a的大小。
*a是数组a中下标为0的元素的引用,*(a+1)是数组中下标为1的元素的引用。

3.2 非数组的指针

在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符的内存区域的地址。
假设我们有两个这样的字符串s和t,我们希望可以借助常用的库函数strcpy和strcat。
char *r;
strcpy(r,s);
strcat(r,t);
这段函数的问题在于没有初始化r。

char r[100];
strcpy(r,s);
strcat(r,t);
但是这段函数又不能确保r足够大。

char *r;
r=malloc(strlen(s)+strlen(t));
strcpy(r,s);
strcat(r,t);
这段函数问题在于
一、malloc函数有可能无法提供请求的内存。
二、给r分配内存后应该释放。
三、未计算空字符的空间。
char *r;
r=malloc(strlen(s)+strlen(t)+1);
if(!r)
exit(1);
strcpy(r,s);
strcat(r,t);
free(r);

3.3 作为参数的数组声明

int strlen(char s[])与int strlen(char *s)一样
char hello[]="hello";
printf("%s\n",hello)与printf("%s\n",&hello[0])一样

3.4 避免举隅法

举隅:即以含义更宽泛的词语来代替含义相对较窄的词语,或者相反。
char *p="xyz",*q;
P的值是一个指向'x','y','z'和‘\0’4个字符组成的数组的起始元素的指针,
q=p;
P和q现在是两个指向内存中同一地址的指针,这个赋值语句并没有同时复制内存中的字符。两者中一个改变内容,另外一个也随之改变。

3.5 空指针并非空字符串

编译器保证由0转换而来的指针并不等于任何有效的指针。
出于文档化的考虑,常数0这个值经常用一个符号来代替。
#define NULL 0
当常数0被转换为指针使用时,这个指针绝对不能解除引用,即不能企图使用该指针所指向的内容中存储的内容。
(char *)0;

3.6 边界计算与不对称边界

C语言中一个拥有n个元素的数组,它的元素的下标范围是从0到n-1为止。
数组中实际不存在的“溢界”元素的地址位于数组所占内存之后,这个地址可以用于进行赋值和比较。当然如果引用该元素,那就是非法了。

3.7 求值顺序

C语言中只有四个运算符(&&、||、?、,)存在规定的求值顺序。
运算符&&和||首先对左侧操作数求值,只有在需要时才对右侧操作数求值。
逗号运算符,首先对左侧操作数求值,然后该值被"丢弃",再对右侧操作数求值。
注:分隔函数参数的逗号并非逗号运算符。
f(x,y)中的求值顺序是未定义的,而在函数g((x,y))中却是确定的。函数g中只有一个参数,先对x求值再丢弃x,接着求y的值。

3.8 运算符&&、||和!

误用移位符代替逻辑符可能产生结果相同的程序,但这只是巧合。
&和&&的求值顺序不一样。

3.9 整数溢出

C语言中存在有符号和无符号运算两类整数算术运算。
在无符号算术运算中,没有所谓的"溢出"一说。
如果是有符号和无符号数进行运算,那么有符号整数会被转换为无符号整数,"溢出"也不可能发生。
只有当爽法都是有符号整数时,可能发生溢出。解决方法可以是把双方都转换为无符号整数,(unsigned)a。

3.10 为函数main提供返回值

如果函数main并为显示声明返回类型,那默认为整型。
严格情况下我们需要在函数结束添加return 0;或者 exit(0);

posted on 2013-06-17 13:40  颓废的悠然  阅读(291)  评论(0编辑  收藏  举报

导航