http://blog.csdn.net/deep_explore/article/details/6025586
2010
總感覺數(shù)據(jù)類型和編碼過程的概念在腦子里比較模糊。。。。所以細(xì)細(xì)來研究一下很有必要了!
(一)理解整數(shù)數(shù)據(jù)類型的含義和編碼方法
C 中的整數(shù)類型可以分為有符號整數(shù)和無符號整數(shù):
有符號整數(shù)類型(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
無符號整數(shù)類型(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
也就是說,有本質(zhì)上區(qū)別的整數(shù)類型有5+6一共11種。
在32位的機(jī)器上(比如我自己的機(jī)器Intel P7350),也就是說,“虛擬內(nèi)存空間”的地址范圍是:
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 (該行計(jì)數(shù),不然眼都花了?。?/p>
所以在32位地址空間大約為4GB,而我的機(jī)器內(nèi)存才2GB。其實(shí)這就是計(jì)算機(jī)中“字長(word size)”的概念:字長就是計(jì)算機(jī)內(nèi)存地址能編碼的最大比特位(這里是32)。
整數(shù)數(shù)據(jù)類型是怎樣編碼的呢?
無符號類型的編碼很明確
無論是無符號還是有符號的字符類型都有8位(1個字節(jié))
對于其他的數(shù)據(jù)類型,C只定義了最小的存儲空間大小。short至少為2個字節(jié),long至少占4個字節(jié),long long至少占8個字節(jié)。這里的“至少”的含義short可以是>2個字節(jié)的,long可以>4個字節(jié)的,long long 可以大于8個字節(jié)的。各自類型實(shí)際占了多少類型,這個到底取決于CPU還是編譯器呢? 我來考察一下我的編程環(huán)境吧。(我用的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 }
運(yùn)行的結(jié)果是:
[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 的長度是一樣的,并且有符號與無符號是等長的!
盡管不同的環(huán)境下,各種數(shù)據(jù)類型的長度不是一成不變的,但是遵循下面的規(guī)則:
sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
其實(shí),整數(shù)類型(這里不包含布爾類型11-1=10)聲明的本質(zhì)是告訴編譯器2個信息:(1)在內(nèi)存中要取的位數(shù) (2)第一位是不是符號位。下面看看在我的環(huán)境下的整數(shù)類型編碼情況:
現(xiàn)介紹一下3個基本概念:(注意:原碼,反碼, 補(bǔ)碼是無符號的時候都是一樣的,這樣的編碼主要是針對有符號而言的?。?/p>
原碼--------------符號位+實(shí)際二進(jìn)制值,如-3原碼1000 0011 (以8位為例)。
反碼--------------原碼(不包含符號位)取反 -3反碼1111 1100
補(bǔ)碼--------------反碼+1 -3補(bǔ)碼 1111 1101
所以,有符號的數(shù)的補(bǔ)碼比反碼大1。
所以對于有符號的數(shù)的編碼方法就有了兩種:
(1)one's complement (反碼表示法)
(2)two's complement ( 補(bǔ)碼表示法)
現(xiàn)在有一個問題:為什么不直接用原碼表示?主要是為了解決“+0” 和“-0”的問題!我們要把“-0”給變成其他數(shù)!
如果用原碼表示8位有符號數(shù):1 1111111(-127)--------1 0000000(-0)~0 0000000(+0)----------0 1111111(+127)
如果用反碼表示8位有符號數(shù):1 0000000(-127)--------1 1111111(-0)~0 1111111(+0)----------0 0000000(+127)
如果用補(bǔ)碼表示8位有符號數(shù):1 0000001(-127)--------1 0000000(-0)~0 0000000(+0)----------0 0000001(+127)
可以看出:如果把補(bǔ)碼的“-0”放在最前面(-127)前面,正好可以表示-128!再把1的補(bǔ)碼(0 1111111)與127的補(bǔ)碼位置掉換!這樣就是從 1 0000000(-128)~0 1111111(127)連續(xù)的序列。(在這里真是佩服編碼的設(shè)計(jì)者!很巧妙)
而反碼的數(shù)據(jù)處理就比較麻煩了。補(bǔ)碼是從-127到127連續(xù)上升的,除了在“-0”、“1”、“127”的位置。只要進(jìn)行3個數(shù)字的移動就可以確保出現(xiàn)遞增序列。反碼只需要在補(bǔ)碼的基礎(chǔ)上減去1得到的序列! 反碼沒有補(bǔ)碼來的直觀簡潔,所以幾乎現(xiàn)在所有的機(jī)器都都采用補(bǔ)碼來表示有符號數(shù)!
(二)類型轉(zhuǎn)換的過程細(xì)節(jié)-細(xì)細(xì)揣摩
無符號之間的轉(zhuǎn)換很簡單,只要改變空間字節(jié)大小就OK。
重點(diǎn)是有符號與無符號之間的轉(zhuǎn)換問題:
強(qiáng)制類型轉(zhuǎn)換的本質(zhì)是:參數(shù)位表示不變,變化的是它的解釋方法。
下面的程序證明了這一點(diǎn):
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 }
運(yùn)行結(jié)果:
0xfffffffd:-3
0xfffffffd:4294967293
i和j的位表示相同,變化了的是它的解釋方法。
最后總結(jié)一些注意點(diǎn):
(1)在賦值的時候,如果要表達(dá)無符號的意圖,那么就要在數(shù)的后面加“U”或“u”,否則將當(dāng)成是有符號來處理。
(2)在一個表達(dá)式中同時出現(xiàn)有符號和無符號的時候的處理辦法是:有符號數(shù)將被強(qiáng)制的轉(zhuǎn)換成了無符號數(shù)了。所以將出現(xiàn)一些奇怪的事情。
如果誰能找出下面的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 }
(當(dāng)length=0的時候,出現(xiàn)內(nèi)存溢出的錯誤,因?yàn)檎Z句i<=length-1中的length為無符號數(shù),所以i,1都將被轉(zhuǎn)換成無符號數(shù),這也不會有問題,這里的BUG出現(xiàn)在length = 0的情況。按道理返回的是0.00..但是這里得到運(yùn)行結(jié)果是“Segmentation fault(段錯誤)”,原因在于length-1的結(jié)果-1將被轉(zhuǎn)換成無符號數(shù),然而unsigned類型并不能存放這么大的數(shù),所以出現(xiàn)了內(nèi)存溢出的錯誤!)
如果把for( i = 0; i <= length - 1; i++ )換成for( i = 0; i < length ; i++ )就會避免這樣的BUG!但這也不是個好辦法,避免這樣的錯誤的辦法就是絕對不要用無符號的數(shù)。這不能說明無符號數(shù)沒有用途,在有的地方無符號數(shù)是非常有用的!比如,把字看成是位的集合而沒有任何數(shù)字意義事,還有當(dāng)實(shí)現(xiàn)模運(yùn)算和多精度運(yùn)算時,無符號數(shù)也非常有用。