计算机大端模式和小端模式 内存对齐问题(sizeof)
目錄(?)[+]
一、大端模式和小端模式的起源
? ? ? ? 關(guān)于大端小端名詞的由來(lái),有一個(gè)有趣的故事,來(lái)自于Jonathan Swift的《格利佛游記》:Lilliput和Blefuscu這兩個(gè)強(qiáng)國(guó)在過(guò)去的36個(gè)月中一直在苦戰(zhàn)。戰(zhàn)爭(zhēng)的原因:大家都知道,吃雞蛋的時(shí)候,原始的方法是打破雞蛋較大的一端,可以那時(shí)的皇帝的祖父由于小時(shí)侯吃雞蛋,按這種方法把手指弄破了,因此他的父親,就下令,命令所有的子民吃雞蛋的時(shí)候,必須先打破雞蛋較小的一端,違令者重罰。然后老百姓對(duì)此法令極為反感,期間發(fā)生了多次叛亂,其中一個(gè)皇帝因此送命,另一個(gè)丟了王位,產(chǎn)生叛亂的原因就是另一個(gè)國(guó)家Blefuscu的國(guó)王大臣煽動(dòng)起來(lái)的,叛亂平息后,就逃到這個(gè)帝國(guó)避難。據(jù)估計(jì),先后幾次有11000余人情愿死也不肯去打破雞蛋較小的端吃雞蛋。這個(gè)其實(shí)諷刺當(dāng)時(shí)英國(guó)和法國(guó)之間持續(xù)的沖突。Danny Cohen一位網(wǎng)絡(luò)協(xié)議的開(kāi)創(chuàng)者,第一次使用這兩個(gè)術(shù)語(yǔ)指代字節(jié)順序,后來(lái)就被大家廣泛接受。?
二、什么是大端和小端
? ? ? ? Big-Endian和Little-Endian的定義如下:1) Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。
2) Big-Endian就是高位字節(jié)排放在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端。
舉一個(gè)例子,比如數(shù)字0x12 34 56 78在內(nèi)存中的表示形式為:
1)大端模式:
低地址 -----------------> 高地址0x12 ?| ?0x34 ?| ?0x56 ?| ?0x78
2)小端模式:
低地址 ------------------> 高地址0x78 ?| ?0x56 ?| ?0x34 ?| ?0x12
可見(jiàn),大端模式和字符串的存儲(chǔ)模式類似。
3)下面是兩個(gè)具體例子:
16bit寬的數(shù)0x1234在Little-endian模式(以及Big-endian模式)CPU內(nèi)存中的存放方式(假設(shè)從地址0x4000開(kāi)始存放)為:| 內(nèi)存地址 | 小端模式存放內(nèi)容 | 大端模式存放內(nèi)容 |
| 0x4000 | 0x34 | 0x12 |
| 0x4001 | 0x12 | 0x34 |
32bit寬的數(shù)0x12345678在Little-endian模式以及Big-endian模式)CPU內(nèi)存中的存放方式(假設(shè)從地址0x4000開(kāi)始存放)為:
| 內(nèi)存地址 | 小端模式存放內(nèi)容 | 大端模式存放內(nèi)容 |
| 0x4000 | 0x78 | 0x12 |
| 0x4001 | 0x56 | 0x34 |
| 0x4002 | 0x34 | 0x56 |
| 0x4003 | 0x12 | 0x78 |
?4)大端小端沒(méi)有誰(shuí)優(yōu)誰(shuí)劣,各自優(yōu)勢(shì)便是對(duì)方劣勢(shì):
小端模式 :強(qiáng)制轉(zhuǎn)換數(shù)據(jù)不需要調(diào)整字節(jié)內(nèi)容,1、2、4字節(jié)的存儲(chǔ)方式一樣。
大端模式 :符號(hào)位的判定固定為第一個(gè)字節(jié),容易判斷正負(fù)。
三、數(shù)組在大端小端情況下的存儲(chǔ):
以u(píng)nsigned int value = 0x12345678為例,分別看看在兩種字節(jié)序下其存儲(chǔ)情況,我們可以用unsigned char buf[4]來(lái)表示value:Big-Endian: 低地址存放高位,如下:
高地址
? ? ? ? ---------------
? ? ? ? buf[3] (0x78) -- 低位
? ? ? ? buf[2] (0x56)
? ? ? ? buf[1] (0x34)
? ? ? ? buf[0] (0x12) -- 高位
? ? ? ? ---------------
? ? ? ? 低地址
Little-Endian: 低地址存放低位,如下:
高地址
? ? ? ? ---------------
? ? ? ? buf[3] (0x12) -- 高位
? ? ? ? buf[2] (0x34)
? ? ? ? buf[1] (0x56)
? ? ? ? buf[0] (0x78) -- 低位
? ? ? ? --------------
低地址
四、為什么會(huì)有大小端模式之分呢?
? ? ? 這是因?yàn)樵谟?jì)算機(jī)系統(tǒng)中,我們是以字節(jié)為單位的,每個(gè)地址單元都對(duì)應(yīng)著一個(gè)字節(jié),一個(gè)字節(jié)為8bit。但是在C語(yǔ)言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對(duì)于位數(shù)大于8位的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個(gè)字節(jié),那么必然存在著一個(gè)如果將多個(gè)字節(jié)安排的問(wèn)題。因此就導(dǎo)致了大端存儲(chǔ)模式和小端存儲(chǔ)模式。例如一個(gè)16bit的short型x,在內(nèi)存中的地址為0x0010,x的值為0x1122,那么0x11為高字節(jié),0x22為低字節(jié)。對(duì)于大端模式,就將0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,剛好相反。我們常用的X86結(jié)構(gòu)是小端模式,而KEIL C51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬件來(lái)選擇是大端模式還是小端模式。
五、如何判斷機(jī)器的字節(jié)序
可以編寫一個(gè)小的測(cè)試程序來(lái)判斷機(jī)器的字節(jié)序:
[cpp] view plaincopy print?聯(lián)合體union的存放順序是所有成員都從低地址開(kāi)始存放,利用該特性可以輕松地獲得了CPU對(duì)內(nèi)存采用Little-endian還是Big-endian模式讀寫
[cpp] view plaincopy print?內(nèi)存對(duì)齊問(wèn)題
怎么判斷內(nèi)存對(duì)齊規(guī)則,sizeof的結(jié)果怎么來(lái)的,請(qǐng)牢記以下3條原則:(在沒(méi)有#pragma pack宏的情況下,務(wù)必看完最后一行)
1:數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說(shuō)是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開(kāi)始(比如int在32位機(jī)為4字節(jié),則要從4的整數(shù)倍地址開(kāi)始存儲(chǔ)。
2:結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開(kāi)始存儲(chǔ).(struct a里存有struct b,b里有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開(kāi)始存儲(chǔ).)
3:收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,.必須是其內(nèi)部最大成員的整數(shù)倍.不足的要補(bǔ)齊.
[cpp] view plaincopy print?結(jié)果是
48 24
ok,上面的全看明白了,內(nèi)存對(duì)齊基本過(guò)關(guān).
再講講#pragma pack().
在代碼前加一句#pragma pack(1),你會(huì)很高興的發(fā)現(xiàn),上面的代碼輸出為
32 16
bb是4+8+4=16,aa是2+4+8+2+16=32;
這不是理想中的沒(méi)有內(nèi)存對(duì)齊的世界嗎.沒(méi)錯(cuò),#pragmapack(1),告訴編譯器,所有的對(duì)齊都按照1的整數(shù)倍對(duì)齊,換句話說(shuō)就是沒(méi)有對(duì)齊規(guī)則.
明白了不?
那#pragma pack(2)的結(jié)果又是多少呢?對(duì)不起,5分鐘到了,自己去測(cè)試吧.
ps:Vc,Vs等編譯器默認(rèn)是#pragma pack(8),所以測(cè)試我們的規(guī)則會(huì)正常;注意gcc默認(rèn)是#pragma pack(4),并且gcc只支持1,2,4對(duì)齊。套用三原則里計(jì)算的對(duì)齊值是不能大于#pragma pack指定的n值。
內(nèi)存對(duì)齊二
VC對(duì)結(jié)構(gòu)的存儲(chǔ)的特殊處理確實(shí)提高CPU存儲(chǔ)變量的速度,但是有時(shí)候也帶來(lái)了一些麻煩,我們也屏蔽掉變量默認(rèn)的對(duì)齊方式,自己可以設(shè)定變量的對(duì)齊方式。VC 中提供了#pragma pack(n)來(lái)設(shè)定變量以n字節(jié)對(duì)齊方式。n字節(jié)對(duì)齊就是說(shuō)變量存放的起始地址的偏移量有兩種情況:
第一、如果n大于等于該變量所占用的字節(jié)數(shù),那么偏移量必須滿足默認(rèn)的對(duì)齊方式;
第二、如果n小于該變量的類型所占用的字節(jié)數(shù),那么偏移量為n的倍數(shù),不用滿足默認(rèn)的對(duì)齊方式。
結(jié)構(gòu)的總大小也有個(gè)約束條件,分下面兩種情況:如果n大于所有成員變量類型所占用的字節(jié)數(shù),那么結(jié)構(gòu)的總大小必須為占用空間最大的變量占用的空間數(shù)的倍數(shù);否則必須為n的倍數(shù)。下面舉例說(shuō)明其用法:
[cpp] view plaincopy print?以上結(jié)構(gòu)的大小為16,下面分析其存儲(chǔ)情況,首先為m1分配空間,其偏移量為0,滿足我們自己設(shè)定的對(duì)齊方式(4字節(jié)對(duì)齊),m1占用1個(gè)字節(jié)。接著開(kāi)始為 m4分配空間,這時(shí)其偏移量為1,需要補(bǔ)足3個(gè)字節(jié),這樣使偏移量滿足為n=4的倍數(shù)(因?yàn)閟izeof(double)大于n),m4占用8個(gè)字節(jié)。接著為m3分配空間,這時(shí)其偏移量為12,滿足為4的倍數(shù),m3占用4個(gè)字節(jié)。這時(shí)已經(jīng)為所有成員變量分配了空間,共分配了4+8+4=16個(gè)字節(jié),滿足為n的倍數(shù)。如果把上面的#pragma pack(4)改為#pragma pack(16),那么我們可以得到結(jié)構(gòu)的大小為24。
再看下面這個(gè)例子:
[cpp] view plaincopy print?成員對(duì)齊有一個(gè)重要的條件,即每個(gè)成員分別對(duì)齊.即每個(gè)成員按自己的方式對(duì)齊.
也就是說(shuō)上面雖然指定了按8字節(jié)對(duì)齊,但并不是所有的成員都是以8字節(jié)對(duì)齊.其對(duì)齊的規(guī)則是,每個(gè)成員按其類型的對(duì)齊參數(shù)(通常是這個(gè)類型的大小)和指定對(duì)齊參數(shù)(這里是8字節(jié))中較小的一個(gè)對(duì)齊.并且結(jié)構(gòu)的長(zhǎng)度必須為所用過(guò)的所有對(duì)齊參數(shù)的整數(shù)倍,不夠就補(bǔ)空字節(jié).
S1中,成員a是1字節(jié)默認(rèn)按1字節(jié)對(duì)齊,指定對(duì)齊參數(shù)為8,這兩個(gè)值中取1,a按1字節(jié)對(duì)齊;成員b是4個(gè)字節(jié),默認(rèn)是按4字節(jié)對(duì)齊,這時(shí)就按4字節(jié)對(duì)齊,所以sizeof(S1)應(yīng)該為8;
S2 中,c和S1中的a一樣,按1字節(jié)對(duì)齊,而d 是個(gè)結(jié)構(gòu),它是8個(gè)字節(jié),它按什么對(duì)齊呢?對(duì)于結(jié)構(gòu)來(lái)說(shuō),它的默認(rèn)對(duì)齊方式就是它的所有成員使用的對(duì)齊參數(shù)中最大的一個(gè),S1的就是4.所以,成員d就是 按4字節(jié)對(duì)齊.成員e是8個(gè)字節(jié),它是默認(rèn)按8字節(jié)對(duì)齊,和指定的一樣,所以它對(duì)到8字節(jié)的邊界上,這時(shí),已經(jīng)使用了12個(gè)字節(jié)了,所以又添加了4個(gè)字節(jié)的空,從第16個(gè)字節(jié)開(kāi)始放置成員e.這時(shí),長(zhǎng)度為24,已經(jīng)可以被8(成員e按8字節(jié)對(duì)齊)整除.這樣,sizeof(S2)為24個(gè)字節(jié).
這里有三點(diǎn)很重要:
1.每個(gè)成員分別按自己的方式對(duì)齊,并能最小化長(zhǎng)度。
2.復(fù)雜類型(如結(jié)構(gòu))的默認(rèn)對(duì)齊方式是它最長(zhǎng)的成員的對(duì)齊方式,這樣在成員是復(fù)雜類型時(shí),可以最小化長(zhǎng)度。
3.對(duì)齊后的長(zhǎng)度必須是成員中最大的對(duì)齊參數(shù)的整數(shù)倍,這樣在處理數(shù)組時(shí)可以保證每一項(xiàng)都邊界對(duì)齊。
?
轉(zhuǎn)http://blog.csdn.NET/ce123_zhouwei/article/details/6971544
http://blog.csdn.Net/hairetz/article/details/4084088
http://www.cnblogs.com/cpoint/p/3369456.html
?未整理進(jìn)來(lái) http://blog.csdn.net/fanfank/article/details/12175585
總結(jié)
以上是生活随笔為你收集整理的计算机大端模式和小端模式 内存对齐问题(sizeof)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。