一、基本知识
1. 字
字是指计算机运算和传送数据的基本单位,其长度即为字长,字长指明了指针数据类型的大小。因此,字长表示了一个计算机的寻址范围,例如:32位机的指针占4个字节,其寻址范围就是0~2^32 - 1。
上面说的字长是机器字长,即CPU的字长,而操作系统字长不一定与机器字长一致(例如,在64位机器上装32位系统)。
2. C语言中基本数据类型的长度
编译器对各种数据类型长度的定义是依据操作系统字长的,下表给出的是操作系统字长和机器字长一致时的情况:
C语言数据类型 | 32位机器 | 64位机器 |
char | 1 | 1 |
short int | 2 | 2 |
int | 4 | 4 |
long int | 4 | 8 |
long long int | 8 | 8 |
指针类型 | 4 | 8 |
float | 4 | 4 |
double | 8 | 8 |
3. 数据对齐方式
不同的机器在内存中存储字节的顺序可能不一样,主要分为小端模式与大端模式:
小端模式先存储数据的低字节,再存储高字节(低字节在低地址,高字节在高地址);
大端模式先存储数据的高字节,再存储低字节(高字节在低地址,低字节在高地址)。
在两台采用不同对齐方式的计算机之间通过网络传递二进制数据时就会有一个问题:发送方发送的字节顺序与接收方接收的字节顺序是反序的。TCP/IP协议定义了统一的网络字节顺序(大端顺序),发送方在发送数据时将主机字节顺序转换成网络字节顺序后再发送出去,而接收方将网络字节顺序转换成主机字节顺序后再处理。Unix系统提供了以下函数来实现主机字节顺序和网络字节顺序的转换:
//主机字节顺序 to 网络字节顺序
unsigned short int htons(unsigned short int);
unsigned long int htonl(unsigned long int);
// 网络字节顺序 to 主机字节顺序
unsigned short int ntohs(unsigned short int);
unsigned long int ntohl(unsigned long int);
以上函数通常只在传递端口等数据时需要用到,而传递实际数据是不需要的,端口等数据是需要TCP/IP协议栈来解析的,必须转换成统一的字节序,而负载数据的顺序不是网络协议关心的事情。
4. 关于移位运算要注意的一点
对一个数进行移位运算,当位移量K超过该数据类型的长度L时,实际通常会移动 K % L 位。但C语言标准没有规定这一点,而Java语言明确要求按照 K % L来计算实际的位移量。
5. 十进制数据快速转换成十六进制
对于十进制数d,如果d = 2n,且n = k + 4m,(k < 4)。则d = 2k * (24)m,那么d可以写成为十六进制数:
0xp000...0 ,p = 2k,p后跟m个0
例如:211 = 2048,211 = 23 * (24)2,则 2048 = 0x800。
那么,对于任意的十进制数d = 2n + q,n = k + 4m,(k < 4)。则我们可以先将2n 部分利用上述办法转换成十六进制数,然后再加上q即可。
例如:2067 = 2048 + 19 = 0x800 + 19 = 0x813 。
二、整数表示
1. 无符号整数的编码
Uint = Xw-12w-1 + Xw-22w-2 + ... + X020 ,无符号整数编码成w个二进制位,Xi表示第i位的值(0或者1),2i表示第i位的权值
能表示的无符号整数的范围是 0 ~ 2w-1
2. 有符号整数的编码
Tint = -Xw-12w-1 + Xw-22w-2 + ... + X020 ,有符号整数编码成w个二进制位,与无符号整数的表示方式类似,只是第w-1位(最高位)的权值是-2w-1
能表示的有符号整数的范围是 -2w-1 ~ 2w-1 - 1
3. 最大值与最小值
Umin = 0 ,Umax = 2w-1
Tmin = -2w-1,Tmax = 2w-1 - 1
我们可以发现:
|Tmin| = Tmax + 1 ,这是因为:一半的数表示负数(最高位为1),一半的数表示非负数(最高位为0),而非负数中包括0,于是能表示的整数就比负数少了一个。
Umax = 2Tmax + 1 ,这是因为:在有符号整数表示中,所有的负数都成了整数,即Umax = |Tmin| + Tmax = 2Tmax + 1 。
4. 无符号整数和有符号整数的转换
相同位数的无符号整数和有符号整数之间进行转换时,底层的位表示并没有发生变化,只是对这些位的解释发生了改变。
用1和2中的 Uint - Tint 可得:Uint - Tint = Xw-12w-1 - (-Xw-12w-1 ) = Xw-12w
那么有符号整数和无符号整数之间的转换函数如下:
(1)有符号整数 -> 无符号整数
T2U = T + Xw-12w ,即:
当T > 0 时,Xw-1 = 0,T2U = T ,即等于原值;
当T < 0 时,Xw-1 = 1,T2U = T + 2w ;
(2)无符号整数 -> 有符号整数
U2T = U - Xw-12w ,即:
当U >= 2w-1 时,Xw-1 = 1,U2T = U - 2w ;
当U < 2w-1 时,Xw-1 = 0,U2T = U;
5. C语言中有符号整数和无符号整数的转换
有符号整数和无符号整数的转换即按照4中的规则进行。C语言中,有符号整数和无符号整数可以通过强制转换进行,更值得注意的是有符号整数隐式转换为无符号整数。
C语言中,若参与运算的两个整数一个是有符号整数,另一个是无符号整数,那么,有符号整数会被隐式地转换为无符号整数。
上述规则可能会导致一些难以察觉的编程错误,我们需要注意。例如:
float add(float num[],unsigned int length)
{
int i = 0;
float sum = 0;
for(i = 0; i <= length - 1; i++) // 减法是按加法来处理的
sum += num[i];
return sum;
}
length声明为无符号整数类型,-1默认是有符号整数类型,那么-1会被转换成无符号整数,根据上面的转换规则U(-1) = 2w - 1,这样后面访问数组就会越界。
当转换既涉及到大小转换也涉及符号转换时,C语言的转换规则是先进行大小转换,然后再进行符号转换,例如:unsigned int 转换为short int,先扩展位改变大小,再进行符号的转换。
将较长字长的数字x转换为较短字长的数字时,会截去高k位,截断操作就相当于对x进行求余运算:x % 2k 。
三、浮点数的表示
1. 小数的二进制表示
b = bmbm-1 ... b1b0 . b-1b-2 ... b-n ,其中小数点之前的权值分别为2m、2m-1 ... 21、20 ,小数点之后的权值为2-1、2-2 ... 2-n 。
小数的二进制表示法只能精确表示那些能够被写成x * 2y的数,其他数只能被近似表示。
2. IEEE754浮点表示法
IEEE754是浮点数表示的国际标准,采用 (-1)s * M * 2E 来表示一个数,其中:
s表示符号位,1表示负数,0表示正数;
M表示尾数,是一个二进制小数;
E表示阶码,对浮点数进行加权,E可以是负数;
看看32位浮点数表示法的位表示:
符号位S (占1位) | 阶码E(占8位) | 尾数M(占23位) |
根据阶码E的位表示,可以分成以下几种情况:
(1)规格化(E不全为0,也不全为1)
阶码E的位表示按照无符号整数来解释,其值e的范围即1 ~ 255,则E = e - (27-1),即E的取值范围是 -126 ~ 127 ;
M隐含以1开头,尾数M的位表示只表示了小数部分,即最高位的权值为-1。
(2)非规格化(E全为0)
E = 1 - (27 - 1);
尾数M不再具有隐含的1,其值即为表示的小数部分的值。
规格化数中尾数M总是大于等于1,因此无法表示0,非规格化数主要用于表示0 :
符号位S为0,阶码E全为0,尾数M全为0时,即表示+0.0;
符号位S为1,阶码E全为0,尾数M全为0时,即表示-0.0;
非规格化数的E = 1 - (27 - 1),可以补偿尾数M不再包含的1。
(3)特殊值(E全为1)
当尾数M全为0时表示无穷大,符号位s为1表示负无穷大,s为0时表示正无穷大;
当尾数M非0时表示NaN(Not a Number)。
3. 浮点数的舍入
浮点数有一定的表示范围和精度,对于某些小数,只能近似表示它们,即必须对这些小数进行舍入后表示。默认采用向偶数舍入(round-to-even)的方式,以十进制小数为例,小数点后保留1位数:
当实际值不是正中间的值时,则向最接近的值舍入,例如:1.43舍入成1.4,1.47舍入成1.5;
当实际值恰好是正中间的值时,则使得最低有效数字是偶数,例如:1.45舍入成1.4,而不是1.5。