C++內(nèi)存對齊的實現(xiàn)
內(nèi)存對齊的基本原則:
- 結(jié)構(gòu)(struct/class)的內(nèi)置類型數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員的起始位置要從自身大小的整數(shù)倍開始存儲(特別注意64位機器的指針大小為8個字節(jié))。
- 如果一個結(jié)構(gòu)A里有結(jié)構(gòu)體成員B,則結(jié)構(gòu)體B要從其內(nèi)部"最寬基本類型成員”的整數(shù)倍地址開始存儲(如struct a里存有struct b,b里有char, int, double等元素,那b應(yīng)該從8的整數(shù)倍位置開始存儲)。
- 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體的有效對齊值的整數(shù)倍,結(jié)構(gòu)體的有效對齊值的確定:
- 當未明確指定時,以結(jié)構(gòu)體或結(jié)構(gòu)體所包含結(jié)構(gòu)體成員中最長的成員長度為其有效值。
- 當用#pragma pack(n)指定時,以n和結(jié)構(gòu)體中最長的成員的長度中較小者為其值。
- 當用__attribute__ ((packed))指定長度時,強制按照此值為結(jié)構(gòu)體的有效對齊值。
- 不管# pragma pack和__attribute__如何指定,結(jié)構(gòu)體內(nèi)部成員的自對齊仍然按照其自身的對齊值。
- union以結(jié)構(gòu)里面size最大元素為union的size,因為在某一時刻,union只有一個成員真正存儲于該地址。
空類/靜態(tài)成員
程序 1
class A{ }; int main() { ?? ?cout << sizeof(A) << endl;?? ?// 1 }
對于一個什么都沒有的空類,實際并不是空的,因為有默認的函數(shù),具體可以參考 (待填入網(wǎng)址),大小是 1,這是因為需要有一個地址,C++ 不允許兩個不同的對象有相同的地址,所以 C++ 中空的類和結(jié)構(gòu)體大小都是 1。
程序 2
class A{ ?? ?A(){} ?? ?~A(){} ?? ?void print() { printf("print()\n"); } ?? ?void foo() { printf("print()\n"); } ?? ?static void sprint() { printf("sprint()\n"); } }; int main() { ?? ?cout << sizeof(A) << endl;?? ?// 1 }
這個類的大小仍然是1,成員函數(shù)、靜態(tài)成員函數(shù)、靜態(tài)成員變量都是不占用類的內(nèi)存的,這是因為這些東西都是類的,而不是每個對象分別存儲。static變量就是存儲在全局靜態(tài)區(qū)。
需要注意的是,子類繼承空類后,子類如果有自己的數(shù)據(jù)成員,而空基類的1個字節(jié)并不會加到子類中去。
程序 3
class Empty {}; struct D : public Empty { int a; };
sizeof(D)為4。
再來看另一種情況,一個類包含一個空類對象數(shù)據(jù)成員,則空類對象的大小仍為1。
程序 4
class Empty {}; class HaveAnInt { int x; Empty e; }
在大多數(shù)編譯器中,你會發(fā)現(xiàn) sizeof(HaveAnInt) 輸出為8。這是由于,Empty類的大小雖然為1,然而為了內(nèi)存對齊,編譯器會為HaveAnInt額外加上一些字節(jié),使得HaveAnInt被放大到足夠又可以存放一個int。
內(nèi)置類型數(shù)據(jù)成員
程序 1
class Data { char c; int a; }; cout << sizeof(Data) << endl;
程序 2
class Data { char c; double a; }; cout << sizeof(Data) << endl;
顯然程序 1 輸出的結(jié)果為 8,程序 2 輸出的結(jié)果為 16 .
程序 1 最大的數(shù)據(jù)成員是4bytes,1+4=5,補齊為4的倍數(shù),也就是8。而程序 2 為8bytes,1+8=9,補齊為8的倍數(shù),也就是16。
程序 3
class Data { char c; int a; char d; }; cout << sizeof(Data) << endl;
程序 4
class Data { char c; char d; int a; }; cout << sizeof(Data) << endl;
程序 3 運行結(jié)果為 12,程序 4 運行結(jié)果為 8
class中的數(shù)據(jù)成員放入內(nèi)存的時候,內(nèi)存拿出一個內(nèi)存塊來,數(shù)據(jù)成員們排隊一個一個往里放,遇到太大的成員時,不是將其劈成兩半能放多少就放多少,而是等下一個內(nèi)存塊過來。這樣的話,就可以理解為什么程序 3 和程序 4 兩段代碼輸出結(jié)果不一樣了,因為程序 3 是
1 + (3) + 4 + 1 + (3) = 12,而程序 4 是1 + 1 + (2) + 4 = 8。括號中為補齊的bytes。
結(jié)構(gòu)體數(shù)據(jù)成員
在默認條件下,內(nèi)存對齊是以class中最大的那個基本類型為基準的,如果class中的數(shù)據(jù)成員包含其他class,則遞歸的取其中最大的基本類型來參與比較。
程序 1
class BigData { char array[33]; }; class Data { BigData bd; int integer; double d; }; cout << sizeof(BigData) << " " << sizeof(Data) << endl;
程序 2
class BigData { char array[33]; }; class Data { BigData bd; double d; }; cout << sizeof(BigData) << " " << sizeof(Data) << endl;
程序 1 和程序 2 運行結(jié)果均為:33 48
程序 1 和程序 2 中內(nèi)存對其的基準均為8字節(jié),BigData的大小均為33。在程序 1 中,BigData接下來是個int(4bytes),能夠放下,這時候內(nèi)存塊還剩3bytes,而接下來是個double(8bytes),放不下,所以要等下一個內(nèi)存快到來。因此,程序 1 的Data的size = 33 + 4 + (3) + 8 = 48,同理程序 2 應(yīng)該是
33 + (7) + 8 = 48。
程序 3
?class A { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?public: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ?double len; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ?char str[33]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?}; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?class B { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?public: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?A a; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?int b; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}; cout << sizeof(A) << " ?" << sizeof(B) << endl;
以上代碼輸出的結(jié)果為: 48 56
不同于程序 1 和程序 2 ,程序 3 中的class A實際會占用41字節(jié),但會發(fā)生8字節(jié)對齊,所以大小為48字節(jié)。對于class B,成員b的起始位置已發(fā)生8字節(jié)對齊,而class B整體還會發(fā)生8字節(jié)對齊,所以最終大小為56。
虛函數(shù)
C++ 的類中如果有虛函數(shù),類內(nèi)就會有一個虛函數(shù)表的指針 _vptr,指向自己的虛函數(shù)表,vptr 一般都是在類的最前邊(取決于編譯器的實現(xiàn))。
class A { public: A(){} virtual ~A(){} virtual void foo(){} virtual void print() {} };
由于只是存一個指向虛函數(shù)表的指針,所以不管有多少個虛函數(shù),都是 4 字節(jié)大?。?2位下,任何指針大小都是 4,64位下,任何指針大小都是 8),比如上面這個類 A,size 就是 4。
需要注意的是就是,對于沒有 override 的虛函數(shù),基類和子類中 _vptr 指向的虛函數(shù)表中,這個虛函數(shù)的地址是一樣的,也就是上邊的 foo() 函數(shù),而對于重寫了的或者默認重寫的析構(gòu)函數(shù)來說,_vptr 指向的虛函數(shù)表中,函數(shù)地址是不一樣的(當然兩個類的 _vptr 地址也是不一樣的,這是肯定的),這就能窺探到多態(tài)的實現(xiàn)了。
繼承
不同的編譯器對繼承后類的大小的計算方式不同,有的是先繼承后對齊,有的是先對齊后繼承。
class A { ? ? int i; ? ? char c1; } class B:public A { ? ? char c2; } class C:public B { ? ? char c3; }
sizeof(C)結(jié)果是多少呢,gcc和vs給出了不同的結(jié)果,分別是8、16。
- gcc中:C相當于把所有成員i、c1、c2、c3當作是在一個class內(nèi)部,(先繼承后對齊)
- vs中:對于A,對齊后其大小是8;對于B,c2加上對齊后的A的大小是9,對齊后就是12;對于C,c3加上對齊后的B大小是13,再對齊就是16 (先對齊后繼承)
內(nèi)存對齊的意義
- 效率原因:經(jīng)過內(nèi)存對齊之后,CPU的內(nèi)存訪問速度大大提升。
- 平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù),某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
比較兩個結(jié)構(gòu)體可以使用memcmp(void*, void*)嗎?
不可以,memcmp函數(shù)是逐個字節(jié)進行比較的,而struct存在內(nèi)存對齊,內(nèi)存對齊時補的字節(jié)內(nèi)容是垃圾值,所以無法比較。
到此這篇關(guān)于C++內(nèi)存對齊的實現(xiàn)的文章就介紹到這了,更多相關(guān)C++內(nèi)存對齊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用C語言中的time函數(shù)獲取系統(tǒng)時間
在C語言中可以使用time函數(shù)來獲取系統(tǒng)時間,以下對time函數(shù)進行了介紹,需要的朋友可以過來參考下2013-07-07在vs2010中,輸出當前文件路徑與源文件當前行號的解決方法
本篇文章是對在vs2010中,輸出當前文件路徑與源文件當前行號的解決方法進行了詳細的分析介紹,需要的朋友參考下2013-05-05C語言利用system調(diào)用系統(tǒng)命令行詳情
這篇文章主要介紹了C語言利用system調(diào)用系統(tǒng)命令行詳情,system就是調(diào)用系統(tǒng)命令行,輸入為字符串,然后把這個字符串輸出給命令行,讓命令行執(zhí)行。下文的具體內(nèi)容,需要的小伙伴可以參考一下2022-01-01Matlab利用遺傳算法GA求解非連續(xù)函數(shù)問題詳解
遺傳算法起源于對生物系統(tǒng)所進行的計算機模擬研究。其本質(zhì)是一種高效、并行、全局搜索的方法,能在搜索過程中自動獲取和積累有關(guān)搜索空間的知識,并自適應(yīng)地控制搜索過程以求得最佳解。本文將利用其求解非連續(xù)函數(shù)問題,需要的可以參考一下2022-09-09