資料的儲存區域

灰色v碰觸發表於2019-05-11

參考文章

程式的靜態儲存區,動態儲存區和堆以及棧的關係是什麼?

堆和棧的區別(轉過無數次的文章)

關於C語言中返回區域性變數和區域性指標變數

儲存區域

  1. 全域性區域(靜態區域,程式結束後由 os 釋放)

    • 全域性已初始化區域(已初始化全域性變數 + 未初始化靜態變數)
    • 全域性未初始化區域(未初始化全域性變數 + 未初始化靜態變數)
  2. 棧區(由編譯器自動分配釋放)

    • 函式引數、區域性變數、函式返回值等
  3. 堆區(由程式設計師分配釋放)

    • 通過 malloc、calloc、realloc 分配的記憶體
  4. 文字常量區(程式結束後由系統釋放 )

    • 常量字串就是放在這裡的

目前已知且自認為能夠理解的儲存區域就以上四種,其他的待補。

實踐證明

1. 全域性區域

在外部宣告的變數以及靜態變數是存放在全域性區域的。

已初始化全域性變數、已初始化靜態變數在同一個區域,如下範例將證明這一點:

int a = 10;
int a1 = 11;
int a2 = 12;

void main(void){
    printf("a = %p
" , &a);   // 假設 &a  = 0x000f24
    printf("a1 = %p
" , &a1);  // 則  &a1 = 0x000f28
    printf("a2 = %p
" , &a2);  // 則  &a2 = 0x000f2c
    
    // 以下是干擾的變數
    int b = 10;
    int b1 = 11;
    int b2 = 12;
    
    printf("b  = %p
" , &b);  // 可以檢視該部分干擾變數的地址
    printf("b1 = %p
" , &b1); // 和之前的全域性 a a1 a2 或 之後的 a3 a4
    printf("b2 = %p
" , &b2); // 變數地址完全不在同一頻道上
    
    // 靜態變數
    static int a3 = 13;
    static int a4 = 14;
     
    printf("a3 = %p
" , &a3); // 0x000f30,地址緊接在全域性變數a2地址後,證明統一區域
    printf("a4 = %p
" , &a4)  // 0x000f34
}

未初始化全域性變數、未初始化靜態變數在同一個區域,以下範例證明(驗證方式同上):

int a;
int a1;
int a2;

void main(void){
    printf("&a  = %p
" , &a);
    printf("&a1 = %p
" , &a1);
    printf("&a2 = %p
" , &a2);
    
    // 干擾變數
    int b;
    int b1;
    
    printf("
");
    
    static a3;
    static a4;
    
    printf("&a3 = %p
" , &a3);
    printf("&a4 = %p
" , &a4);
}

2. 棧區

函式引數,函式內部區域性變數在記憶體中的的棧區。

int test(int a , int b){
    // a,棧區
    // b,棧區
    // a + b 的結果,棧區
    return a + b;
}

void main(void) {
    int a = 10; // 棧區
    int b = 10; // 棧區
    const int a = 10; // 棧區
    int *p = &a; // 棧區
    int *const p1 = &a; // 棧區
    
    int c = test(); // 棧區
}

3. 堆區

使用 malloc、calloc、realloc 分配的記憶體空間屬於堆區。

void main(void) {
    int len = 2; // 棧區
    int *p = (int *)malloc(len * sizeof(int)); // p 在棧區,malloc 分配的記憶體在堆區!
    
    int i = 0; // 棧區
    
    for (; i < len; ++i)
    {
        printf("請輸入成績:");
        scanf_s("%d" , p + i); // p + i 指向的記憶體地址在堆區,資料儲存在堆區
    }
    
    // 輸出成績總和
    int c = 0; // 棧區
    
    for (i = 0; i < len; ++i)
    {
        // 輸出使用者輸入的成績
        printf("成績%d = %d
" , i , *(p + i));
        c += *(p + i);   // c 棧區,p 棧區, *(p + i) 堆區
    }
    
    printf("總成績 = %d
" , c); 
    
    system("pause");
}

應用

上述知識有什麼用呢??請看如下範例:

棧區 有關的

int * test(){
    int arr[2] = {1 , 2};
    
    return arr;
}

void main(void){
    int *p = test();
    
    printf("arr[0] = %d
" , *p);       // 1
    printf("arr[1] = %d
" , *(p + 1)); // -858993460
}

為什麼會出現這樣的現象呢??這就和記憶體的儲存區域有關了!

test 函式內的相關變數存放在棧區,所以他們由編譯器自動分配釋放,即:他們會在函式呼叫完畢後釋放,注意:釋放不是銷燬,只是放棄對棧區的使用權,即棧區記憶體區域允許被其他資料覆蓋!

test 執行完畢後,棧區釋放。第一次呼叫 printf 的時候,在還沒有進入 printf 之前獲取 *p 的值,此時棧區資料還在,沒有被其他資料覆蓋,然後被拷貝一份副本當做變元給 printf 當第一個引數,正確輸出,輸出完畢後,棧區被破壞(資料被覆蓋了,不然放著老資料佔用記憶體肯定是不行的),所以第二次呼叫 printf 輸出 *(p + 1) 的時候,結果未知,就是因為 p + 1 指向的棧區資料已經被銷燬了。


堆區 有關的,請看下面的範例:

int * test(void){
    int *p = (int *)malloc(2 * sizeof(int));
    
    *p = 1;
    *(p + 1) = 2;
    
    return p;
}

void main(void) {
    int *p = test();
    
    printf("*p = %d
" , *p); // 1
    printf("*(p + 1) = %d
" , *(p + 1); // 2
}

通過 malloc 分配的記憶體區域在堆區。所以在 test 函式呼叫完畢後,分配的 2 * sizeof(int) Byte空間沒有被釋放,因而通過 *p、*(p + 1) 能夠正確訪問資料。


文字常量區 有關的,請看下面的範例:

char * test(void){
    char *str = "test";
    return str;
}

void main(void) {
    char *p = test();
    
    printf("%s
" , *p);
    printf("%s
" , *p);
}

字串 "test" 是存放在 文字常量 區的,所以在 test 函式呼叫完畢後,其並沒有被釋放,因而兩次呼叫printf 都能正確輸出。

相關文章