总感觉数据类型和编码过程的概念在脑子里比较模糊。。。。所以细细来研究一下很有必要了!
(一)理解整数数据类型的含义和编码方法
C 中的整数类型可以分为有符号整数和无符号整数:
有符号整数类型(5种): 同义词
signed char ----------------------------
int------------------------------------------signed,signed int
short---------------------------------------short int, signed int, signed short int
long----------------------------------------long int, signed long, signed long int
long long----------------------------------long long int, signed long long, signed long long int
无符号整数类型(6种):
_Bool---------------------------------------bool
unsigned char---------------------------
unsigned int------------------------------unsigned
unsigned short---------------------------unsigned short int
unsigned long----------------------------unsigned long int
unsinged long long--------------------- unsigned long long int
也就是说,有本质上区别的整数类型有5+6一共11种。
在32位的机器上(比如我自己的机器Intel P7350),也就是说,“虚拟内存空间”的地址范围是:
0000 0000 0000 0000 0000 0000 0000 0000 (最小值)
1111 1111 1111 1111 1111 1111 1111 1111 (最大值)4294967295(类型是无符号的)
1 2 3 4 5 6 7 8 (该行计数,不然眼都花了!)
所以在32位地址空间大约为4GB,而我的机器内存才2GB。其实这就是计算机中“字长(word size)”的概念:字长就是计算机内存地址能编码的最大比特位(这里是32)。
整数数据类型是怎样编码的呢?
无符号类型的编码很明确
无论是无符号还是有符号的字符类型都有8位(1个字节)
对于其他的数据类型,C只定义了最小的存储空间大小。short至少为2个字节,long至少占4个字节,long long至少占8个字节。这里的“至少”的含义short可以是>2个字节的,long可以>4个字节的,long long 可以大于8个字节的。各自类型实际占了多少类型,这个到底取决于CPU还是编译器呢? 我来考察一下我的编程环境吧。(我用的CPU是Intel P7350,编译是GCC4.1.2
)下面是我的测试程序:
1 #include<stdio.h>
2 int
3 main()
4 {
5 printf("sizeof(short)=%d/n", sizeof(short));
6 printf("sizeof(int)=%d/n", sizeof(int));
7 printf("sizeof(long)=%d/n", sizeof(long));
8 printf("sizeof(long long)=%d/n", sizeof(long long));
9 printf("sizeof(unsigned int)=%d/n", sizeof(unsigned int));
10 printf("sizeof(unsigned short)=%d/n", sizeof(unsigned short));
11 printf("sizeof(unsigned long)=%d/n", sizeof(unsigned long));
12 printf("sizeof(unsigned long long)=%d/n", sizeof(unsigned long long));
13
14 return 0;
15 }
运行的结果是:
[root@localhost ~]# ./a.out
sizeof(short)=2
sizeof(int)=4
sizeof(long)=8
sizeof(long long)=8
sizeof(unsigned int)=4
sizeof(unsigned short)=2
sizeof(unsigned long)=8
sizeof(unsigned long long)=8
可以看到long 与long long 的长度是一样的,并且有符号与无符号是等长的!
尽管不同的环境下,各种数据类型的长度不是一成不变的,但是遵循下面的规则:
sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
其实,整数类型(这里不包含布尔类型11-1=10)声明的本质是告诉编译器2个信息:(1)在内存中要取的位数 (2)第一位是不是符号位。下面看看在我的环境下的整数类型编码情况:
现介绍一下3个基本概念:(注意:原码,反码, 补码是无符号的时候都是一样的,这样的编码主要是针对有符号而言的!)
原码--------------符号位+实际二进制值,如-3原码1000 0011 (以8位为例)。
反码--------------原码(不包含符号位)取反 -3反码1111 1100
补码--------------反码+1 -3补码 1111 1101
所以,有符号的数的补码比反码大1。
所以对于有符号的数的编码方法就有了两种:
(1)one's complement (反码表示法)
(2)two's complement ( 补码表示法)
现在有一个问题:为什么不直接用原码表示?主要是为了解决“+0” 和“-0”的问题!我们要把“-0”给变成其他数!
如果用原码表示8位有符号数:1 1111111(-127)--------1 0000000(-0)~0 0000000(+0)----------0 1111111(+127)
如果用反码表示8位有符号数:1 0000000(-127)--------1 1111111(-0)~0 1111111(+0)----------0 0000000(+127)
如果用补码表示8位有符号数:1 0000001(-127)--------1 0000000(-0)~0 0000000(+0)----------0 0000001(+127)
可以看出:如果把补码的“-0”放在最前面(-127)前面,正好可以表示-128!再把1的补码(0 1111111)与127的补码位置掉换!这样就是从 1 0000000(-128)~0 1111111(127)连续的序列。(在这里真是佩服编码的设计者!很巧妙)
而反码的数据处理就比较麻烦了。补码是从-127到127连续上升的,除了在“-0”、“1”、“127”的位置。只要进行3个数字的移动就可以确 保出现递增序列。反码只需要在补码的基础上减去1得到的序列! 反码没有补码来的直观简洁,所以几乎现在所有的机器都都采用补码来表示有符号数!
(二)类型转换的过程细节-细细揣摩
无符号之间的转换很简单,只要改变空间字节大小就OK。
重点是有符号与无符号之间的转换问题:
强制类型转换的本质是:参数位表示不变,变化的是它的解释方法。
下面的程序证明了这一点:
1 #include<stdio.h>
2 int
3 main()
4 {
5 int i = -3;
6 unsigned j = (unsigned)i;
7 printf("%#x:%d/n", i, i);
8 printf("%#x:%u/n", j, j);
9 return 0;
10 }
运行结果:
0xfffffffd:-3
0xfffffffd:4294967293
i和j的位表示相同,变化了的是它的解释方法。
最后总结一些注意点:
(1)在赋值的时候,如果要表达无符号的意图,那么就要在数的后面加“U”或“u”,否则将当成是有符号来处理。
(2)在一个表达式中同时出现有符号和无符号的时候的处理办法是:有符号数将被强制的转换成了无符号数了。所以将出现一些奇怪的事情。
如果谁能找出下面的BUG,我想他会非常的兴奋,起码我是这样。。。。。
1 #include<stdio.h>
2
3 float sum_float( float a[], unsigned length )
4 {
5 int i;
6 float sum = 0;
7
8 for( i = 0; i <= length - 1; i++ ){
9 sum += a[i];
10 }
11
12 return sum;
13
14 }
15 int main()
16 {
17 float a[4] = { 1.1, 2.2, 3.3, 4.4 };
18 printf("%f/n", sum_float (a, 0));
19 return 0;
20 }
(当length=0的时候,出现内存溢出的错误,因为语句i<=length-1中的length为无符号数,所以i,1都将被转换成无符 号数,这也不会有问题,这里的BUG出现在length = 0的情况。按道理返回的是0.00..但是这里得到运行结果是“Segmentation fault(段错误)”,原因在于length-1的结果-1将被转换成无符号数,然而unsigned类型并不能存放这么大的数,所以出现了内存溢出的 错误!)
如果把for( i = 0; i <= length - 1; i++ )换成for( i = 0; i < length ; i++ )就会避免这样的BUG!但这也不是个好办法,避免这样的错误的办法就是绝对不要用无符号的数。这不能说明无符号数没有用途,在有的地方无符号数是非常有 用的!比如,把字看成是位的集合而没有任何数字意义事,还有当实现模运算和多精度运算时,无符号数也非常有用。