C語(yǔ)言編程學(xué)習(xí)之意向不到的內(nèi)存對(duì)齊問(wèn)題

C語(yǔ)言是面向過(guò)程的,而C++是面向?qū)ο蟮?/p>

C和C++的區(qū)別:

C是一個(gè)結(jié)構(gòu)化語(yǔ)言,它的重點(diǎn)在于算法和數(shù)據(jù)結(jié)構(gòu)。C程序的設(shè)計(jì)首要考慮的是如何通過(guò)一個(gè)過(guò)程,對(duì)輸入(或環(huán)境條件)進(jìn)行運(yùn)算處理得到輸出(或?qū)崿F(xiàn)過(guò)程(事務(wù))控制)。

C++,首要考慮的是如何構(gòu)造一個(gè)對(duì)象模型,讓這個(gè)模型能夠契合與之對(duì)應(yīng)的問(wèn)題域,這樣就可以通過(guò)獲取對(duì)象的狀態(tài)信息得到輸出或?qū)崿F(xiàn)過(guò)程(事務(wù))控制。 所以C與C++的最大區(qū)別在于它們的用于解決問(wèn)題的思想方法不一樣。之所以說(shuō)C++比C更先進(jìn),是因?yàn)椤?設(shè)計(jì)這個(gè)概念已經(jīng)被融入到C++之中 ”。

C與C++的最大區(qū)別:在于它們的用于解決問(wèn)題的思想方法不一樣。之所以說(shuō)C++比C更先進(jìn),是因?yàn)椤?設(shè)計(jì)這個(gè)概念已經(jīng)被融入到C++之中 ”,而就語(yǔ)言本身而言,在C中更多的是算法的概念。那么是不是C就不重要了,錯(cuò)!算法是程序設(shè)計(jì)的基礎(chǔ),好的設(shè)計(jì)如果沒(méi)有好的算法,一樣不行。而且,“C加上好的設(shè)計(jì)”也能寫(xiě)出非常好的東西。

什么是內(nèi)存對(duì)齊?看下面的結(jié)構(gòu)體

小編推薦一個(gè)學(xué)C語(yǔ)言/C++的學(xué)習(xí)裙【 六九九,四七零,五九六 】,無(wú)論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來(lái)了解一起進(jìn)步一起學(xué)習(xí)!裙內(nèi)有開(kāi)發(fā)工具,很多干貨和技術(shù)資料分享!

struct StudentTest{

char ch1;

short s;

char ch2;

int i;

};

假設(shè)這個(gè)結(jié)構(gòu)的成員在內(nèi)存中是緊湊排列的,

假設(shè)ch1的地址是0x00000000,那么s的地址就應(yīng)該是0x00000001,

ch2的地址應(yīng)該0x00000003,i的地址應(yīng)該是0x00000004

實(shí)際情況如何,寫(xiě)一個(gè)小程序來(lái)測(cè)試下:

struct StudentTest{

char ch1;

short s;

char ch2;

int i;

};

int main(void)

{

struct StudentTest a;

printf("ch1 %p,s %p,ch2 %p,i %p ",

(unsigned int)(void*)&a.ch1-(unsigned int)(void*)&a,

(unsigned int)(void*)&a.s-(unsigned int)(void*)&a,

(unsigned int)(void*)&a.ch2-(unsigned int)(void*)&a,

(unsigned int)(void*)&a.i-(unsigned int)(void*)&a

);

return 0;

}

實(shí)際輸出如下:

小編推薦一個(gè)學(xué)C語(yǔ)言/C++的學(xué)習(xí)裙【 六九九,四七零,五九六 】,無(wú)論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來(lái)了解一起進(jìn)步一起學(xué)習(xí)!裙內(nèi)有開(kāi)發(fā)工具,很多干貨和技術(shù)資料分享!

這就是內(nèi)存對(duì)齊導(dǎo)致的問(wèn)題。

為什么會(huì)有內(nèi)存對(duì)齊?

