C++提供了指令來設(shè)置對(duì)齊方式。一個(gè)是pack pragma,該指令用來設(shè)置結(jié)構(gòu)成員的對(duì)齊;另一個(gè)是align,用來設(shè)置整個(gè)類型的對(duì)齊。下面我們看一下這兩個(gè)指令是如何影響變量在內(nèi)存的存儲(chǔ)的。
1、pack pragma
pack pragma設(shè)置了struct、union或class中各成員的對(duì)齊方式,結(jié)構(gòu)成員對(duì)齊指的是成員相對(duì)于起始地址的偏移量。該指令基本用法如下:
#pragma pack(n)
它
指定了結(jié)構(gòu)成員按n(1,2,4,8,16)字節(jié)對(duì)齊,如果未指定n,則恢復(fù)成默認(rèn)值。需要注意的是,它并不是指結(jié)構(gòu)體中的每個(gè)成員都要按n對(duì)齊,而是按
照每個(gè)成員的大小和n相比較小的值對(duì)齊。下面引用MSDN中C++ Preprocessor Reference部分關(guān)于pack指令的說明:
n (optional)
Specifies the value, in bytes, to be used for packing. The default value for n is 8. Valid values are 1, 2, 4, 8, and 16. The alignment of a member will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.
即成員member的對(duì)齊值 align of member = min( pack setting value, sizeof(member) )
請(qǐng)看下面示例代碼:
#include <iostream>
using namespace std;
#pragma pack(show) //顯示當(dāng)前結(jié)構(gòu)成員對(duì)齊設(shè)置

