1. 數(shù)據(jù)的寬度與單位
計算機內(nèi)部任何數(shù)據(jù)都被表示成二進制編碼形式。二進制數(shù)據(jù)的每一位(0 or 1)二進制信息的最小單位,稱為一個"比特"(bit),簡稱"位",bit是計算機中存儲,運算和傳輸信息的最小單位。
每個西文字符需要用8個比特表示,而每個漢字需要用16個比特才能表示。計算機內(nèi)部,二進制信息的計量單位是"字節(jié)"(Byte),也成為"位組"。 1 Byte = 8 bit
計算機中運行和處理二進制信息時使用的單位除了比特和字節(jié)之外,還經(jīng)常使用"字"(word)作為單位,必須注意,不同的計算機,字的長度和組成不完全相同,有的由2個字節(jié)組成,有的由4個,8個,甚至16個字節(jié)組成。
在考察計算機性能時,一個重要的指標就是機器的"字長"。平時所說的"某種機器是32位機或是64位機",其中的32,64就是指的字長。所謂機器字長通常指CPU內(nèi)部用于整數(shù)運算的數(shù)據(jù)通路的寬度,也就是說,"字長"等于CPU內(nèi)部用于整數(shù)運算的運算器位數(shù)和通用寄存器寬度。
注 : "字"和"字長"的概念不同
|- "字"用來表示被處理信息的單位,用來度量各種數(shù)據(jù)類型的寬度,大小以機器為準
|-"字長"表示數(shù)據(jù)運算,存儲和傳送的部件的寬度,它反映了計算機處理信息的一種能力。
單位換算
1 B = 8 b
K :1KB = 2^10 B = 1024 字節(jié)
M :1MB = 2^20 B
G : 1GB = 2^30 B
T : 1TB = 2^40 B
C語言中數(shù)值數(shù)據(jù)類型的寬度(字節(jié)(Byte)為單位)
| 類型 | 典型的32位機器(Linux) | 64位機器 |
|---|---|---|
| char | 1 | 1 |
| short int | 2 | 2 |
| int | 4 | 4 |
| long int | 4 | 8 |
| --- | --- | --- |
| char * | 4 | 8 |
| --- | --- | --- |
| float | 4 | 4 |
| double | 8 | 8 |
另外,對于相同類型的數(shù)據(jù),并不是所有機器都采用相同的數(shù)據(jù)寬度,分配的字節(jié)數(shù)隨處理器和編譯器的不同而不同
例如:指針類型一般認為32位機器是 4 個字節(jié),64位機器是 8 個字節(jié),這個沒錯,但是在64位的機器上編譯程序,計算指針的大小,返回的是 4,這時認為是不是以前書上講的是錯的,其實不是,只不過編譯選項里的平臺是Win32,也就是在64位系統(tǒng)運行的是32位的程序,所以說是受編譯器的影響
2. 數(shù)據(jù)的儲存和排列順序
現(xiàn)代計算機基本上都采用字節(jié)編址方式,即對存儲空間的存儲單元編號時,每個地址編號中存放一個字節(jié)。
- 例如:在一個按字節(jié)編址的計算機中,假定
int型變量i的地址為0800H,i的機器數(shù)為01 23 45 67H,根據(jù)不同的地址排序方式,i的4個字節(jié)01H,23H,45H,67H有不同的排列順序
兩種排列方式:
大端模式(big endian) : 將數(shù)據(jù)的最高有效字節(jié)存放在低地址單元,最低有效字節(jié)存放在高地址單元
小端模式(small endian) : 將數(shù)據(jù)的最高有效字節(jié)存放在高地址單元,最低有效字節(jié)存放在低地址單元
| 字節(jié)編址地址 | …… | 0800H | 0801H | 0802H | 0803H | …… |
|---|---|---|---|---|---|---|
| 大端 | …… | 01H | 23H | 45H | 67H | …… |
| 小端 | …… | 67H | 45H | 23H | 01H | …… |
補充: 網(wǎng)絡(luò)序是大端模式
Linux網(wǎng)絡(luò)編程中,為使網(wǎng)絡(luò)程序具有可移植性,使同樣的C代碼在大端和小端計算機上編譯后都能正常運行,可以調(diào)用以下庫函數(shù)做網(wǎng)絡(luò)字節(jié)序和主機字節(jié)序的轉(zhuǎn)換。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
3. 數(shù)據(jù)對齊原則
3.1 為什么要數(shù)據(jù)對齊?
可以將存儲器看成由連續(xù)的位(cell)構(gòu)成,每8位為一個字節(jié),每個字節(jié)有一個地址編號,稱之為按字節(jié)編址,假定計算機系統(tǒng)中訪問機制限制每次訪存最多只能只能讀寫64位,即8個字節(jié),那么,第0-7字節(jié)可以同時讀寫,第8-15字節(jié)可以同時讀寫,以此類推,這種稱為8字節(jié)寬的存儲機制;因此,如果一條指令要訪問存儲器的數(shù)據(jù)不在 8i ~ 8i + 7 (i = 0,1,2 ...)之間的存儲單元內(nèi),那么就需要多次訪存,因此延長了指令的執(zhí)行時間,引入數(shù)據(jù)對齊的最重要的原因就是為了避免多次訪存帶來的指令執(zhí)行效率的降低?。?!
對齊方式下程序的執(zhí)行效率更高,為此,操作系統(tǒng)通常按照對齊方式分配管理內(nèi)存,編譯器也按照對齊方式轉(zhuǎn)換代碼。
3.2 基本數(shù)據(jù)類型的對齊策略
-
最簡單的對齊策略:要求不同的基本類型按照其數(shù)據(jù)長度進行對齊;
例如,int類型數(shù)據(jù)的長度為4個字節(jié),因此規(guī)定int型數(shù)據(jù)的地址是4的倍數(shù),char的長度為1個字節(jié),時刻是對齊的,其他的同理;這種策略下,對于8字節(jié)寬的存儲機制來說,所有的基本類型都僅需訪存一次;windows采用的就是這種策略 -
Linux的對齊策略 : 更加寬松,規(guī)定short數(shù)據(jù)的地址是2的倍數(shù),其他的如int ,float,double和指針等類型的數(shù)據(jù)的地址都是4的倍數(shù);
這種情況,對于8字節(jié)寬的存儲機制,double型數(shù)據(jù)就可能需要訪存兩次
3.3 結(jié)構(gòu)體 & 結(jié)構(gòu)數(shù)組的對齊策略
滿足下面三個條件
- (1). 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除
[備注]:編譯器在給結(jié)構(gòu)體開辟空間時,首先找到結(jié)構(gòu)體中最寬的基本數(shù)據(jù)類型,然后尋找內(nèi)存地址能被該基本數(shù)據(jù)類型所整除的位置,作為結(jié)構(gòu)體的首地址。將這個最寬的基本數(shù)據(jù)類型的大小作為對齊的模數(shù),也就是我們經(jīng)常說的4字節(jié)對齊(32位機器默認),8字節(jié)對齊(64位機器默認)。 - (2). 結(jié)構(gòu)體中的每個數(shù)據(jù)成員都按照"基本數(shù)據(jù)類型對齊策略"的要求進行對齊
也就是說:結(jié)構(gòu)體每個成員相對于結(jié)構(gòu)體首地址的偏移量(offset)都是成員自身大小的整數(shù)倍,如有需要,編譯器會在成員之間加上填充字節(jié)(internal adding) - (3). 結(jié)構(gòu)體的最終大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要,編譯器會在最末一個成員之后加上填充字節(jié)(trailing padding)
也就是說:若結(jié)構(gòu)體的總大小是最大基礎(chǔ)成員大小的整數(shù)倍,那么也就一定是其他任一基礎(chǔ)成員大小的整數(shù)倍,那么每個結(jié)構(gòu)體的起始地址就一定是其任一基礎(chǔ)成員大小的整數(shù)倍 ,這樣的話就保證了所有基礎(chǔ)類型數(shù)據(jù)、非基礎(chǔ)類型數(shù)據(jù)全部對齊。
[補充] (1)(2) 是為了保證內(nèi)存對齊,提高處理器訪問內(nèi)存的效率;(3) 是為了保證結(jié)構(gòu)體數(shù)組中的每個元素都能滿足對齊要求
舉個例子:
EX1
結(jié)構(gòu)體
struct SD
{
int i;
short si;
char c;
double d;
};
按照字節(jié)對齊原則,結(jié)構(gòu)體SD所占的存儲空間大小為 16 個字節(jié)
假設(shè)結(jié)構(gòu)體的首地址為0
EX2
結(jié)構(gòu)體數(shù)組
struct SDT
{
int i;
short si;
double d;
char c;
};
struct SDT sa[10];
按照字節(jié)對齊原則,結(jié)構(gòu)體SD所占的存儲空間大小為 24 個字節(jié)
假設(shè)結(jié)構(gòu)體數(shù)組的第一個數(shù)組元素sa[0]的首地址為0
補充概念 : 首地址 n 字節(jié)對齊原則
VC對結(jié)構(gòu)的存儲的特殊處理確實提高CPU存儲變量的速度,但是有時候也帶來了一些麻煩,我們也屏蔽掉變量默認的對齊方式,自己可以設(shè)定變量的對齊方式。
VC 中提供了#pragma pack(n)來設(shè)定變量以n字節(jié)對齊方式。n字節(jié)對齊就是說變量存放的起始地址的偏移量有兩種情況:第一、如果n大于等于該變量所占用的字節(jié)數(shù),那么偏移量必須滿足默認的對齊方式,第二、如果n小于該變量的類型所占用的字節(jié)數(shù),那么偏移量為n的倍數(shù),不用滿足默認的對齊方式。結(jié)構(gòu)的總大小也有個約束條件,分下面兩種情況:如果n大于所有成員變量類型所占用的字節(jié)數(shù),那么結(jié)構(gòu)的總大小必須為占用空間最大的變量占用的空間數(shù)的倍數(shù),否則必須為n的倍數(shù)。下面舉例說明其用法。
#pragma
pack(push) //保存對齊狀態(tài)
#pragma
pack(4)//設(shè)定為4字節(jié)對齊
struct test
{
char m1;
double m4;
int m3;
};
#pragma
pack(pop)//恢復(fù)對齊狀態(tài)
以上結(jié)構(gòu)的大小為16,下面分析其存儲情況,首先為m1分配空間,其偏移量為0,滿足我們自己設(shè)定的對齊方式(4字節(jié)對齊),m1占用1個字節(jié)。接著開始為m4分配空間,這時其偏移量為1,需要補足3個字節(jié),這樣使偏移量滿足為n=4的倍數(shù)(因為sizeof(double)大于n),m4占用8個字節(jié)。接著為m3分配空間,這時其偏移量為12,滿足為4的倍數(shù),m3占用4個字節(jié)。這時已經(jīng)為所有成員變量分配了空間,共分配了16個字節(jié),滿足為n的倍數(shù)。如果把上面的#pragma pack(4)改為#pragma pack(16),那么我們可以得到結(jié)構(gòu)的>大小為24。