字、雙字和四字在自然邊界上方是不需要對(duì)齊的,(對(duì)于字,雙字和四字來(lái)說(shuō)自然邊界分別是偶數(shù)地址,能被4整除的地址和能被8整除的地址)。為了提高程序的性能,數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能的在自然邊界上對(duì)齊。原因在于,為了訪問(wèn)未對(duì)齊的內(nèi)存,處理器需要做兩次內(nèi)存訪問(wèn)。而對(duì)齊的內(nèi)存只需做一次內(nèi)存訪問(wèn)。 一個(gè)字或雙字操作數(shù)跨越了4 字節(jié)邊界,或者一個(gè)四字操作數(shù)跨越了8 字節(jié)邊界,被認(rèn)為是未對(duì)齊的,從而需要兩次總線周期來(lái)訪問(wèn)內(nèi)存。一個(gè)字起始地址是奇數(shù)但卻沒(méi)有跨越字邊界被認(rèn)為是對(duì)齊的,能夠在一個(gè)總線周期中被訪問(wèn)。某些操作雙四字的指令需要內(nèi)存操作數(shù)在自然邊界上對(duì)齊。如果操作數(shù)沒(méi)有對(duì)齊,這些指令將會(huì)產(chǎn)生一個(gè)通用保護(hù)異常。雙四字的自然邊界是能夠被16 整除的地址。其他的操作雙四字的指令允許未對(duì)齊的訪問(wèn)(不會(huì)產(chǎn)生通用保護(hù)異常),然而,需要額外的內(nèi)存總線周期來(lái)訪問(wèn)內(nèi)存中未對(duì)齊的數(shù)據(jù)。

缺省情況下,編譯器默認(rèn)將結(jié)構(gòu)、棧中的成員數(shù)據(jù)進(jìn)行內(nèi)存對(duì)齊。因此,上面的程序輸出就變成了:

c1 00000000, s 00000002, c2 00000004, i 00000008。

編譯器將未對(duì)齊的成員向后移,將每一個(gè)都成員對(duì)齊到自然邊界上,從而也導(dǎo)致了整個(gè)結(jié)構(gòu)的尺寸變大。盡管會(huì)犧牲一點(diǎn)空間(成員之間有部分內(nèi)存空閑),但提高了性能。也正是這個(gè)原因,我們不可以斷言sizeof(TestStruct1)的結(jié)果為8。在這個(gè)例子中,sizeof(TestStruct1)的結(jié)果為12。

如何避免內(nèi)存對(duì)齊影響?

那么,能不能既達(dá)到提高性能的目的,又能節(jié)約一點(diǎn)空間呢?有一點(diǎn)小技巧可以使用。比如我們可以將上面的結(jié)構(gòu)改成:

struct StudentTest2

{

char c1;

char c2;

short s;

int i;

};

這樣一來(lái),每個(gè)成員都對(duì)齊在其自然邊界上,從而避免了編譯器自動(dòng)對(duì)齊。在這個(gè)例子中,sizeof(StudentTest2)的值為8。這個(gè)技巧有一個(gè)重要的作用,尤其是這個(gè)結(jié)構(gòu)作為API的一部分提供給第三方開(kāi)發(fā)使用的時(shí)候。第三方開(kāi)發(fā)者可能將編譯器的默認(rèn)對(duì)齊選項(xiàng)改變,從而造成這個(gè)結(jié)構(gòu)在你的發(fā)行的DLL 中使用某種對(duì)齊方式,而在第三方開(kāi)發(fā)者哪里卻使用另外一種對(duì)齊方式。這將會(huì)導(dǎo)致重大問(wèn)題。比如,StudentTest結(jié)構(gòu),我們的DLL 使用默認(rèn)對(duì)齊選項(xiàng),對(duì)齊為c1 00000000, s 00000002, c2 00000004, i 00000008,同時(shí)sizeof(StudentTest)的值為12。

而第三方將對(duì)齊選項(xiàng)關(guān)閉,導(dǎo)致

c1 00000000, s 00000001, c2 00000003, i 00000004,同時(shí)sizeof(StudentTest)的值為8。

除此之外我們還可以利用#pragma pack()來(lái)改變編譯器的默認(rèn)對(duì)齊方式。

使用指令#pragma pack (n),編譯器將按照n 個(gè)字節(jié)對(duì)齊。

使用指令#pragma pack (),編譯器將取消自定義字節(jié)對(duì)齊方式。

在#pragma pack (n)和#pragma pack ()之間的代碼按n 個(gè)字節(jié)對(duì)齊。