#pragma pack(8)
struct A
...{
int n;
char c;
short s;
};
struct B
...{
char c;
int n;
short s;
};
#pragma pack()
int _tmain(int argc, _TCHAR* argv[])
...{
A a;
B b;
memset( &a, 0, sizeof(A) );
memset( &b, 0, sizeof(B) );
a.c = '1';
a.n = 2;
a.s = 3;
b.c = '1';
b.n = 2;
b.s = 3;
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
return 0;
}
筆者的測(cè)試環(huán)境為x86體系32位計(jì)算機(jī) win2000操作系統(tǒng),VS2003編譯器。
編譯器默認(rèn)的成員對(duì)齊值是8字節(jié),通過#pragma pack(show)指令,編譯的時(shí)候在輸出欄會(huì)限制默認(rèn)對(duì)齊值。以上程序運(yùn)行完通過調(diào)試的內(nèi)存察看功能得到a和b的內(nèi)存存儲(chǔ)區(qū)域如下:
a的存儲(chǔ)區(qū)域:0x0012FED0 02 00 00 00 31 00 03 00
b的存儲(chǔ)區(qū)域:0x0012FEBC 31 00 00 00 02 00 00 00 03 00 00 00
最前面的4字節(jié)整數(shù)是變量的起始地址,后面是變量的整個(gè)存儲(chǔ)區(qū)域。
現(xiàn)在我們按照 align of member = min( pack setting value, sizeof(member) )的公式分析一下a和b的存儲(chǔ)。
a 的第一個(gè)成員n為int,大小為4,align of a.n = min(8,sizeof(int) ),對(duì)齊值為4。第一個(gè)成員相對(duì)于結(jié)構(gòu)體起始地址從0偏移開始,前四個(gè)字節(jié)02 00 00 00即為n的存儲(chǔ)區(qū)域,因?yàn)閤86是Little Endian(低字節(jié)在前)的字節(jié)順序,所以第一字節(jié)是2,后面三個(gè)字節(jié)0,我們通常寫成0x00000002;
a的第二個(gè)成員c為char,大小為1,align of a.c=min(8,sizeof(char)),對(duì)齊值為1。c緊接著a后面存儲(chǔ)從偏移4開始,滿足1字節(jié)對(duì)齊的要求。它的值為'1',ASCII碼為0x31,共一個(gè)字節(jié)31;
a的第三個(gè)成員為short,大小為2,align of a.s=min(8,sizeof(short)),對(duì)齊值為2。如果緊接第二個(gè)成員從偏移5開始存儲(chǔ)就不滿足2字節(jié)對(duì)齊,因此跳過1個(gè)字節(jié),從偏移6字節(jié)的地方開始存儲(chǔ),即最后兩個(gè)字節(jié)03 00;
b的第一個(gè)成員c為char,大小為1,align of a.c=min(8,sizeof(char)),對(duì)齊值為1。第一個(gè)成員從偏移起始地址0字節(jié)開始存儲(chǔ),它的值為'1',ASCII碼為0x31,共一個(gè)字節(jié)31;
b 的第二個(gè)成員n為int,大小為4,align of a.n = min(8,sizeof(int) ),對(duì)齊值為4。如果緊接第二個(gè)成員后面從偏移1開始存儲(chǔ)就不能4字節(jié)對(duì)齊,因此跳過3個(gè)字節(jié),從偏移4字節(jié)的地方開始存儲(chǔ),即第5-8的四個(gè)字節(jié)02 00 00 00;
b的第三個(gè)成員為short,大小為2,align of a.s=min(8,sizeof(short)),對(duì)齊值為2。緊接第二個(gè)成員從偏移8字節(jié)的地方開始存儲(chǔ),即9-10兩個(gè)字節(jié)03 00;
這時(shí)有人可能要問,b為什么最后多了兩個(gè)字節(jié)00 00呢?這就是我們下面要講的,整個(gè)結(jié)構(gòu)體的對(duì)齊。
2、align指令
align指令可以用于設(shè)置各種內(nèi)置類型、自定義類型如struct、union或class的的對(duì)齊方式。指令格式為: __declspec(align( # )) ,#是對(duì)齊值,取值為2的1次方至2的8192次方。在聲明自定義類型或內(nèi)置變量時(shí),如果指定了對(duì)齊值,則對(duì)應(yīng)變量的起始地址必須是該值的整數(shù)倍。除此外,它還會(huì)影響結(jié)構(gòu)體的大小。下面引用兩段MSDN關(guān)于align的描述:
Without __declspec(align( # )) , Visual C++ aligns data on natural boundaries based on the size of the data, for example 4-byte integers on 4-byte boundaries and 8-byte doubles on 8-byte boundaries. Data in classes or structures is aligned within the class or structure at the minimum of its natural alignment and the current packing setting (from #pragma pack or the /Zp compiler option).
從這段可以看出,如果沒有設(shè)置align(#)值,變量x按照sizeof(x)來對(duì)齊起始地址。類或結(jié)構(gòu)體內(nèi)的成員在類或結(jié)構(gòu)體內(nèi)部按照min( pack setting value,sizeof(member))來對(duì)齊。這個(gè)我們?cè)趐ack指令部分已經(jīng)分析過。
The sizeof
value for any structure is the offset of the final member, plus that
member's size, rounded up to the nearest multiple of the largest member
alignment value or the whole structure alignment value, whichever is
greater.
從這段可以看出,align(#)指令會(huì)影響結(jié)構(gòu)體或類的大小。總結(jié)公式為:
sizeof(structure) = (結(jié)構(gòu)體最后一個(gè)成員的偏移 + sizeof(結(jié)構(gòu)體最后一個(gè)成員) ) 上取整 ( n* max( 結(jié)構(gòu)體各成員的對(duì)齊值,align(#)設(shè)置的值 ) ); 其中n為正整數(shù) 。
根據(jù)該公式我們分析一下b為什么后面會(huì)多兩個(gè)填充字節(jié)0。
b的最后一個(gè)成s偏移為8,大小為2,b中各成員對(duì)齊值最大的為4,因?yàn)槲丛O(shè)置align(#),所以上取整的數(shù)值為4n。8+2按4的倍數(shù)上取整為12。因此后面需要填充兩個(gè)字節(jié),這樣才能使sizeof(b) == 12。
下面以一代碼來說明align(#)指令的用法:
#include <iostream>
using namespace std;
#define CACHE_LINE 32
#define CACHE_ALIGN __declspec(align(CACHE_LINE))
#pragma pack(8)
struct CACHE_ALIGN S1
...{
int a, b, c, d;
};
struct S3
...{
struct S1 s1;
int a;
};
#pragma pack()
int _tmain(int argc, _TCHAR* argv[])
...{
CACHE_ALIGN int i = 2;
cout << sizeof(S1) << endl;
cout << sizeof(S3) << endl;
return 0;
}

運(yùn)行程序輸出32和64,按公式sizeof(structure) = (結(jié)構(gòu)體最后一個(gè)成員的偏移 + sizeof(結(jié)構(gòu)體最后一個(gè)成員) ) 上取整 ( n* max( 結(jié)構(gòu)體各成員的對(duì)齊值,align(#)設(shè)置的值 ) )分析:
sizeof(S1) = (12+4) 上取整 ( n * max( 4, 32 ) )
sizeof(S1) = (16) 上取整 ( 32 )
sizeof(S1) = 32
S3的大小留待大家練練手。


...{