資料結構與演算法系列(二):關於陣列,我想探討兩個問題

oatlmy發表於2019-02-13

前言

時間與空間複雜度以後還會繼續深入,目前可以先擱置一邊。

​ 資料結構與演算法中,最基本的兩個結構就是:陣列、連結串列。二者最大的區別在於:陣列儲存地址是連續的,連結串列儲存地址不是(完全)連續的。

​ 陣列可以說的沒有多少,一是因為簡單;二是因為有很多文章都講的很詳細。所以我們不再贅述,本片對陣列引出的兩個問題進行探討:

  • 棧的生長方向以及陣列的偏移
  • 大小端問題

棧的生長方向及陣列偏移

資料結構與演算法系列(二):關於陣列,我想探討兩個問題

引用一張 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 對於多位元組資料的儲存模式相統一,才能夠正確執行程式。

擴充閱讀

  • 《深入理解計算機系統》
  • 大小端
  • 程式模型

相關文章