c語言在內存中的分佈

參考文章https://www.cnblogs.com/yif1991/p/5049638.html

存儲時結構

首先寫一個c語言版的Hello World

#include <stdio.h>
void main()
{
    printf("hello,world\n");
}

將該段程序寫在一個hello.c的文件中。打開終端,編譯hello.c生成一個a.out的文件。
這裏寫圖片描述

終端上展示的是一個c語言的可執行文件在不同的內存所佔的空間,text、data、bss分別代表內存中不同的區域,dec代表十進制總和,hex代表着16進制總和,filename代表着文件名。

text 代碼區。

  • 程序被操作系統加載到內存的時候,所有的可執行代碼(程序代碼指令、常量字符串等)都加載到代碼區,這塊內存在程序運行期間是不變的。代碼區是平行的,裏面裝的就是一堆指令,在程序運行期間是不能改變的。函數也是代碼的一部分,故函數都被放在代碼區,包括main函數。

  • 注意:“int a = 0;”語句可拆分成”int a;”和”a = 0”,定義變量a的”int a;”語句並不是代碼,它在程序編譯時就執行了,並沒有放到代碼區,放到代碼區的只有”a = 0”這句。

靜態區

  • 該區包含了在程序中明確被初始化的全局變量、靜態變量(包括全局靜態變量和局部靜態變量)和常量數據(如字符串常量)
int x = 99;
static x = 100; 

未初始化數據區

  • BSS區(uninitialized data segment)
  • BSS區的數據在程序開始執行之前被內核初始化爲0或者空指針(NULL)
int x;

以上爲可執行代碼存儲時結構。

運行時結構

運行時結構多個棧區和堆區。

這裏寫圖片描述

棧(stack)

  • 一種先進後出的內存結構,局部變量(自動變量)和函數形式參數都存儲在此,存儲的這個動作由編譯器自動完成,寫程序時不需要考慮。
  • 棧區在程序運行期間是可以隨時修改的。當一個自動變量超出其作用域時,自動從棧中彈出。
  • 每個線程都有自己專屬的棧。
  • 棧的最大尺寸固定,超出則引起棧溢出。
  • 變量離開作用域後棧上的內存會自動釋放。

下面這個例子可以看出地址分配規律。

#include <stdio.h>

int n = 0;
void test(int a, int b);

int main() {
    static int m = 0;
    int a = 0;
    int b = 0;
    printf("自動變量a的地址是:%d\n自動變量b的地址是:%d\n", &a, &b);
    printf("全局變量n的地址是:%d\n靜態變量m的地址是:%d\n", &n, &m);
    test(a, b);
    printf("main函數的地址是:%d", &main);
}

void test(int x, int y)
{
    printf("形式參數x的地址是:%d\n形式參數y的地址是:%d\n",&x, &y);
}

運行結果如下:
這裏寫圖片描述

  • 由運行結果可以知道局部變量和形參地址相似,存儲在一個內存區;全局變量和靜態變量地址相似,存儲在一個區。根據運行結果中a,b,x,y的地址值大小可以還原佔內存的內存分配原理。局部變量按照執行順序入棧,函數參數的入棧順序是從右到左。

這裏寫圖片描述

內存與指針

#include <stdio.h>
int *getx()
{
    int x = 10;
    return &x;
}

int main()
{
    int *p = getx();

    printf("%d\n", *getx());
    printf("%d\n", *p);

    *p = 20;
    printf("%d\n", *p);
}

這段代碼沒有任何語法錯誤,編譯也可以正常通過。因爲int *p = getx()中變量x的作用域爲getx()函數體內部,這裏得到一個臨時棧變量x的地址,getx()函數調用結束後這個地址就無效了。所以有關於這個地址的任何操作都不一定成立了。

  • 棧不會很大,一般都是以K爲單位。如果在程序中直接將較大的數組保存在函數內的棧變量中,很可能會內存溢出,導致程序崩潰。比較大的內存就要用到堆(heap)內存了。

堆(heap)

  1. 堆是一種在程序運行過程中可以隨時修改的內存區域,但沒有棧那樣先進後出的順序。更重要的是堆是一個大容器,它的容量要遠遠大於棧,這可以解決上面實驗三造成的內存溢出困難。一般比較複雜的數據類型都是放在堆中。
  2. 在C語言中,堆內存空間的申請和釋放需要手動通過代碼來完成。對於一個32位操作系統,最大管理管理4G內存,其中1G是給操作系統自己用的,剩下的3G都是給用戶程序,一個用戶程序理論上可以使用3G的內存空間。堆上的內存必須手動釋放(C/C++)

malloc與free

  • malloc函數用來在堆中分配指定大小的內存,單位爲字節(Byte),函數返回void *指針。
  • free負責在堆中釋放malloc分配的內存。malloc與free一定成對使用。
#include <stdio.h>
#include "stdlib.h"
#include "string.h"
void print_array(char *p, char n)
{
    int i = 0;
    for (i = 0; i < n; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }
}

int main()
{
    char *p = (char *)malloc(1024*1024*1024);//在堆中申請了內存
    memset(p, 'a', 10);//初始化內存
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        p[i] = i + 65;
    }
    print_array(p, 10);
    free(p);//釋放申請的堆內存
}

運行結果如下:
這裏寫圖片描述

改造佔內存中那個以指針作爲返回值的getx()的例子,改爲申請堆內存,即可完成需求,但一定用通過free函數釋放申請的堆內存空間。改造以後的函數如下:

#include <stdio.h>
#include "stdlib.h"
#include "string.h"
int *getx()
{
    int *p = (int *)malloc(sizeof(int));//申請了一個堆空間
    return p;
}


int main()
{
    int *pp = getx();
    *pp = 10;

    printf("%d\n", *pp);

    free(pp);
}

用來在堆中申請內存空間的函數還有calloc和realloc,用法與malloc類似。文章地址:https://www.cnblogs.com/lidabo/p/4611411.html

動態創建數組,則用堆。

#include <stdio.h>
#include "stdlib.h"

int main() {
    int len;
    int * arr;

    printf("請輸入數組長度:");
    scanf("%d", &len);
    arr = (int *)malloc(sizeof(int)*len);

    printf("請輸入數組的值:");
    for ( int i = 0; i < len; i ++) {
        scanf("%d", &arr[i]);
    }
    for (int j = 0; j < len; j ++) {
        printf("%d:%d ", j , arr[j]);
    }
    free(arr);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章