吐槽
最近又去面試工作機會,筆者是獨立遊戲開發者,一直是傾向於多面手的角色(着重遊戲開發全過程,包括了美術,即工程方面的,筆者可是懷揣着拯救國內這個悲催的遊戲市場的夢想呢)。然並卵,面試官從不問我的項目經驗,因此在優化和算法上吃了不少虧,真的是應了“面試造航母,工作擰螺絲”這句話,不過,算法確實很重要,這應該也就是他們爲啥樂此不疲的利用這個來篩選面試者的原因吧。 扯遠了,還是先記下他們常提的“字節對齊”吧,謀生重要啊。
原理
在特定平臺特定讀取模式下,cpu並不會隨機讀取內存,比如讀取結構體數據時,會以結構體首地址作爲參考點,位移x*n(n爲處理字長,這個跟編譯指令裏設置字節對齊值不是一個概念),故一但出現讀取奇數地址且長度大於1的數據時,就可能會出現讀取一個數據會讀取多次的現象,通常,編譯器會對結構體的每個字段進行字節對齊,使其達到n,一次即可讀取,從而提高cpu讀取效率。
優缺點
優點:
- 提高讀取特定數據效率
- 運用得當,在默認情況下能節省空間
缺點:
- 佔用額外無用空間,這也是空間換時間的弊端
應用場景
編譯器所做的字節對齊優化,在特定場景中,我們並不期望有。比如在網絡協議模塊,由於這些協議字段經常會有變動,但如果按照默認對齊方式的話,程序修改會很麻煩。比如客戶端和服務端的對接會遇到問題,不同平臺很難確定讀取或是寫入的地址。因此,通常服務協議的結構體都不按默認對齊方式,你會看到有:
#pragma pack(push,1)
...
#pragma pack(pop)
這一對編譯指令,這樣修改協議會簡單很多。那爲什麼默認對齊字節不是1而是4呢?這樣不僅省空間,還能解決一些麻煩的問題。筆者在原理中已經提到了,在特定平臺特定的讀取模式下,這樣可能會降低cpu讀取內存的效率。
例子
先介紹三個概念:自身對齊值、指定對齊值、有效對齊值。
自身對齊值:數據類型本身的對齊值,例如char類型的自身對齊值是1,short類型是2;
指定對齊值:編譯器或程序員指定的對齊值,32位單片機的指定對齊值默認是4;
有效對齊值:自身對齊值和指定對齊值中較小的那個。
對於結構體
1、不但結構體的成員有有效對齊值,結構體本身也有對齊值,這主要是考慮結構體的數組,對於結構體或者類,要將其補齊爲其有效對齊值的整數倍。結構體的有效對齊值是其最大數據成員的自身對齊值;
2、存放成員的起始地址必須是該成員有效對齊值的整數倍。
具體看下下面的例子讀者應該就會明白。
#include<iostream>
using namespace std;
#pragma pack(push,1)//可爲1,2,4,8
struct s1
{
char c1;
char c2;
int x;
};
struct s2
{
char c1;
int x;
char c2;
};
struct s3
{
char c1;
int x;
short sx;
char c3;
};
int main()
{
char test;
cout << "s1:"<<sizeof(s1)<<endl;
cout << "s2:" << sizeof(s2) << endl;
cout << "s3:" << sizeof(s3) << endl;
system("pause");
return 0;
}
#pragma pack(pop)
當對齊字節爲1時:
當對齊字節爲4(默認)時: