前言
時間與空間複雜度以後還會繼續深入,目前可以先擱置一邊。
資料結構與演算法中,最基本的兩個結構就是:陣列、連結串列。二者最大的區別在於:陣列儲存地址是連續的,連結串列儲存地址不是(完全)連續的。
陣列可以說的沒有多少,一是因為簡單;二是因為有很多文章都講的很詳細。所以我們不再贅述,本片對陣列引出的兩個問題進行探討:
- 棧的生長方向以及陣列的偏移
- 大小端問題
棧的生長方向及陣列偏移
引用一張 linux 程式記憶體結構的圖,其中 stack 棧的生長方向是向下生長的,也就是向低地址生長,即比如從 0x10008 - 0x00010。windows的棧也是向下生長。而我們都知道,陣列的元素和其對應的地址是往大增長的:
比如:int a[2] = {0}
, 其中陣列 a 的地址為0x00010,那麼a[1] 的地址應該是 0x00010 + 1 * sizeof(int)
。
正是因為這種區別,考慮到如下的 C 程式 :
#include <stdio.h>
int main() {
int i = 5;
int a[3] = {0};
int j = 6;
j = a[3]; // (1)
printf("j is : %d", j); // (2)
return 0;
}
複製程式碼
執行後,輸出為:
j is 5
為什麼?
因為上述程式在執行 (1)之前,由於(1)之前的變數都是區域性變數,所以儲存在棧中。而上文說過了,棧的生長方向是向下的,且陣列的元素地址是遞增的,所以(1)之前的記憶體(棧)佈局為:
所以,當陣列越界訪問 a[3]
時,就越界到了 i = 5
的位置,所以就會將 5 賦值給 j。
當然,實現上述結果的前提還有下面的兩點:
- i 和 陣列 a 型別相同,或者說二者的型別佔用位元組數相同
- 所選用語言沒有對陣列越界做出限制
這個程式還可以進行擴充套件,留到下次進行分析。
大小端問題
上面是陣列如何儲存的,那麼陣列中的某一個元素又是如何儲存的?比如 int i = 5
,其又是如何儲存的呢?
棧是一種資料結構,其本質是被作業系統管理的記憶體空間。變數 i 的儲存肯定不是依靠作業系統來完成的,那麼是誰呢?
我們寫好了某個語言的程式,程式需要轉換成機器語言即01010這種二進位制語言才能夠被計算機識別,這一過程可能是由編譯器來完成也可能是由翻譯器來完成。
比如 i 轉換成機器語言後可以用十六進位制表示成 “0x00000005”(展開就是01010二進位制),CPU需要讀取指令並執行,那麼當它收到這個數字時,又是如何解讀的呢?
假設 int 為 4 位元組大小,那麼一種解讀規則是:記憶體中的低位地址儲存 i 的高位位元組,即:
0x00 | 0x1000 |
---|---|
... | ... |
0x05 | 0x1003 |
這種儲存模式就稱為 大端儲存
而另外一種模式則反過來,如下表:
0x05 | 0x1000 |
---|---|
... | ... |
0x00 | 0x1003 |
這種儲存模式就稱作為 小端儲存
。
只有編譯器(直譯器)和 CPU 對於多位元組資料的儲存模式相統一,才能夠正確執行程式。
擴充閱讀
- 《深入理解計算機系統》
- 大小端
- 程式模型