**陸**
***結構***
**數據對齊:**
許多計算機系統**對基本數據類型的合法地址做出了一些限制**:**要求某種類型對象的地址必須是某個值K(通常是2、4、8)的倍數,**這種對齊限制簡化了 處理器和存儲系統之間接口的硬件設計。
(因為:如果處理器經常從內存中取出8字節,若內存中一個存儲器塊單位是8個字節。則我們保證將所有double類型數據的地址對齊成8的倍數,那么就可以用一個存儲器操作來讀寫值了! 否則,對象可能被分在兩個存儲器塊中,我們就要執行兩次存儲器訪問!)
**保持數據對齊能夠提高效率。**
數據對齊 是與操作系統和硬件相關的。
在IA32平臺下,Linux的對齊規則是:2字節數據類型(例如short)的地址必須是2的倍數,而其它數據類型的地址必須是4的倍數。
**WINDOWS對齊的要求更嚴格——任何n字節基本數據對象的地址 都必須是n的倍數,n=2,4,8。??**這種要求提高了存儲器性能,而代價是浪費了一些空間。
~~~
struct S1
{ int i ;
char c ;
int j ;
} ;
~~~
//編譯器可能需要在字段的分配中插入間隙,**以保證每個結構元素都滿足它的對齊要求**,而結構本身對它的起始地址也有一些對齊要求。
(故為了滿足字段i和j的4字節對齊要求,編譯器在字段c和j之間插入一個3字節的間隙[可稱之為:內存空洞] 結構的)
此外也要考慮結構體地址的對齊。
故結構體的數據對齊要考慮每個成員的對齊要求還要考慮整個結構體的對齊要求。
★但可歸結為一個準則:**以結構體中最大數據類型元素的對齊為標準。**
**結構的總大小要滿足最大元素 對齊的倍數**
~~~
struct S2
{ doubled ;
int i ;
int j ;
char c ;
} ;
~~~
//我們以成員中最大數據類型為標準來對齊,由此來計算整個結構的大小。最大對齊規則都滿足了,小的對齊規則就好滿足了。
此例中:d 占8個字節,i和j分別占4個字節,而c及尾部空洞共占8個字節。
因為:如此時c和上面一樣還和空洞共占4字節的話,結構的總大小就不滿足最大元素對齊的倍數了。這樣若有結構體數組,則下個結構體的首位置就不會對齊了。
(可以這樣思考:**以最大類型元素的對齊大小為單位 分割結構的存儲空間**這樣最后總的存儲空間就是此單位的倍數了。)
★【**按照 數據對齊嚴格程度的大小有序排列結構中的成員,可最大限度地減少因邊界對齊帶來的損失**】
如果你必須**確定結構中某個成員的實際位置,可以使用offsetof宏**(定義于stddef.h)
offsetof(結構體類型名,成員名) 其返回指定成員據結構體首元素地址的字節數。
~~~
struct S1
{ char c ;
double d ;
} ; // offsetof(struct S1, d) ;表達式值為8
~~~
***聯合***
聯合提供了一種方式,能夠規避C語言的類型系統,**允許以多種類型來引用一個對象**。
一個聯合的總大小等于它最大字段的大小。**聯合是用不同的字段來引用相同的存儲器塊。**
在某些情況下,聯合十分用,但若使用不當,容易引發錯誤,因為它們繞過了C語言類型系統提供的安全措施。
聯合可以被初始化,但這個值必須是聯合的第1個成員類型。
例: union { inta ; char c[4]; } x = {5} ;
**應用:**
**1,我們事先知道一個結構中的兩個不同字段是互斥的**,**那么我們將兩個字段聲明為聯合的一部分,以減少存儲空間。**(對于有較多類似情況的結構,會節省很多空間)
此時,我們一般引入一個枚舉類型,來標記這個聯合中當前的選擇,然后再創建一個結構,包含一個標簽字段和這個聯合。
~~~
typedef enum { N_LEAF, N_INTERNAL } nodetype_t ;//定義枚舉
struct NODE_T
{ nodetype_t type ; //可標記此結點是葉結點還是內部結點
union //聯合
{ struct{ struct NODE_T * left ; structNODE_T * right } internal ;
double data ;
} info ;
} ;
~~~
**2,★可以用來訪問不同數據類型的位模式**
~~~
//下面的代碼返回的是:float作為unsigned的位表示
unsigned float2bit ( float f )
{ union
{ float f ;
unsigned u ;
}tmp ;
tmp.f= f ;
return tmp.u ;
}
~~~
//以上,**我們以一種數據類型來存儲聯合中的參數,又以另一種數據類型來訪問它。(不同于強制類型轉換)**
(實際對應的機器碼為movl 8(%ebp), %eax
這就說明了?**機器碼中沒有類型信息**,無論參數是float還是unsigned,它們都在相對%ebp偏移量為8的地方,程序只是簡單地將它們的值復制到返回值,**不修改任何位**[而強制類型轉換 涉及浮點值的轉換時 會修改位])
**3,可以深入到 數據內部的字節級來訪問**
~~~
union
{ double d ;
unsignedu[2] ;
} tmp ;
tmp.u[0] = x1 ;
tmp.u[1] = x2 ; //分別訪問了double型的數據d的低4字節與高4字節。
(注意在大端法機和小端法機上的結果相反)
~~~
【注意:如果聯合中成員的長度相差懸殊,當存儲較短成員時會浪費很多空間。
好的方法是:
在聯合中存儲指向不同成員的指針,而不是存儲成員本身,當需要哪個成員時,動態分配空間】
***位段***
C允許程序員**把整數成員包裝到比編譯器正常所允許的更小的空間中。這種整數成分成為位段,**是通過在結構體的成員聲明中使用一個冒號和一個常量表達式(指定了位段所占據的位數)指定的。
(位段就是允許我們自己定義某個整型的數據占據多少位,而對位的解釋還是與其數據類型一樣)
**ANSI標準C允許位段為unsigned int , signed int , int類型**,它們分別稱為:無符號位段、有符號位段、普通位段(有可能是有符號或無符號的)。
有些編譯器允許使用任何整數類型的位段,包括char型。
編譯器可以選擇地對位段的最大長度加以限制,并指定位段無法跨越的地址邊界,當一個字段將跨越一個字的邊界時,它可能會移動到下一個字。
【注意:取地址操作符&無法用于位段成員,因為計算機無法對任意長度的字段編址】
**關于填充:**
~~~
struct s
{ unsigned a :4 ;
unsigned :2 ;
unsigned b :6 ;
} ; //此位段結構共占4字節。
~~~
結構中也可以包含無名位段。作為相鄰成員之間的填充。
**★注意:編譯器是以字(int型大小)為單位,向位段結構分配存儲空間的!一次分配一個字的空間**(32位機就是32個位),而不是根據定義的位的數量來分配(這是為了數據對齊)。若一個位段結構的總定義空間大于一個字,則再分配一個字。
(如此看來,使用位段不一定能節省空間,有可能還浪費空間。只是它可以對一個字的內部位方便地訪問,**使具名訪問深入到了位級!**)
**當然:若是定義char型的位段,則編譯器是以字節(char型大小)為單位,來分配空間的!**
(這樣,我們可以根據需要選用兩種不同大小規格的位段結構)
**★為一個無名字段指定0長度 具有特殊含義**,它表示不應該有其它位段被包裝到前一個位段所在的區域。如果有,它就被放置。
~~~
struct s
{ unsigned a :4 ;
unsigned :0 ;
unsigned b :6 ;
} ;
~~~
**【0長度字段就相當于是一個分隔線,把其前和其后的字段分隔到不同的字單位中】**
故:上面的unsigneda :4 與后面的空洞合占一個字的空間;unsigned b :6與后面的空洞合占一個字的空間。unsigned a :4,unsigned b :6兩個成員分屬不同的字空間。整個位段結構占2個字即8字節空間。
**移植性問題:**
使用位段的程序是無法移植的。因為其在不同計算機平臺上的實現結果可能不同,依賴于硬件(大端小端,字長)和編譯器。
**應用:**
用于一個結構體數組,由于數組太大,所以要求結構體的成員必須緊密包裝,以節省內存。
(只有在內存空間問題上有嚴格要求的程序 才使用位段。 一般情況不用!)
***位級操作***
位段也是一種位級操作的方式,但位段不可移植(缺點),不過其能通過變量名來訪問位,使操作上更簡便,程序更清晰。(優點)
**我們通過掩碼和移位操作可完全取代位段的功能**,且具有移植性。只是操作不及位段簡單。
(這兩種方法都可在位級操作,我們可根據情況,在程序清晰性和可移植性間做出考量)
位運算符:按位取反~ 、左移<< 、右移>> 、與運算&(邏輯乘) 、或運算|(邏輯加)、異或運算^(按位加)
【**注意:移位操作符的優先級問題**】
例t =a<<2 + a; 對于a<<2這個位操作,優先級要比加法要低,所以這個表達式就成了“t = a<< (2+a)”,而我們實際的意圖可能是t= (a<<2) + a;
【涉及移位操作的表達式要警惕其優先級問題!】
**常用的位操作:**
**1、取出(檢測)某位的值**
value & 掩碼??????? (結果為0則某位為0,結果非0則某位為1)
**2、設置某位的值**
①把某位置1
value |= 掩碼
②把某位置0
value &= ~掩碼
【注意:以上掩碼均意為——把要設置的位置1,其余位置0】
以上兩類的操作(一類可對任意位訪問,一類可對任意位賦值)組合起來可實現所有的位操作!
**掩碼的生成**
**1,可用左移操作(<<)生成所需要的掩碼**
例:value ?&? ( 1<<n )
? ? ? value ?|=? ( 1<<n )
value ?&= ~(1<<n )
**2,預先算出包含所有掩碼的數組(用于頻繁的位級操作)**
例:unsigned intmasks[ ] = { 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 } ;//從0號位到7號位所有的掩碼。
value & masks[n] ;
【應用聯合 可用unsigned的方式訪問任意數據類型,再用位級操作,兩者結合起來可以訪問任意數據內部的任意位,無論它是什么數據類型!如此一來,數據的底層細節可暴露無遺,高級語言對細節的封裝和隱蔽亦可暴露。 這就使C具備了匯編的某些特性。】