1.1 嵌套的數組
當我們創建數組的數組時,數組分配和引用的一般原則也是成立的。
例如,聲明 int A[5][3];
等價于下面的聲明
typedef int row3_t[3];
row3_t A[5]
要訪問多維數組的元素,編譯器會以數組起始為基地址, (可能需要經過伸縮的)偏移量為索引,產生計算期望的元素的偏移量,然后使用某種 MOV指令。通常來說,對于一個 聲明如下的數組:
T D[R][C];
它的數組元素D[i][j]的內存地址為
&D[i][j]= Xd?+L(C*i+ j)
這里L是數據類型T以字節為單位的大小。
1.2定長數組
C語言編譯器能夠優化定長多維數組上的操作代碼。這里我們展示優化等級設置為 -01時GCC采用的一些優化。假設我們用如下方式將數據類型fixjnatrix聲明為 16X16 的整型數組:
#define N 16;
typedef int fix_matrix[N][N];
這樣做的好處是,如果需要修改這個值只需要簡單的修改這個# define聲明就可以了。
1.3?變長數組
歷史上,C語言只支持大小在編譯時就能確定的多維數組(對第一維可能有些例外)。 程序員需要變長數組時不得不用malloc或calloc這樣的函數為這些數組分配存儲空間,而且不得不顯式地編碼,用行優先索引將多維數組映射到一維數組,ISO C99引入了一種功能,允許數組的維度是表達式,在數組被分配的時候才計算出來。
在變長數組的C版本中,我們可以將一個數組聲明如下:
int? A[expr1]?[expr2];
它可以作為一個局部變量,也可以作為一個函數的參數,然后在遇到這個聲明的時候,通過對表達式和求值來確定數組的維度。
因此,例如要訪問 n *n 數組的元素 I ,j,我們可以寫一個如下的函數:
int var_ele(long n, int A[n][n], long i, long j){
return A[i][j]; }
參數n必須在參數A[n][n]之前,這樣函數就可以在遇到這個數組的時候計算出數組的維度。
GCC為這個引用函數產生的代碼如下所示:
int var_ele(long n, int A[n][n], long i, long j)
n in %rdi,?A in %rsi,?i in %rdx, j in %rcx
1??var_ele:
2? imulq %rdx, %rdi
3??leaq (%rsi,%rdi*4), %rax
4??movl?(%rax,%rcx,4), %eax
5??ret
這 個地址的計算類似于定長數組的地址計算,不同點在于
1)由于增加了參數 n, 寄存器的使用變化了;
2)用了乘法指令來計算n?* i(第2行),而不是用leaq指令來計算3i?。因此引用變長數組只需要對定長數組做一點兒概括。
動態的版本必須用乘法指令對 i 伸縮n倍,而不能用一系列的移位和加法。在一些處理器中,乘法會招致嚴重的性能處 罰,但是在這種情況中無可避免。
2.1異質的數據結構
C語言提供了兩種將不同類型的對象組合到一起創建數據類型的機制:結構(structure),用關鍵字 struct 來聲明,將多個對象集合到一個單位中;聯合(union),用關鍵 字union 來聲明,允許用幾種不同的類型來引用一個對象
2.2結構
C語言的struct聲明創建一個數據類型,將可能不同類型的對象聚合到一個對象中。 用名字來引用結構的各個組成部分。類似于數組的實現,結構的所有組成部分都存放在內存中一段連續的區域內,而指向結構的指針就是結構第一個字節的地址。編譯器維護關于每個結構類型的信息,指示每個字段(field)的字節偏移。它以這些偏移作為內存引用指令中的位移,從而產生對結構元素的引用。
2.3聯合
聯合提供了一種方式,能夠規避C語言的類型系統,允許以多種類型來引用一個對 象。聯合聲明的語法與結構的語法一樣,只不過語義相差比較大。它們是用不同的字段來引用相同的內存塊。
考慮下面的聲明: struct S3{
char c;
int i[2];
double v;
};
union U3{
char c;
int i[2];
double v;?
};
在一臺x86-64Lmux機器上編譯時,字段的偏移量、數據類型S3和U3的完整大小如下:
在一些下上文中,聯合十分有用。但是,它也能引起一些討厭的錯誤,因為它們繞過 了C語言類型系統提供的安全措施。一種應用情況是,我們事先知道對一個數據結構中的兩個不同字段的使用是互斥的,那么將這兩個字段聲明為聯合的一部分,而不是結構的一 部分,會減小分配空間的總量。
2.4數據對齊
許多計算機系統對基本數據類型的合法地址做出了一些限制,要求某種類型對象的地 址必須是某個值K(通常是2、4或8)的倍數。這種對齊限制簡化了形成處理器和內存系統 之間接口的硬件設計。
例如,假設一個處理器總是從內存中取8個字節,則地址必須為8 的倍數。如果我們能保證將所有的double類型數據的地址對齊成8的倍數,那么就可以 用一個內存操作來讀或者寫值了。否則,我們可能需要執行兩次內存訪問,因為對象可能被分放在兩個8字節內存塊中。
對齊原則是任何K字節的基本對象的地址必須是K?的倍數。可以 看到這條原則會得到如下對齊:
確保每種數據類型都是按照指定方式來組織和分配,即每種類型的對象都滿足它的對齊限制,就可保證實施對齊。編譯器在匯編代碼中放人命令,指明全局數據所需的對齊。