前景回顧
前面我們瞭解到依據CPU的端模式的架構不同,資料的儲存的位元組序也不同
BE big-endian 大端模式,最直觀的位元組序 地址低位儲存值的高位,地址高位儲存值的低位 ,資料填寫時,不要考慮對應關係,只需要把記憶體位址從左至右按照由低到高的順序寫出,把值按照通常的高位到低位的順序寫出,兩者對照,一個位元組一個位元組的填充進去。
LE little-endian 小端模式,則最符合人的思維的位元組序,地址低位儲存值的低位,地址高位儲存值的高位 ,這點比較符合人的思維的位元組序,是因為從人的第一觀感來說,低位值小,就應該放在記憶體位址小的地方,也即記憶體位址低位,反之,高位值就應該放在記憶體位址大的地方,也即記憶體位址高位
任何資料在記憶體中都是以二進位的形式儲存的。
具體參照深入理解電腦系統-之-數值儲存(一)-CPU大端和小端模式詳解
接著我們討論了如何通過程式列印出變數在記憶體中的儲存形式,具體參照深入理解電腦系統-之-數值儲存(二)–C程式列印變數的每一位元組或者位
然後我們瞭解了原碼,反碼,補碼和移碼的表示形式,具體參照深入理解電腦系統-之-數值儲存(三)– 原碼、反碼、補碼和移碼詳解 整型資料概述 整型數
C/C++語言中表示整數、字元和布爾值的算術類型合稱為整型。
字元類型有兩種:char 和 wchar_t。
char 類型保證了有足夠的空間,能夠儲存機器基底字元集中任何字元相應的數值,因此,char 類型通常是單個機器位元組byte)。
wchar_t 類型用於擴充字元集,比如漢字,這些字元集中的一些字元不能用單個 char 表示。
short、int 和 long 類型都表示整型值,儲存空間的大小不同。一般, short類型為半個機器字長,int 類型為一個機器字長,而long 類型為一個或兩個機器字長(在 32 位元電腦中 int 類型和 long 類型通常字長是相同的)。
bool 類型表示真值 true 和 false。可以將算術類型的任何值賦給 bool對象。0 值算術類型代表 false,任何非 0 的值都代表 true。 帶符號和無符號類型
除 bool 類型外,整型可以是帶符號的(signed)也可以是無符號的
(unsigned)。顧名思義,帶符號類型可以表示正數也可以表示負數(包括 0),
而無符號型只能表示大於或等於 0 的數。
整型 int、short 和 long 都預設為帶符號型。要獲得無符號型則必須指定該類型為 unsigned,比如 unsigned long。unsigned int 類型可以簡寫為unsigned,也就是說,unsigned 後不加其他類型說明符意味著是 unsigned int 。
和其他整型不同,char 有三種不同的類型:plain char 、unsigned char 和signed char。雖然 char 有三種不同的類型,但只有兩種表示方式。可以使用unsigned char 或 signed char 表示 char 類型。使用哪種 char 表示方式由編譯器而定。 整數以補碼形式儲存 樣本程式
那現在我們明細了。我們通過下面的程式來查看,整數在記憶體中的表現形式。
#include <stdio.h>#include <stdlib.h>int check_end(){ int i = 0x12345678; char *c = (char *)&i; return (*c == 0x12);}int print_bit(void *addr, int size){ unsigned char *ptr = (unsigned char *)addr; int print_bytes = 0; if(ptr == NULL) { return -1; } for(print_bytes = 0; print_bytes < size; print_bytes++, ptr++) {#ifdef DEBUG printf("byte %d, data = %02x -=>", print_bytes, *ptr); #endif for(int print_bits = 7; print_bits >= 0; print_bits--) { printf("%d", ((*ptr >> print_bits) & 1)); }#ifdef DEBUG printf("\n");#endif } printf("\n"); return print_bytes;}int print_byte(void *addr, int size){ unsigned char *paddr = (unsigned char *)addr; int print_bytes = 0; if(paddr == NULL) { return -1; } while(print_bytes < size) { printf("%02x", *paddr); paddr++; print_bytes++; } printf("\n"); return print_bytes; }int main(void){ if(check_end() == 1) { printf("大端模式\n"); } else { printf("小端模式\n"); } int a = 1; print_bit((void *)&a, sizeof(a)); int b = -1; print_bit((void *)&b, sizeof(b));/*X = -00101011 = 0x0000002B [X]原= 10000000 00000000 00000000 00101011[X]反= 11111111 11111111 11111111 11010100[X]補= 11111111 11111111 11111111 11010101[X]移=01010101*/ int x = 0x2B; print_bit((void *)&x, sizeof(x)); int y = -0x2B; print_bit((void *)&y, sizeof(y)); return EXIT_SUCCESS;}
程式分析
不管是原碼,反碼還是,補碼,正數的表示方法是一致的。
但是負數來說,是有區別的。
X = -0X2B = -00101011 = 0x0000002B
[X]原 = 10000000 00000000 00000000 00101011 = 0x8000002B
[X]反 = 11111111 11111111 11111111 11010100 = 0xFFFFFFD4
[X]補 = 11111111 11111111 11111111 11010101 = 0xFFFFFFD5
[X]移 =01010101
由於我們的intel的CPU架構採用小端模式,那麼用補碼儲存的時候,0xFFFFFFD5用小端模式儲存下來,低位元組到高位元組的資料排列下來依次是0x D5 FF FF FF,即11010101 11111111 11111111 11111111
可見,整數是以補碼的形式在記憶體中儲存的 溢出 整型值的表示
無符號型中,所有的位都表示數值。如果在某種機器中,定義一種類型使用8 位表示,那麼這種類型的 unsigned 型可以取值 0 到 255。
C++ 標準並未定義 signed 類型如何用位來表示,而是由每個編譯器自由決定如何表示 signed 類型。這些表示方式會影響signed 類型的取值範圍。
8 位signed 類型的取值肯定至少是從 -127 到 127,但也有許多實現允許取值從-128 到 127。
表示 signed 整數型別最常見的策略是用其中一個位作為符號位。符號位為1,值就為負數;符號位為 0,值就為 0 或正數。一個 signed 整型取值是從 -128到 127。
對於 unsigned 類型來說,編譯器必須調整越界值使其滿足要求。編譯器會將該值對 unsigned 類型的可能取值數目求模,然後取所得值。比如 8 位的unsigned char,其取值範圍從 0 到 255(包括 255)。如果賦給超出這個範圍的值,那麼編譯器將會取該值對 256 求模後的值(其本質就是多出的位被截斷,捨棄掉)。
例如,如果試圖將 336 儲存到 8 位的unsigned char 中,則實際賦值為 80,因為 80 是 336 對 256 求模後的值。
高位元組的進位1被截斷後捨棄掉,這樣就剩下80=336-256
對於 unsigned 類型來說,負數總是超出其取值範圍。unsigned 類型的對象可能永遠不會儲存負數。有些語言中將負數賦給 unsigned 類型是非法的,但在 C/C++ 中這是合法的。
C/C++ 中,把負值賦給 unsigned 對象是完全合法的,其結果是
該負數對該類型的取值個數求模後的值。
所以,如果把 -1 賦給8 位的 unsigned char,那麼結果是 255,因為 255 是 -1 對256 求模後的值。
同樣是截斷,-1用補碼儲存,高位溢出位被截斷,剩下的就是255
當將超過取值範圍的值賦給 signed 類型時,由編譯器決定實際賦的值。在實際操作中,很多的編譯器處理 signed 類型的方式和 unsigned 類型類似。也就是說,賦值時是取該值對該類型取值數目求模後的值。然而我們不能保證編譯器都會這樣處理 signed 類型。
#include <stdio.h>#include <stdlib.h>int check_end(){ int i = 0x12345678; char *c = (char *)&i; return (*c == 0x12);}int print_bit(void *addr, int size){ unsigned char *ptr = (unsigned char *)addr; int print_bytes = 0; if(ptr == NULL) { return -1; } for(print_bytes = 0; print_bytes < size; print_bytes++, ptr++) {#ifdef DEBUG printf("byte %d, data = %02x -=>", print_bytes, *ptr);#endif for(int print_bits = 7; print_bits >= 0; print_bits--) { printf("%d", ((*ptr >> print_bits) & 1)); }#ifdef DEBUG printf("\n");#endif } printf("\n"); return print_bytes;}int print_byte(void *addr, int size){ unsigned char *paddr = (unsigned char *)addr; int print_bytes = 0; if(paddr == NULL) { return -1; } while(print_bytes < size) { printf("%02x", *paddr); paddr++; print_bytes++; } printf("\n"); return print_bytes;}int main(void){ printf("%d\n", sizeof(long)); unsigned char a = 336; print_bit((void *)&a, sizeof(char)); print_bit((void *)&a, sizeof(short)); printf("%d\n", a); int *p = &a; print_bit((void *)p, sizeof(int)); printf("%d\n", *p); unsigned char b = -1; print_bit((void *)&b, sizeof(b)); printf("%d\n", b); union A { short ia; unsigned char ca[2]; }aa; aa.ia = 336; printf("%d %d\n", aa.ca[0], aa.ca[1]); aa.ca[0] = 336; printf("%d %d\n", aa.ca[0], aa.ca[1]); aa.ca[1] = 336; printf("%d %d\n", aa.ca[0], aa.ca[1]); return EXIT_SUCCESS;}