今天這里聊聊如何設(shè)計結(jié)構(gòu)體,注意本文不介紹在C++中結(jié)構(gòu)體和類具體有什么區(qū)別纫版,本文所說的結(jié)構(gòu)體是指只有數(shù)據(jù)字段不帶任何函數(shù)的那種結(jié)構(gòu)體其弊。
當(dāng)創(chuàng)建結(jié)構(gòu)體的實例時膀斋,結(jié)構(gòu)體的數(shù)據(jù)成員會按其聲明的順序連續(xù)存儲。然而糊识,這個聲明的順序也是有學(xué)問的摔蓝,順序不同結(jié)構(gòu)體的大小可能有很大差別,數(shù)據(jù)成員的訪問性能也可能會有很大區(qū)別哑梳!
這里涉及一個概念:內(nèi)存對齊。關(guān)于內(nèi)存對齊這里不深入討論鸠真,只是簡單介紹一下。
大多數(shù)編譯器會對齊數(shù)據(jù)成員锡垄,會以四舍五入地址方式來優(yōu)化數(shù)據(jù)的訪問祭隔,如下表所示。
這種內(nèi)存對齊可能會在成員大小混合的結(jié)構(gòu)體中產(chǎn)生未使用字節(jié)的空洞千贯。
例如:
struct S {
short int a; // 2字節(jié)
// 6個空洞
double b; // 8
int d; // 4
// 4個空洞
};
S ArrayOfStructures[100];
這里搔谴,在a和b之間有6個未使用的字節(jié)桩撮,因為b必須從一個能被8整除的地址開始。
最后還有4個未使用的字節(jié)空洞芜果。這樣做的原因是融师,數(shù)組中S的下一個實例必須從一個能被8整除的地址開始诬滩,以便將其b成員以8對齊。
然而疼鸟,如果改變一下結(jié)構(gòu)體中數(shù)據(jù)成員聲明的順序空镜,通過將最小的成員放在最后,未使用的字節(jié)數(shù)可以減少到2:
struct S {
double b; // 8
int d; // 4
short int a; // 2
// 2個空洞
};
S ArrayOfStructures[100];
這種重新排序使結(jié)構(gòu)體變小了8個字節(jié)张抄,那整個數(shù)組則變小了800個字節(jié)洼怔。
在此特性上,類和結(jié)構(gòu)體相同极谊。通過重新排序數(shù)據(jù)成員,結(jié)構(gòu)體對象和類對象通撤牵可以變得更小猜煮。如果類至少有一個虛成員函數(shù),則在第一個數(shù)據(jù)成員之前或最后一個成員之后會有一個指向虛函數(shù)表的指針王带。該指針在32位系統(tǒng)中為4字節(jié)辫秧,在64位系統(tǒng)中為8字節(jié)被丧。
如果不確定結(jié)構(gòu)體或它的每個成員有多大绪妹,可以使用sizeof操作符進行一些測試。sizeof操作符返回的值包括對象末尾的任何未使用的字節(jié)(內(nèi)存對齊后的字節(jié)數(shù))黄选。
也可以無腦使用緊湊型結(jié)構(gòu)體办陷?
還有一個知識點:
如果數(shù)據(jù)成員相對于結(jié)構(gòu)體或類開頭的偏移量小于128律歼,則訪問數(shù)據(jù)成員的代碼會更加緊湊险毁,因為該偏移量可以使用8位有符號的數(shù)字來表示。如果相對于結(jié)構(gòu)體或類的開頭的偏移量是128字節(jié)或更多畔况,那么偏移量必須表示為一個32位數(shù)字(指令集在8位到32位之間沒有偏移量)跷跪。例如:
struct S {
int a[100]; // 400
int b; // 4
int read() { return b; }
};
b成員的偏移量是400。任何通過指針或成員函數(shù)訪問b字段的代碼都需要將偏移量編碼為32位數(shù)字嵌戈。如果交換a和b,則兩者都可以通過編碼為8位有符號數(shù)字的偏移量來訪問宽档,或者根本不需要偏移量庵朝。
這會使代碼更緊湊,方便更有效地使用代碼緩存九府。因此,建議在結(jié)構(gòu)或類聲明中肺蔚,大數(shù)組和其他大對象排在最后儡羔,最常用的數(shù)據(jù)成員排在前面。如果不能在前128個字節(jié)內(nèi)包含所有數(shù)據(jù)成員仇冯,則將最常用的成員放在前128個字節(jié)中苛坚。
通過上面兩個小知識點可以使得將結(jié)構(gòu)體設(shè)計的更小色难,訪問數(shù)據(jù)成員的速度更快枷莉,但是這有時往往會犧牲一些可讀性,比如這種結(jié)構(gòu)體:
struct S {
int deskA;
double deskB;
bool deskC;
int chairA;
double chairB;
bool chairC;
};
可能這樣修改后結(jié)構(gòu)體會更醒恼辍:
struct S {
int deskA;
int chairA;
double deskB;
double chairB;
bool deskC;
bool chairC;
};
但是我們一般情況下貌似希望同類的字段放在一起宋渔,這樣代碼可讀性更高一些辜限,易于讀懂代碼。至于這種結(jié)構(gòu)體具體需不需要重新排序颗胡,那就需要大家自己權(quán)衡啦吩坝。
小總結(jié):
- 注意內(nèi)存對齊;
- 128是個檻弧呐,常用的數(shù)據(jù)成員可考慮放在前128字節(jié)中俘枫,不常用的或大的數(shù)據(jù)成員可考慮放在后面(好久之前看的某個國外的博客學(xué)習(xí)到的鸠蚪,但是具體出處找不到了)师溅;
- 注重性能優(yōu)化的同時也需要權(quán)衡一下代碼的可讀性险胰。
打完收工矿筝。