數(shù)組概述
對(duì)于數(shù)據(jù)類(lèi)型 T 和 整型常數(shù) N,聲明如下 T A[N],這個(gè)聲明有如下效果:
- 首先,計(jì)算機(jī)在內(nèi)存中分配一個(gè) L * N 字節(jié)的連續(xù)區(qū)域,這里 L 指的是數(shù)據(jù)類(lèi)型 T 的大小。
- 數(shù)組引入標(biāo)識(shí) A, 可以用 A 來(lái)作為指向數(shù)組開(kāi)頭的指針,這個(gè)指針的值表示為 Xa,可以用 0 ~ N-1 的整數(shù)索引來(lái)訪問(wèn)該數(shù)組元素,第 i 個(gè)數(shù)組元素會(huì)被存放在地址為 Xa + L*i 的地方。
訪問(wèn)數(shù)組
C 語(yǔ)言允許對(duì)指針進(jìn)行運(yùn)算,而計(jì)算出來(lái)的值會(huì)根據(jù)該指針引用的數(shù)據(jù)類(lèi)型的大小進(jìn)行伸縮。也就是說(shuō),如果 p 是一個(gè)指向類(lèi)型為 T 的數(shù)據(jù)的指針,p 的值為 xp, 那么表達(dá)式 p+i 的值為 xp + L*i,這里 L 是數(shù)據(jù)類(lèi)型 T 的大小。操作符 & 可以產(chǎn)生指針,操作符 * 可以產(chǎn)生間接引用指針。也就是說(shuō),對(duì)于一個(gè)表示某個(gè)對(duì)象的表達(dá)式 Expr, &Expr 是給出該對(duì)象地址的一個(gè)指針。對(duì)于一個(gè)表示地址的表達(dá)式 AExpr, *AExp 是給出該地址處的值。因此,表達(dá)式 Expr 和 *&Expr 是等價(jià)的。可以對(duì)數(shù)組和指針應(yīng)用數(shù)組下標(biāo)操作,數(shù)組引用 A[ i ] 等價(jià)于表達(dá)式 *(A+i),它計(jì)算第 i 個(gè)數(shù)組元素的地址,然后訪問(wèn)這個(gè)內(nèi)存地址。
嵌套數(shù)組
當(dāng)我們創(chuàng)建數(shù)組類(lèi)型的數(shù)組時(shí),數(shù)組分配和引用的一般原則也是成立的。創(chuàng)建了一個(gè) T 數(shù)據(jù)類(lèi)型的嵌套數(shù)組 A [R][C],表示數(shù)組有 R 行,C 列,一個(gè) T 數(shù)據(jù)類(lèi)型需要 k 個(gè)字節(jié)存儲(chǔ)。 整個(gè)數(shù)組 A 的大小就是 R * C * k 字節(jié)。要訪問(wèn)嵌套數(shù)組的元素,編譯器會(huì)以數(shù)組起始為基地址,偏移量為索引,計(jì)算出期望的元素的偏移量。對(duì)于 A[R][C] 來(lái)說(shuō), &A[i][j] 的內(nèi)存地址為 &A[R][C] = x + k * ( C * i + j ),其中 x 為數(shù)組 A 的起始地址, k是數(shù)據(jù)類(lèi)型 T 以字節(jié)為單位的大小。
二維數(shù)組在內(nèi)存中的排列順序是以行為主,如上圖所示,int A[R][C] 中 R 的值先從 0 開(kāi)始,接著 C 的值從 0 ~ (c-1), 之后 R 的值到 1, C 的值再?gòu)?0 ~ (c-1), 直到 R 的值為 R-1。
多維數(shù)組
回憶一下,上面提到的嵌套數(shù)組。
那么接下來(lái)講講多維數(shù)組,這兩個(gè)概念可是不一樣的哦!
上圖中,首先定義了三個(gè) zip_dig 類(lèi)型的數(shù)組 cmu,uw,ucb。然后定義了一個(gè) univ 的指針數(shù)組,該指針數(shù)組保存了 cmu,uw,ucb 的值。注意一下,univ 保存的是指針。
那么怎么訪問(wèn)嵌套數(shù)組呢?又怎么訪問(wèn)多維數(shù)組呢?
對(duì)于嵌套數(shù)組來(lái)說(shuō),數(shù)組在內(nèi)存的排列是連續(xù)的。所以數(shù)組元素的內(nèi)存地址的計(jì)算方式是 Mem [ sea + 20 * index + 4 *dig ] ,其中 sea 是數(shù)組的起始地址,4 是數(shù)組中每個(gè)元素的字節(jié)大小, 20 是嵌套數(shù)組每行的內(nèi)存容量由 4 * 5 得來(lái),Mem表示內(nèi)存數(shù)組。
對(duì)于多維數(shù)組來(lái)說(shuō),數(shù)組在內(nèi)存的排列則是非連續(xù)性的,依賴于指針的值。數(shù)組元素的內(nèi)存地址的計(jì)算方式需要經(jīng)過(guò) 2 個(gè)步驟,第一個(gè)步驟是計(jì)算出一維數(shù)組的指針地址 Mem[univ + 4 * index],第二個(gè)步驟才是計(jì)算出數(shù)組元素的地址 Mem[ Mem[univ + 4 * index] + 4* dig]。其中 univ 是多維數(shù)組的起始位置, 4 是數(shù)組中每個(gè)元素的字節(jié)大小,Mem表示內(nèi)存數(shù)組。
結(jié)構(gòu)體
C 語(yǔ)言的 struct 聲明創(chuàng)建一個(gè)數(shù)據(jù)類(lèi)型,將可以能不同類(lèi)型的對(duì)象聚合到一個(gè)對(duì)象中,用名字來(lái)引用結(jié)構(gòu)中的各個(gè)組成部分。類(lèi)似于數(shù)組的實(shí)現(xiàn),結(jié)構(gòu)中的所有組成部分都存放在內(nèi)存中一段連續(xù)的區(qū)域內(nèi),而指向結(jié)構(gòu)的指針就是結(jié)構(gòu)第一個(gè)字節(jié)的地址,結(jié)構(gòu)體中的變量的偏移地址是由編譯器在編譯時(shí)期確定的。
對(duì)于 struct 的實(shí)例來(lái)說(shuō),我們可以使用點(diǎn)(.)操作符來(lái)引用 struct 的變量
struct rec r1;
r1.i = val;
也可以使用指針來(lái)表示 struct,如 struct rec *r = &r1;
使用指針和點(diǎn)(.)操作符來(lái)訪問(wèn) struct 變量
struct rec r1;
(*r1).i = val;
使用箭頭(->)操作符來(lái)訪問(wèn) struct 變量
r->i = val;
結(jié)構(gòu)體對(duì)齊
數(shù)據(jù)對(duì)齊的實(shí)現(xiàn)是依賴于機(jī)器的,IA32 Linux,x86-64 Linux 和 window 都是有差異的。
如上圖所示,結(jié)構(gòu)體 S1 的內(nèi)存排列順序在未進(jìn)行對(duì)齊的情況下是這樣的。按結(jié)構(gòu)體的變量聲明順序排列,每個(gè)變量的容量大小按變量類(lèi)型的字節(jié)大小分配。
在數(shù)據(jù)對(duì)齊中,有 2 個(gè)重要的點(diǎn),第
- 假設(shè)變量的數(shù)據(jù)類(lèi)型的字節(jié)大小 k 。
- 對(duì)于數(shù)據(jù)類(lèi)型的字節(jié)大小為 k 的變量,這個(gè)變量的地址必須是 k 的倍數(shù)。
對(duì)于結(jié)構(gòu)體 S1, char c 分配 1 個(gè)字節(jié), int i [2] 分配 8 個(gè)字節(jié), double v 分配 8 個(gè)字節(jié)。
- 從結(jié)構(gòu)體對(duì)齊的角度來(lái)說(shuō),結(jié)構(gòu)體 S1 的第一個(gè)變量 c 的地址必須是結(jié)構(gòu)體 S1 的最大數(shù)據(jù)類(lèi)型 double 的字節(jié)大小的倍數(shù)。
- 結(jié)構(gòu)體 S1 的第二個(gè)變量是 int 類(lèi)型的 i[0],根據(jù) “對(duì)于數(shù)據(jù)類(lèi)型的字節(jié)大小為 k 的變量,這個(gè)變量的地址必須是 k 的倍數(shù)” 的原則, i[0] 的地址必須是 4 的倍數(shù),所以需要在 c 之后增加 3 個(gè) 字節(jié)的位置之后再放置 i[0]。
- 結(jié)構(gòu)體 S1 的第二個(gè)變量是 int 類(lèi)型的 i[1],由于 i[0] 的地址是 4 的倍數(shù),而且 i[0] 占用 4 個(gè)位置,所以 i[0] 之后不需要增加額外的位置,可以直接放置 i[1]。
- 結(jié)構(gòu)體 S1 的第三個(gè)變量 v 是 double 類(lèi)型,所以 v 的地址需要是 8 的倍數(shù),i[1] 的地址是 8 的倍數(shù),而 i[1] 的容量是 4,所以需要再增加額外的 4 個(gè)字節(jié)之后再放置 v。
- 所以結(jié)構(gòu)體 S1 的內(nèi)存對(duì)齊格式如上圖所示。
總結(jié)
數(shù)組和結(jié)構(gòu)體是 C 語(yǔ)言編程過(guò)程中經(jīng)常使用到的數(shù)據(jù)類(lèi)型,對(duì)它們的理解越多越深,更能避免使用過(guò)程中的各種錯(cuò)誤。
參考
本文是華盛頓大學(xué)的公開(kāi)課 《 The Hardware / Software Interface 》的課程筆記,該課程的參考書(shū)籍是大名鼎鼎的 CSAPP 也就是《 深入理解計(jì)算機(jī)系統(tǒng) 》這書(shū)。文章截圖來(lái)源于課程,文章的內(nèi)容也參考了 CSAPP 的書(shū)本內(nèi)容。