剛剛完成一個文件的遷移程序,其中遇到了結(jié)構(gòu)體對齊的問題,所以拿出來說說,與各位博友們分享。
我的程序很簡單,就是把之前通過一個結(jié)構(gòu)體fwrite到文件A里的內(nèi)容讀出,然后轉(zhuǎn)給另一個結(jié)構(gòu)體保存。程序是簡單,但我擔(dān)心的是之前把結(jié)構(gòu)體fwrite到文件A的程序?qū)R結(jié)構(gòu)體規(guī)則是怎樣的?一定要知道它嗎? 當(dāng)然了,如果那個程序結(jié)構(gòu)體是按照1對齊寫入的,我的程序結(jié)構(gòu)體是按照4對齊讀入,那不就糟了!
這里我引入結(jié)構(gòu)體對齊的概念,也可以說是內(nèi)存對齊了。為什么要內(nèi)存對齊呢,就是方便CPU尋址了,具體原因大家要參考計算機體系結(jié)構(gòu)了。先看一個內(nèi)存對齊的例子:
struct example1{
char a;
double b;
long l;
};
struct example2{
char a;
long l;
double b;
};
大家算算結(jié)構(gòu)體大小,初次接觸的博友可能對答案有點驚訝,VC編譯, sizeof后結(jié)果分別是:24,16。 同樣是的結(jié)構(gòu)體,成員換了順序,大小就不同了。其實內(nèi)存對齊有個規(guī)則,只要知道了,就OK。那么以下5點是關(guān)鍵
1. 內(nèi)存對齊與編譯器設(shè)置有關(guān),首先要搞清編譯器這個默認值是多少
2. 如果不想編譯器默認的話,可以通過#pragma pack(n)來指定按照n對齊
3. 每個結(jié)構(gòu)體變量對齊,如果對齊參數(shù)n(編譯器默認或者通過pragma指定)大于該變量所占字節(jié)數(shù)(m),那么就按照m對齊,內(nèi)存偏移后的地址是m的倍數(shù),否則是按照n對齊,內(nèi)存偏移后的地址是n的倍數(shù)。也就是最小化長度規(guī)則
4. 結(jié)構(gòu)體總大小: 對齊后的長度必須是成員中最大的對齊參數(shù)的整數(shù)倍。最大對齊參數(shù)是從第三步得到的。
5. 補充:如果結(jié)構(gòu)體A中還要結(jié)構(gòu)體B,那么B的對齊方式是選它里面最長的成員的對齊方式
所以計算結(jié)構(gòu)體大小要走三步,首先確定是當(dāng)前程序按照幾對齊(參照1,2點),接著計算每個結(jié)構(gòu)體變量的大小和偏移(參照3,5),最后計算結(jié)構(gòu)體總大小(參照4)。
先算算example1吧,假設(shè)編譯器是以16對齊的
1.確定按照幾對齊: 16;
2.確定每個成員的偏移:a 占一個字節(jié),16>1, 按照1對齊,起始位置0,0%1 = 0,那么a就存在0位置;b占8個字節(jié),16>8,按照8對齊,起始位置就不能是1了,因為要按照8對齊,所以最近的偏移起始位置是8, 8%8 =0, 那么b就存在位置8-15的位置;l占4個字節(jié),16>4,按照4對齊,起始位置16, 16%4=0,那么l就存在位置16-19的位置。所以結(jié)構(gòu)體從0到19一共占用20個字節(jié)
3.結(jié)構(gòu)體總大小:成員中最大的對齊參數(shù)是b的8對齊,所以20%8!=0, 24剛好。
真的很搞!同理計算example2應(yīng)該是16;
再舉個結(jié)構(gòu)體嵌套的例子吧,
#pragma pack(push)
#pragma pack(8)
struct test1{
int a;
char b;
int c[20]
long l;
} ;
struct test2{
char a1;
char a2;
struct test1 t1;
double b1;
}
#pragma pack(pop)
先計算test1, 8對齊,a占用0-3,b占用4,c占用8-87,l占用88-91,一共92個字節(jié)。成員中最大的對齊參數(shù)是int了92%4=0;
再計算test2, a1z占用0,a2占用1,t1呢,4 % 4 (test1里面最長的成員的對齊方式) = 0, 4-95,b1占96到103;一共104個字節(jié),成員中最大的對齊參數(shù)是double了104%8=0; 所以是104.
那關(guān)于我文章開頭提到的那個文件轉(zhuǎn)換,我現(xiàn)在只要知道原始程序是按照什么對齊的,然后在新程序中指定按照幾對齊就可以了,哈哈!
擠時間寫的,有的地方有遺漏,請各位指正!