1.內存對齊
ANSI C保證結構體中各字段在內存中出現的位置是隨它們的聲明順序依次遞增的,並且第一個字段的首地址等於整個結構體實例的首地址。
實際的計算機系統對基本類型數據在內存中存放的位置有限制,它們會要求這些數據的首地址的值是某個數k(通常它爲4或8)的倍數,這就是所謂的內存對齊,而這個k則被稱爲該數據類型的對齊模數(alignment modulus)。
/*Win32平臺下的微軟C編譯器(cl.exe for80x86)在默認情況下采用如下的對齊規則: 任何基本數據類型T的對齊模數就是T的大小,即sizeof(T)。
例如對於double類型(8字節),就要求該類型數據的地址總是8的倍數,而char類型數據(1字節)則可以從任何一個地址開始。Linux下的GCC奉行的是另外一套規則(在資料中查得,並未驗證,如錯誤請指正):任何2字節大小(包括單字節嗎?)的數據類型(比如short)的對齊模數是2,而其它所有超過2字節的數據類型(比如long,double)都以4爲對齊模數。*/
內存對齊方式體現了內存存儲空間和存儲效率的一種平衡。例如對於一次性存入double(8字節)類型,那麼在存入其他類型的同時可能存在內存的浪費。而對於以4爲對齊模數的情況下,一個double數據需要兩個存儲週期才能完成,降低了存儲效率。
好的內存對齊方式也能提高內存命中率。
2.結構的大小
更具體的可參見:http://blog.csdn.net/lbjfeng/archive/2009/03/04/3952387.aspx
可以 使用預編譯指令#progma pack (value)來告訴編譯器,使用我們指定的對齊值來取代缺省的。
#progma pack (2) /*指定按2字節對齊*/
struct C
{
char b;
int a;
short c;
};
#progma pack () /*取消指定對齊,恢復缺省對齊*/
sizeof(struct C)值是8。
如果一個程序中的兩個模塊是用不同的對齊選項分別編譯的,那麼它很可能會產生一些非常微妙的錯誤。如果你的程序確實有很難理解的行爲,不防仔細檢查一下各個模塊的編譯選項。
3.位結構
//具體的實現和應用還不知道只是做個簡單的瞭解
位結構是一種特殊的結構, 在需按位訪問一個字節或字的多個位時, 位結構比按位運算符更加方便。
位結構定義的一般形式爲:
struct位結構名
{
數據類型變量名: 整型常數;
數據類型變量名: 整型常數;
} 位結構變量;
其中: 數據類型必須是int(unsigned或signed)。整型常數必須是非負的整數, 範圍是0~15, 表示二進制位的個數, 即表示有多少位。
struct mybitfields
{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
}test;
void main(void)
{
int i;
test.a=2;
test.b=3;
test.c=0;
i=*((short *)&test);
printf("%d ",i);
}
這裏的冒號相當於分配幾位空間,也即在定義結構體的時候,分配的成員a 4位的空間, b 5位,c 7位,一共是16位,正好兩個字節。一個簡單的示意:
變量名 位數
test 15 14 13 12 11 10 9 |8 7 6 5 4 |3 2 1 0
test.a | |0 0 1 0
test.b |0 0 0 1 1 |
test.c 0 0 0 0 0 0 0 | |
在執行i=*((short*)&test); 時,取從地址&test開始兩個字節(short佔兩個字節)的內容轉化爲short型數據,即爲0x0032,再
轉爲int型爲0x00000032,即50。輸出的結果就是50。
2 含位域結構體的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______|______|
|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|
位域類型爲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。
寫出下列程序在X86上的運行結果。
struct mybitfields
{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
}test;
void main(void)
{
int i;
test.a=2;
test.b=3;
test.c=0;
i=*((short *)&test);
printf("%d ",i);
}
這個題的爲難之處呢,就在於前面定義結構體裏面用到的冒號,如果你能理解這個符號的含義,那麼問題就很好解決了。這裏的冒號相當於分配幾位空間,也即在定義結構體的時候,分配的成員a 4位的空間, b 5位,c 7位,一共是16位,正好兩個字節。下面畫一個簡單的示意:
變量名 位數
test 15 14 13 12 11 10 9 |8 7 6 5 4 |3 2 10
test.a | |0 0 1 0
test.b |0 0 0 1 1 |
test.c 0 0 0 0 0 0 0 | |
在執行i=*((short*)&test); 時,取從地址&test開始兩個字節(short佔兩個字節)的內容轉化爲short型數據,即爲0x0032,再轉爲int型爲0x00000032,即50。輸出的結果就是50。當然,這裏還涉及到字節及位的存儲順序問題,後面再說。
前面定義的結構體被稱爲位結構體。所謂位結構體,是一種特殊的結構體,在需要按位訪問字節或字的一個或多個位時,位結構體比按位操作要更方便一些。
位結構體的定義方式如下:
struct [位結構體名]{
數據類型 變量名:整數常數;
...
}位結構變量;
說明:
1)這裏的數據類型只能爲int型(包括signed和unsigned);
2)整數常數必須爲0~15之間的整數,當該常數爲1時,數據類型爲unsigned(顯然嘛,只有一位,咋表示signed?光一符號?沒意義呀);
3)按數據類型變量名:整數常數;方式定義的結構成員稱爲位結構成員,好像也叫位域,在一個位結構體中,可以同時包含位結構成員及普通的結構成員;
4)位結構成員不能是指針或數據,但結構變量可以是指針或數據;
5)位結構體所佔用的位數由各個位結構成員的位數總各決定。如在前面定義的結構體中,一共佔用4+5+7=16位,兩個字節。另外我們看到,在定義位結構成員時,必須指定數據類型,這個數據類型在位結構體佔用多少內存時也起到不少的作用。舉個例子:
struct mybitfieldA{
char a:4;
char b:3;
}testA;
struct mybitfieldB{
short a:4;
short b:3;
}testB;
這裏,testA佔用一個字節,而testB佔用兩個字節。知道原因了吧。在testA中,是以char來定義位域的,char是一個字節的,因此,位域佔用的單位也按字節做單位,也即,如果不滿一個字節的話按一個字節算(未定義的位按零處理)。而在testB中,short爲兩個字節,所以了,不滿兩個字節的都按兩個字節算(未定義位按零處理)
關於位結構體在內存中的存儲問題
Kevin's Theory #2: In a C structure thatcontains bit fields, if field A is defined in front of field B, then field A alwaysoccupies a lower bit address than field B. (來自http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxk&Number=638637&page=0&view=collapsed&sb=5&o=all&fpart=all)
說的是,在C結構體中,如果一個位域A在另一個位域B之前定義,那麼位域A將存儲在比B小的位地址中。
如果一個位域有多個位時,各個位的排列順序通常是按CPU的端模式(Endianess)來進行的,即在大端模式(big endian)下,高有效位在低位地址,小端模式則相反。
補充說明一個關於位域與普通結構成員一起使用的問題
先看一個例子
struct mybitfield{
char a:4;
char b:3;
char aa;
char c:1;}test;
這種情況下,test應該佔幾個字節呢?2個(4+3+1=8佔一個字節,aa佔一個)還是3個(4+3不足補一位,佔一個字節,aa佔一個字節,c佔一個字節)?
寫個小程序驗證一下:
int main(int argc, char* argv[])
{
int i;
test.a = 1;
test.b = 1;
test.aa = 1;
test.c = 1;
i=*((short *)&test);
printf("%d /n",i);
return 0;
}
輸出結果是273,化爲十六進制數0x111,可見是按三個字節來處理了(如果按兩個字節處理的話,cba組成一個字節,是10010001(十六進制0x91)再加上aa,那就應該是0x191了)
舉這個例子是爲了說明一下,定義位域的話,最好是把所以有位域放在一起,這樣可以節省空間(如果把c和aa換一下位置,那test就只佔兩個字節了)。另外也是爲了強調一下位結構體的內存分配方式,按定義的先後順序來分配,而位域(或成員)內的字節順序則按照CPU的位順序來進行(一般與CPU的端模式對應)。
struct mybitfields
{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
} test;
=> sizeof(test) == 2;
struct mybitfields
{
unsigned char a : 4;
unsigned char b : 5;
unsigned char c : 7;
} test;
=> sizeof(test) == 3;
struct mybitfields
{
unsigned char a : 4;
unsigned short b : 5;
unsigned char c : 7;
} test;
=> sizeof(test) == 6;
struct mybitfields
{
unsigned short a : 4;
unsigned char b : 5;
unsigned char c : 7;
} test;
=> sizeof(test) == 4;
struct mybitfields
{
unsigned char a : 4;
unsigned char b : 5;
unsigned short c : 7;
} test;
=> sizeof(test) == 4;
struct mybitfields
{
unsigned char a : 4;
unsigned int b : 5;
unsigned short c : 7;
} test;
=> sizeof(test) == 12;