如果直接操作結構體成員是不會取到不期望的值
但是對於要求連續數據格式的時候需要考慮對齊的問題
例如通訊中的數據幀格式等 ,如 ip數據包等
#pragma pack(1)
struct tagStruct
{
...
}
t;
#pragma pack()
的方式來強制連續存放
其中前面 pack(1) 是指對齊邊界為 1
1。幾個結構體例子:
struct{
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;
sizeof( A)=6, sizeof( B)=8,為什么?
注:sizeof(short)=2,sizeof(long)=4
因為:“成員對齊有一個重要的條件,即每個成員按自己的方式對齊。其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這里默認是8字節)中較小的一個對齊。並且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空字節。”(引用)
結構體A中有3個short類型變量,各自以2字節對齊,結構體對齊參數按默認的8字節對齊,則a1,a2,a3都取2字節對齊,則sizeof(A)為6,其也是2的整數倍;
B中a1為4字節對齊,a2為2字節對齊,結構體默認對齊參數為8,則a1取4字節對齊,a2取2字節對齊,結構體大小6字節,6不為4的整數倍,補空字節,增到8時,符合所有條件,則sizeof(B)為8;
可以設置成對齊的
#pragma pack(1)
#pragma pack(push)
#pragma pack(1)
struct{
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;
#pragma pack(pop)
結果為sizeof( A)=6,sizeof( B)=6
************************
#pragma pack(8)
struct S1{
char a;
long b;
};
struct S2 {
char c;
struct S1 d;
long long e;
};
#pragma pack()
sizeof(S2)結果為24.
成員對齊有一個重要的條件,即每個成員分別對齊,即每個成員按自己的方式對齊。
也就是說上面雖然指定了按8字節對齊,但並不是所有的成員都是以8字節對齊。其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這里是8字節)中較小的一個對齊。並且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空字節。
S1中,成員a是1字節默認按1字節對齊,指定對齊參數為8,這兩個值中取1,a按1字節對齊;成員b是4個字節,默認是按4字節對齊,這時就按4字節對齊,所以sizeof(S1)應該為8;
S2 中,c和S1中的a一樣,按1字節對齊,而d 是個結構,它是8個字節,它按什么對齊呢?對於結構來說,它的默認對齊方式就是它的所有成員使用的對齊參數 中最大的一個,S1的就是4.所以,成員d就是 按4字節對齊。成員e是8個字節,它是默認按8字節對齊,和指定的一樣,所以它對到8字節的邊界上,這時,已經使用了12個字節了,所以又添加了4個字節 的空,從第16個字節開始放置成員e。這時,長度為24,已經可以被8(成員e按8字節對齊)整除.這樣, 一共使用了24個字節。
a b
S1的內存布局:1***, 1111,
c S1.a S1.b e
S2的內存布局:1***, 1***, 1111, ****11111111
這里有三點很重要:
1.每個成員分別按自己的方式對齊,並能最小化長度
2.復雜類型(如結構)的默認對齊方式是它最長的成員的對齊方式,這樣在成員是復雜類型時,可以最小化長度
3.對齊后的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時可以保證每一項都邊界對齊
補充一下,對於數組,比如:
char a[3];這種,它的對齊方式和分別寫3個char是一樣的。也就是說它還是按1個字節對齊。
如果寫: typedef char Array3[3];
Array3這種類型的對齊方式還是按1個字節對齊,而不是按它的長度。
不論類型是什么,對齊的邊界一定是1,2,4,8,16,32,64....中的一個。
/***********************/
字節對齊詳解
為什么要對齊?
現代計算機中內存空間都是按照byte划分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特定的內存地址訪問,這就需要各種類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
對 齊的作用和原因:各個硬件平台對存儲空間的處理上有很大的不同。一些平台對某些特定類型的 數據只能從某些特定地址開始存取。比如有些架構的CPU在訪問一個沒有進行對齊的變量的時候會發生錯誤,那么在這種架構下編程必須保證字節對齊。其他平台 可能沒有這種情況,但是最常見的是如果不按照適合其平台要求對數據存放進行對齊,會在存取效率上帶來損失。比如有些平台每次讀都是從偶地址開始,如果一個 int型(假設為32位系統)如果存放在偶地址開始的地方,那么一個讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要 2個讀周期,並對兩次讀出的結果的高低字節進行拼湊才能得到該32bit數 據。顯然在讀取效率上下降很多。
二。字節對齊對程序的影響:
先讓我們看幾個例子吧(32bit,x86環境,gcc編譯器):
設結構體如下定義:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
現在已知32位機器上各種數據類型的長度如下:
char:1(有符號無符號同)
short:2(有符號無符號同)
int:4(有符號無符號同)
long:4(有符號無符號同)
float:4 double:8
那么上面兩個結構大小如何呢?
結果是:
sizeof(strcut A)值為8
sizeof(struct B)的值卻是12
結構體A中包含了4字節長度的int一個,1字節長度的char一個和2字節長度的short型數據一個,B也一樣;按理說A,B大小應該都是7字節。
之所以出現上面的結果是因為編譯器要對數據成員在空間上進行對齊。上面是按照編譯器的默認設置進行對齊的結果,那么我們是不是可以改變編譯器的這種默認對齊設置呢,當然可以。例如:
#pragma pack (2) /*指定按2字節對齊*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
sizeof(struct C)值是8。
修改對齊值為1:
#pragma pack (1) /*指定按1字節對齊*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
sizeof(struct D)值為7。
后面我們再講解#pragma pack()的作用。
三。編譯器是按照什么樣的原則進行對齊的?
先讓我們看四個重要的基本概念:
1.數據類型自身的對齊值:對於char型數據,其自身對齊值為1,對於short型為2,對於int,float,double類型,其自身對齊值為4,單位字節。
2.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。
3.指定對齊值:#pragma pack (value)時的指定對齊值value。
4.數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
有 了這些值,我們就可以很方便的來討論具體數據結構的成員和其自身的對齊方式。有效對齊值N是最終用來決定數據存放地址方式的值,最重要。有效對齊N,就是 表示“對齊在N上”,也就是說該數據的“存放起始地址%N=0”。而數據結構中的數據變量都是按定義的先后順序來排放的。第一個數據變量的起始地址就是數 據結構的起始地址。結構體的成員變量要對齊排放,結構體本身也要根據自身的有效對齊值圓整(就是結構體成員變量占用總長度需要是對結構體有效對齊值的整數 倍,結合下面例子理解)。這樣就不能理解上面的幾個例子的值了。
例子分析:
分析例子B:
struct B
{
char b;
int a;
short c;
};
假 設B從地址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環境下,該值默認為4。第一個成員變量b的自身對齊值是1,比指定或者默認指定 對齊值4小,所以其有效對齊值為1,所以其存放地址0x0000符合0x0000%1=0。第二個成員變量a,其自身對齊值為4,所以有 效對齊值也為4,所以只能存放在起始地址為0x0004到0x0007這四個連續的字節空間中,符合0x0004%4=0,且緊靠第一個變量。第三個變量 c,自身對齊值為2,所以有效對齊值也是2,可以存放在0x0008到0x0009這兩個字節空間中,符合0x0008%2=0。所以從0x0000到 0x0009存放的都是B內容。再看數據結構B的自身對齊值為其變量中最大對齊值(這里是b)所以就是4,所以結構體的有效對齊值也是4。根據結構體圓整 的要求,0x0009到0x0000=10字節,(10+2)%4=0。所以0x0000A到0x000B也為結構體B所占用。故B從0x0000到 0x000B 共有12個字節,sizeof(struct B)=12;其實如果就這一個就來說它已將滿足字節對齊了, 因為它的起始地址是0,因此肯定是對齊的。之所以在后面補充2個字節,是因為編譯器為了實現結構數組的存取效率,試想如果我們定義了一個結構B的數組,那 么第一個結構起始地址是0沒有問題,但是第二個結構呢?按照數組的定義,數組中所有元素都是緊挨着的,如果我們不把結構的大小補充為4的整數倍,那么下一 個結構的起始地址將是0x0000A,這顯然不能滿足結構的地址對齊了,因此我們要把結構補充成有效對齊大小的整數倍。其實諸如:對於char型數據,其 自身對齊值為1,對於short型為2,對於int,float,double類型,其自身對齊值為4,這些已有類型的自身對齊值也是基於數組考慮的,只 是因為這些類型的長度已知了,所以他們的自身對齊值也就已知了。
同理,分析上面例子C:
#pragma pack (2) /*指定按2字節對齊*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
第 一個變量b的自身對齊值為1,指定對齊值為2,所以,其有效對齊值為1,假設C從0x0000開始,那么b存放在0x0000,符合 0x0000%1= 0;第二個變量,自身對齊值為4,指定對齊值為2,所以有效對齊值為2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個連續 字節中,符合0x0002%2=0。第三個變量c的自身對齊值為2,所以有效對齊值為2,順序存放在0x0006、0x0007中,符合 0x0006%2=0。所以從0x0000到0x00007共八字節存放的是C的變量。又C的自身對齊值為4,所以C的有效對齊值為2。又 8%2=0,C 只占用0x0000到0x0007的八個字節。所以sizeof(struct C)=8。
四。如何修改編譯器的默認對齊值?
1.在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++選項卡Category的Code Generation選項的Struct Member Alignment中修改,默認是8字節。
2.在編碼時,可以這樣動態修改:#pragma pack(value) 。注意:是pragma而不是progma.
五。針對字節對齊,我們在編程中如何考慮?
如 果在編程的時候要考慮節約空間的話,那么我們只需要假定結構的首地址是0,然后各個變量按照上面的原則進行排列即可,基本的原則就是把結構中的變量按照類 型大小,從小到大聲明,盡量減少中間的填補空間。還有一種就是為了以空間換取時間的效率,我們顯示的進行填補空間進行對齊,比如:有一種使用空間 換時間做法是顯式的插入reserved成員:
struct A{
char a;
char reserved[3];//使用空間換時間
int b;
}
reserved成員對我們的程序沒有什么意義,它只是起到填補空間以達到字節對齊的目的,當然即使不加這個成員通常編譯器也會給我們自動填補對齊,我們自己加上它只是起到顯式的提醒作用。
六。字節對齊可能帶來的隱患:
代碼中關於對齊的隱患,很多是隱式的。比如在強制類型轉換的時候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后兩句代碼,從奇數邊界去訪問unsignedshort型變量,顯然不符合對齊的規定。
在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個error,因為它們要求必須字節對齊。
七。如何查找與字節對齊方面的問題:
如果出現對齊或者賦值問題首先查看
1. 編譯器的big little端設置
2. 看這種體系本身是否支持非對齊訪問
3. 如果支持看設置了對齊與否,如果沒有則看訪問時需要加某些特殊的修飾來標志其特殊訪問操作。
ARM下的對齊處理
from DUI0067D_ADS1_2_CompLib
3.13 type qulifiers
有部分摘自ARM編譯器文檔對齊部分
對齊的使用:
1.__align(num)
這個用於修改最高級別對象的字節邊界。在匯編中使用LDRD或者STRD時 就要用到此命令__align(8)進行修飾限制。來保證數據對象是相應 對齊。 這個修飾對象的命令最大是8個字節限制,可以讓2字節的對象進行4字節 對齊,但是不能讓4字節的對象2字節對齊。 __align是存儲類修改,他只修飾最高級類型對象不能用於結構或者函數對象。
2.__packed
__packed是進行一字節對齊1.不能對packed的對象進行對齊2.所有對象的讀寫訪問都進行非對齊訪問
3.float及包含float的結構聯合及未用__packed的對象將不能字節對齊
4.__packed對局部整形變量無影響
5.強制由unpacked對象向packed對象轉化是未定義,整形指針可以合法定義為packed。 __packed int* p;//__packed int 則沒有意義
6.對齊或非對齊讀寫訪問帶來問題
__packed struct STRUCT_TEST
{
char a;
int b;
char c;
} ; //定義如下結構此時b的起始地址一定是不對齊的
//在棧中訪問b可能有問題,因為棧上數據肯定是對齊訪問[from CL]
//將下面變量定義成全局靜態不在棧上
static char* p;
static struct STRUCT_TEST a;
void Main()
{
__packed int* q; //此時定義成__packed來修飾當前q指向為非對齊的數據地址下面的訪問則可以
p = (char*)&a;
q = (int*)(p+1);
*q = 0x87654321;
/*
得到賦值的匯編指令很清楚
ldr r5,0x20001590 ; = #0x12345678
[0xe1a00005] mov r0,r5
[0xeb0000b0] bl __rt_uwrite4 //在此處調用一個寫4byte的操作函數
[0xe5c10000] strb r0,[r1,#0] //函數進行4次strb操作然后返回保證了數據正確的訪問
[0xe1a02420] mov r2,r0,lsr #8
[0xe5c12001] strb r2,[r1,#1]
[0xe1a02820] mov r2,r0,lsr #16
[0xe5c12002] strb r2,[r1,#2]
[0xe1a02c20] mov r2,r0,lsr #24
[0xe5c12003] strb r2,[r1,#3]
[0xe1a0f00e] mov pc,r14
*/
/*
如果q沒有加__packed修飾則匯編出來指令是這樣直接會導致奇地址處訪問失敗
[0xe59f2018] ldr r2,0x20001594 ; = #0x87654321
[0xe5812000] str r2,[r1,#0]
*/
//這樣可以很清楚的看到非對齊訪問是如何產生錯誤的
//以及如何消除非對齊訪問帶來問題
//也可以看到非對齊訪問和對齊訪問的指令差異導致效率問題
}
sizeof進行結構體大小的判斷
typedef struct
{
int a;
char b;
}A_t;
typedef struct
{
int a;
char b;
char c;
}B_t;
typedef struct
{
char a;
int b;
char c;
}C_t;
void main()
{
char*a=0;
cout<<sizeof(a)<<endl;//4
cout<<sizeof(*a)<<endl;//1--這個能理解
cout<<sizeof(A_t)<<endl;//8
cout<<sizeof(B_t)<<endl;//8
cout<<sizeof(C_t)<<endl;//12
}
為什么是這樣的結果啊?
2. 語法:
sizeof有三種語法形式,如下:
1) sizeof( object ); // sizeof( 對象 );
2) sizeof( type_name ); // sizeof( 類型 );
3) sizeof object; // sizeof 對象;
5. 指針變量的sizeof
既然是來存放地址的,那么它當然等於計算機內部地址總線的寬度。所以在32位計算機中,一個指針變量的返回值必定是4(以字節為單位),可以預計,在將來的64位系統中指針變量的sizeof結果為8。
char* pc = "abc";
int* pi;
string* ps;
char** ppc = &pc;
void (*pf)();// 函數指針
sizeof( pc ); // 結果為4
sizeof( pi ); // 結果為4
sizeof( ps ); // 結果為4
sizeof( ppc ); // 結果為4
sizeof( pf );// 結果為4
指針變量的sizeof值與指針所指的對象沒有任何關系,正是由於所有的指針變量所占內存大小相等,所以MFC消息處理函數使用兩個參數WPARAM、LPARAM就能傳遞各種復雜的消息結構(使用指向結構體的指針)。
6. 數組的sizeof
數組的sizeof值等於數組所占用的內存字節數
如:
char a1[] = "abc";
int a2[3];
sizeof( a1 ); // 結果為4,字符串末尾還存在一個NULL終止符
sizeof( a2 ); // 結果為3*4=12(依賴於int)
一些朋友剛開始時把sizeof當作了求數組元素的個數,現在,你應該知道這是不對的,那么應該怎么求數組元素的個數呢?Easy,通常有下面兩種寫法:
int c1 = sizeof( a1 ) / sizeof( char ); // 總長度/單個元素的長度
int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 總長度/第一個元素的長度
寫到這里,提一問,下面的c3,c4值應該是多少呢?
void foo3(char a3[3])
{
int c3 = sizeof( a3 ); // c3 ==
}
void foo4(char a4[])
{
int c4 = sizeof( a4 ); // c4 ==
}
也 許當你試圖回答c4的值時已經意識到c3答錯了,是的,c3!=3。這里函數參數a3已不再是數組類型,而是蛻變成指針,相當於 char* a3,為什么?仔細想想就不難明白,我們調用函數foo1時,程序會在棧上分配一個大小為3的數組嗎?不會!數組是“傳址”的,調用者只需將實參的地址傳 遞過去,所以a3自然為指針類型(char*),c3的值也就為4。
7. 結構體的sizeof
這是初學者問得最多的一個問題,所以這里有必要多費點筆墨。讓我們先看一個結構體:
struct S1
{
char c;
int i;
};
問sizeof(s1)等於多少?聰明的你開始思考了,char占1個字節,int占4個字節,那么加起來就應該是5。是這樣嗎?你在你機器上試過了嗎?也許你是對的,但很可能你是錯的!VC6中按默認設置得到的結果為8。
Why?為什么受傷的總是我?請不要沮喪,我們來好好琢磨一下sizeof的定義——sizeof的結果等於對象或者類型所占的內存字節數,好吧,那就讓我們來看看S1的內存分配情況:
S1 s1 = { a , 0xFFFFFFFF };
定義上面的變量后,加上斷點,運行程序,觀察s1所在的內存,你發現了什么?
以我的VC6.0為例,s1的地址為0x0012FF78,其數據內容如下:
0012FF78: 61 CC CC CC FF FF FF FF
發現了什么?怎么中間夾雜了3個字節的CC?看看MSDN上的說明:
When applied to a structure type or variable, sizeof returns the actual size,
which may include padding bytes inserted for alignment.
原 來如此,這就是傳說中的字節對齊啊!一個重要的話題出現了。為什么需要字節對齊?計算機組成原理教導我們這樣有助於加快計算機的取數速度,否則就得多花指 令周期了。為此,編譯器默認會對結構體進行處理(實際上其它地方的數據變量也是如此),讓寬度為2的基本數據類型(short等)都位於能被2整除的地址 上,讓寬度為 4的基本數據類型(int等)都位於能被4整除的地址上,以此類推。這樣,兩個數中間就可能需要加入填充字節,所以整個結構體的sizeof值就增長了。 讓我們交換一下S1中char與int的位置:
struct S2
{
int i;
char c;
};
看看sizeof(S2)的結果為多少,怎么還是8?再看看內存,原來成員c后面仍然有3個填充字節,這又是為什么啊?別着急,下面總結規律。
字節對齊的細節和編譯器實現相關,但一般而言,滿足三個准則:
1) 結構體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2) 結構體每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充字節(internal adding);
3) 結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之后加上填充字節(trailing padding)。
對於上面的准則,有幾點需要說明:
1) 前 面不是說結構體成員的地址是其大小的整數倍,怎么又說到偏移量了呢?因為有了第1點存在,所以我們就可以只考慮成員的偏移量,這樣思考起來簡單。想想為什 么。結構體某個成員相對於結構體首地址的偏移量可以通過宏offsetof()來獲得,這個宏也在stddef.h中定義,如下:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
例如,想要獲得S2中c的偏移量,方法為
size_t pos = offsetof(S2, c);// pos等於4
2) 基本類型是指前面提到的像char、short、int、float、double這樣的內置數據類型,這里所說的“數據寬度”就是指其sizeof的大 小。由於結構體的成員可以是復合類型,比如另外一個結構體,所以在尋找最寬基本類型成員時,應當包括復合類型成員的子成員,而不是把復合成員看成是一個整 體。但在確定復合類型成員的偏移位置時則是將復合類型作為整體看待。這里敘述起來有點拗口,思考起來也有點撓頭,還是讓我們看看例子吧(具體數值仍以 VC6為例,以后不再說明):
struct S3
{
char c1;
S1 s;
char c2
};
S1的最寬簡單成員的類型為int,S3在考慮最寬簡單類型成員時是將S1“打散”看的,所以S3的最寬簡單類型為int,這樣,通過S3定義的變量,其存儲空間首地址需要被4整除,整個sizeof(S3)的值也應該被4整除。
c1 的偏移量為0,s的偏移量呢?這時s是一個整體,它作為結構體變量也滿足前面三個准則,所以其大小為8,偏移量為4,c1與s之間便需要3個填充字節,而 c2與s之間就不需要了,所以c2的偏移量為12,算上c2的大小為13,13是不能被4整除的,這樣末尾還得補上3個填充字節。最后得到sizeof (S3)的值為16。
通過上面的敘述,我們可以得到一個公式:
結構體的大小等於最后一個成員的偏移量加上其大小再加上末尾的填充字節數目,即:
sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( trailing padding )
到 這里,朋友們應該對結構體的sizeof有了一個全新的認識,但不要高興得太早,有一個影響sizeof的重要參量還未被提及,那便是編譯器的 pack指令。它是用來調整結構體對齊方式的,不同編譯器名稱和用法略有不同,VC6中通過#pragma pack實現,也可以直接修改/Zp編譯開關。#pragma pack的基本用法為:#pragma pack( n ),n為字節對齊數,其取值為1、2、4、8、16,默認是8,如果這個值比結構體成員的sizeof值小,那么該成員的偏移量應該以此值為准,即是說, 結構體成員的偏移量應該取二者的最小值,公式如下:
offsetof( item ) = min( n, sizeof( item ) )
再看示例:
#pragma pack(push) // 將當前pack設置壓棧保存
#pragma pack(2)// 必須在結構體定義之前使用
struct S1
{
char c;
int i;
};
struct S3
{
char c1;
S1 s;
char c2
};
#pragma pack(pop) // 恢復先前的pack設置
計 算sizeof(S1)時,min(2, sizeof(i))的值為2,所以i的偏移量為2,加上sizeof(i)等於6,能夠被2整除,所以整個S1的大小為6。 同樣,對於sizeof(S3),s的偏移量為2,c2的偏移量為8,加上sizeof(c2)等於9,不能被2整除,添加一個填充字節,所以 sizeof(S3)等於10。現在,朋友們可以輕松的出一口氣了,還有一點要注意,“空結構體”(不含數據成員)的大小不為0,而是1。試想一個“不占 空間”的變量如何被取地址、兩個不同的“空結構體”變量又如何得以區分呢?於是,“空結構體”變量也得被存儲,這樣編譯器也就只能為其分配一個字節的空間 用於占位了。
如下:
struct S5 { };
sizeof( S5 ); // 結果為1
8. 含位域結構體的sizeof
前 面已經說過,位域成員不能單獨被取sizeof值,我們這里要討論的是含有位域的結構體的 sizeof,只是考慮到其特殊性而將其專門列了出來。C99規定int、unsigned int和bool可以作為位域類型,但編譯器幾乎都對此作了擴展,允許其它類型類型的存在。
使用位域的主要目的是壓縮存儲,其大致規則為:
1) 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則后面的字段將緊鄰前一個字段存儲,直到不能容納為止;
2) 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則后面的字段將從新的存儲單元開始,其偏移量為其類型大小的整數倍;
3) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6采取不壓縮方式,Dev-C++采取壓縮方式;
4) 如果位域字段之間穿插着非位域字段,則不進行壓縮;
5) 整個結構體的總大小為最寬基本類型成員大小的整數倍。
還是讓我們來看看例子。
示例1:
struct BF1
{
char f1 : 3;
char f2 : 4;
char f3 : 5;
};
其內存布局為:
|_f1__|__f2__|_|____f3___|____|
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|
0 3 7 8 1316
位域類型為char,第1個字節僅能容納下f1和f2,所以f2被壓縮到第1個字節中,而f3只能從下一個字節開始。因此sizeof(BF1)的結果為2。
示例2:
struct BF2
{
char f1 : 3;
short f2 : 4;
char f3 : 5;
};
由於相鄰位域類型不同,在VC6中其sizeof為6,在Dev-C++中為2。
示例3:
struct BF3
{
char f1 : 3;
char f2;
char f3 : 5;
};
非位域字段穿插在其中,不會產生壓縮,在VC6和Dev-C++中得到的大小均為3。
9. 聯合體的sizeof
結構體在內存組織上是順序式的,聯合體則是重疊式,各成員共享一段內存,所以整個聯合體的sizeof也就是每個成員sizeof的最大值。結構體的成員也可以是復合類型,這里,復合類型成員是被作為整體考慮的。
所以,下面例子中,U的sizeof值等於sizeof(s)。
union U
{
int i;
char c;
S1 s;
};