C陷阱和缺陷--第七章 “可移植性缺陷”

C语言在许多不同的系统平台上都有实现;因此我们应该能够预料到,机器不同则其上的C语言实现也有细微差别;

7.1 应对C语言标准变更

作者写这本书的时候,还是1988年;对于2024年的我们,应该不需要考虑C语言标准变更的问题;对于标准版本变更而言,我们必须在当前需要产生的工作量和未来的收益之间做出选择;

7.2 标识符名称的限制

为了保证程序的可移植性,谨慎的选择外部标识符的名称是重要的;

// 错误示例,使用 含义相同的变量名称
int print_fields;
int print_float;	
// 错误示例,变量名称仅仅知识大小写存在区别
char state;
char STATE;

定义的函数名称和库函数,仅仅只是大小写不一致;
ps: 但是现在的编译器都已经区分大小写了,可能作者那个时候还不支持;

// 错误示例, 如果编译器不区分大小写,则变为递归调用了
char *Malloc(unsigned n)
{
	char *p,*malloc(unsigned);
	p = malloc(n);
	if (p == NULL)
		panic("out of memory);
	return p;
}

7.3 整数的大小

C语言为编程者提供了 3种不同长度的整数:short, int, long 型;但是在不同的硬件设备上(8位,13位,32位,64位),同一个类型对应的数据长度,可能是不一样的;
C语言的定义中,对各种不同类型整数的相对长度作了一些规定
1: 3种类型的整数其长度是非递减的;也就是说,short 型整数容纳的值肯定能够被 int型整数容纳,int型整数容纳的值也肯定能够被 long型整数容纳;
2:一个普通int型整数足够大,可以容纳任何数组的下标;
3:字符长度由硬件特性决定;
ANSI标准要求long型整数的长度至少应该是32位,而short和int型整数的长度至少应该是16位;
为了适配不同设备上的数据长度,我们最好使用的使用重新定义一个参数类型

// 当我们在不同的设备上的时候,只需要修改 Int16 的定义就行
#define short Int16
#define unsigned short UInt16

// 改动上面的#define 之后,这里的长度就会自动适配了
Int16 a;
UInt16 b;

7.4 字符是有符号整数还是无符号整数

有符号类型的最高bit位为1,当我们把 一个字符值转换为较大的整数时,可能会发现数据的符号位发生非预期的变化;

void func5()
{
    char a=10;
    char b=20;
    char c = 10*20;						// 溢出了

    printf("c= %d \n",c );      // c= -56 
}

当数据类型发生变化时候,也应该注意符号位是否发生变化;

void func6()
{
    char a=-10;
    int  b=0;
	
    memcpy(&b,&a,1);								 // b的符号位没有发生变化
    printf("a=%d b=%d \n",a, b );      // a=-10 b=246
}

7.5 移位运算符

1:向右移位时,空出的位是由 0 填充,还是由符号位的副本填充呢?
如果操作对象是无符号数,空出的位被0填充;如果操作对象是有符号数,那么不同的编译器可能填充的结果是不一致的;尽量不要对有符号数,进行移位运算;
2:移位计数(即移位操作的位数)允许的取值范围是什么;
根据不对称边界计算,

int a=9;				// 认为 int 型长度是32位
if ((n>=0) && (n<31))
	a = a>>n;

以除法运算来代替移位运算,将可能导致程序运行速度大大减慢;

unsigned int low;
unsigned int high;
// something
mid = (low+high) >> 1;		//执行速度更快 
// 等价于
mid = (low+high) / 2;

7.6 内存位置0

null指针并不指向任何对象。因此,除非是用于赋值或比较运算,出于其它任何目的使用null指针都是非法的;
在所有的C程序中,误用 null 指针的效果都是未定义的,可能会造成程序崩溃问题;

#include <stdio.h>
main()
{
	char *p;
	p = NULL;
	// 在禁止读取内存地址0的机器上,这个程序将会执行失败。在可以读取的机器上,会打印内存位置0中存储的字符内容
	printf("location 0 contains %d \n", *p);
}

7.7 除法运算时发生的截断

// TODO 没看明白这里想说的重点是什么

7.8 随机数的大小

在不同的系统上,rand函数中随机数的取值范围是不一致的;
在16位系统上,rand 函数将返回一个介于 0 ~ ( 2 15 2^{15} 215-1) 之间的整数
在32位系统上,rand 函数将返回一个介于 0 ~ ( 2 31 2^{31} 231-1) 之间的整数
为了处理这个问题,ANSI C标准中定义了一个常数 RAND_MAX, 它的值等于岁数的最大取值;

7.9 大小写转换

库函数 toupper和tolower 也有与随机数类似的历史;
不同的库函数,可能具体的实现方式不一致;使用者需要特别注意

// 函数会判断传进来的参数是否是有效的
int toupper(int c)
{
	if (c >= 'a' && c <= 'z')
		return c+'A'-'a';
	return c;
}
// 宏定义需要用户自己判断参数,不然可能出错
#define _toupper(c) ((c)+'A'-'a')

7.10 首先释放,然后重新分配

大多数C语言实现都为使用者提供了三个内存分配函数: malloc, realloc, free。
不同版本的C语言,可能会realloc的使用要求不一致;有的需要先释放,在realloc,使用时需要特别注意;

// 申请一块n大小的内存空间
int *p = malloc(n);	
// 重新申请一块 m 大小的内存空间,可能涉及到内存的拷贝; min(n,m)部分存储的内容将保持不变
int *rea_p = realloc(p,m);					
if (p != NULL)
	free(p);						// 将申请的内容空间释放掉
if (rea_p  != NULL)
	free(rea_p );

在某些版本的代码中,下面的代码是合法的

free(p);
p = realloc(p, new_size);

那么在如上系统中,我们可以使用下面的方法来释放 一个链表中的所有元素; 同时不必担心调用free之后,会是 p->next 变的无效;不过这种技巧不值得推荐,因为并非所有的C实现在某块内存被释放后还能较长时间的保留之;

for (p = head; p != NULL; p = p->next)
	free(p);
  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值