但是,成員對(duì)齊有一個(gè)重要的條件,即每個(gè)成員按自己的方式對(duì)齊.也就是說(shuō)雖然指定了按n 字節(jié)對(duì)齊,但并不是所有的成員都是以n 字節(jié)對(duì)齊。其對(duì)齊的規(guī)則是,每個(gè)成員按其類型的對(duì)齊參數(shù)(通常是這個(gè)類型的大小)和指定對(duì)齊參數(shù)(這里是n 字節(jié))中較小的一個(gè)對(duì)齊,即:

min( n, sizeof( item )) 。并且結(jié)構(gòu)的長(zhǎng)度必須為所用過(guò)的所有對(duì)齊參數(shù)的整數(shù)倍,不夠就補(bǔ)空字節(jié)??慈缦吕樱?/p>

#pragma pack(8)

struct TestStruct4{

char a;

long b;

};

struct TestStruct5{

char c;

TestStruct4 d;

long long e;

};

#pragma pack()

A),sizeof(TestStruct4) = ?

B), TestStruct5 的c 后面空了幾個(gè)字節(jié)接著是d?

TestStruct4 中,成員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(TestStruct4)應(yīng)該為8;

TestStruct5 中,c 和TestStruct4 中的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è), TestStruct4 的就是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ì)齊)整除.這樣,一共使用了24 個(gè)字節(jié).內(nèi)存布局如下(*表示空閑內(nèi)存,1 表示使用內(nèi)存。單位為1byte):

a b

TestStruct4 的內(nèi)存布局:1***, 1111,

c TestStruct4.a TestStruct4.b d

TestStruct5 的內(nèi)存布局: 1***, 1***, 1111, ****, 11111111

這里有三點(diǎn)很重要:

首先,每個(gè)成員分別按自己的方式對(duì)齊,并能最小化長(zhǎng)度。

其次,復(fù)雜類型(如結(jié)構(gòu))的默認(rèn)對(duì)齊方式是它最長(zhǎng)的成員的對(duì)齊方式,這樣在成員是復(fù)雜類型時(shí),可以最小化長(zhǎng)度。然后,對(duì)齊后的長(zhǎng)度必須是成員中最大的對(duì)齊參數(shù)的整數(shù)倍,這樣在處理數(shù)組時(shí)可以保證每一項(xiàng)都邊界對(duì)齊。

補(bǔ)充一下,對(duì)于數(shù)組,比如:char a[3];它的對(duì)齊方式和分別寫(xiě)3 個(gè)char 是一樣的.也就是說(shuō)

它還是按1 個(gè)字節(jié)對(duì)齊.如果寫(xiě): typedef char Array3[3];Array3 這種類型的對(duì)齊方式還是按1個(gè)字節(jié)對(duì)齊,而不是按它的長(zhǎng)度。但是不論類型是什么,對(duì)齊的邊界一定是1,2,4,8,16,32,64....中的一個(gè)。

另外,注意別的#pragma pack 的其他用法:

#pragma pack(push) //保存當(dāng)前對(duì)其方式到packing stack

#pragma pack(push,n) 等效于

#pragma pack(push)

#pragma pack(n) //n=1,2,4,8,16 保存當(dāng)前對(duì)齊方式,設(shè)置按n 字節(jié)對(duì)齊。

#pragma pack(pop) //packing stack 出棧,并將對(duì)其方式設(shè)置為出棧的對(duì)齊

小編推薦一個(gè)學(xué)C語(yǔ)言/C++的學(xué)習(xí)裙【 六九九,四七零,五九六 】,無(wú)論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來(lái)了解一起進(jìn)步一起學(xué)習(xí)!裙內(nèi)有開(kāi)發(fā)工具,很多干貨和技術(shù)資料分享!

這些是C/C++能做的

服務(wù)器開(kāi)發(fā)工程師、人工智能、云計(jì)算工程師、信息安全(黑客反黑客)、大數(shù)據(jù) 、數(shù)據(jù)平臺(tái)、嵌入式工程師、流媒體服務(wù)器、數(shù)據(jù)控解、圖像處理、音頻視頻開(kāi)發(fā)工程師、游戲服務(wù)器、分布式系統(tǒng)、游戲輔助等

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容