C 語言中的進程空間(補充)

本文是對這篇文章的一個補充。

首先我們知道:

  • 程序是靜態的
  • 源碼經過預處理、編譯、彙編、鏈接變成可執行文件,該可執行文件可以認爲就是程序
  • 可執行文件能夠多次執行,但並不意味着每次使用的資源是一樣的
  • 進程是動態的
  • 當程序加載到內存中開始運行,直到運行結束,這樣從開始到結束的過程就是進程
  • 程序位於存儲設備上,此時不叫做進程,當加載到內存上開始執行才轉變爲進程

進程空間

 上圖給出了進程空間的一個模型。在 C 語言中如果利用 %p 輸出變量地址的話,會出現該變量的對應地址,但其實該地址可能並不意味着真正的變量地址。通常情況下,程序到進程的過程爲:

上圖表明,硬盤上的程序和數據之間還隔着虛擬內存和 MMU(memory manager unit),硬盤上的程序通過虛擬內存獲得內存資源在虛擬內存上的一個映射,然後 MMU 再將虛擬內存中的資源映射到實際的內存單元,從而獲得了真實的內存資源,得以運行。

對於一個進程來說,會存在幾部分內容。

命令行參數和環境變量

#include <stdio.h>

int main(int argc, char *argv[], char **env)
{
    printf("The number of arguments is %d\n",argc);
    printf("******************\n");

    for(int i=0; i<=argc; i++)
    {
        printf("The %d argument is %s\n",i,argv[i]);
    }
    printf("******************\n");

    while(*env++)
        printf("%s\n",*env);
    printf("******************\n");

    return 0;
}

對於上邊的程序,在命令行中的運行結果爲:

C:\Users\wood\Desktop\Qtest\build-test-Desktop_Qt_5_8_0_MinGW_32bit-Debug\debug>test.exe 10 20 30
The number of arguments is 4
******************
The 0 argument is test.exe
The 1 argument is 10
The 2 argument is 20
The 3 argument is 30
The 4 argument is (null)
******************
APPDATA=C:\Users\wood\AppData\Roaming
AWE_DIR=D:\Awesomium_SDK\1.6.6\
...
******************

從上邊的程序中,我們可以看出:

  • 帶有參數的 main 函數可以在命令行中實現調用
  • argc 表示參數個數,是包含可執行程序在內的所有參數
  • argv 爲實際的參數,實際參數個數比 argc 多 1,可執行參數默認爲參數 0,null 默認爲最後一個參數
  • env 表示當前系統內的環境變量
  • 上邊所有的內容在程序運行時自動加載到進程空間中。

棧和堆

命令行參數下的是 stack 和 heap 空間,從箭頭指向上來看,stack 空間是往下壓棧的,而 heap 是往上壓棧的:

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

int main()
{
    int a;
    int b;
    printf("%p\n",&a);
    printf("%p\n",&b);

    char *p1 = (char *)malloc(100);
    char *p2 = (char *)malloc(100);

    printf("%p\n",p1);
    printf("%p\n",p2);

    return 0;
}

結果爲:

0060FEA4
0060FEA0
00F60E70
00F60EE0

從上邊的結果可以看出 b 的地址比 a 的地址低,p2 的地址比 p1 的地址高。同樣進行函數調用時,函數內部變量也會加入壓棧的順序。

同時如果會使用到外部的函數庫的話,還會將函數庫的內容加載到內存中,對應在 stack 和 heap 之間的一段區域。

未初始化數據段,初始化數據段,代碼段

#include <stdio.h>

int a;
int b = 10;

int main()
{
    static int c;
    static int d = 20;
    char str[10] = "hello";
    int arr[5] = {1,2,3,4,5};
    
    return 0;

}

 對於上邊程序中存在的變量:

  • 未初始化的普通全局變量和靜態變量存儲在 uninitialized data,如變量 a,c
  • 初始化的普通全局變量和靜態變量存儲在 initialized data 中的 rw 段(read-write),如變量 b,d
  • 字符串常量存儲在 uninitialized data 中的 ro 段(read-only),如 “hello”
  • 還有一些常量存儲在 text 段,如 {1,2,3,4,5}

歸納爲:

變量 進程空間分佈 說明
  Command-line arguments and environment variables 命令行參數和環境變量
普通局部變量 stack
  dynamic lib 動態庫
     
alloc 函數申請空間 heap

普通全局變量

靜態變量

未初始化 uninitialized data 未初始化數據段
初始化 initialized data(rw) 初始化數據 rw 段
常量 initialized data(ro) 初始化數據 ro 段
text 代碼段

函數調用時的壓棧和出棧

#include <stdio.h>

int func(int x)
{
    return x;
}

int foo(int x)
{
    return func(x);
}

int main()
{
    int a = 10;
    printf("%d",foo(a));

    return 0;
}

在上邊的程序中,先是 main 函數發生調用,然後在 main 函數中又調用函數 foo,之後在函數 foo 中又調用 func。整個過程會發生一系列的入棧和出棧,具體順序爲:

調用 main 函數時,入棧:

  • 操作系統運行狀態
  • 返回地址
  • main() 參數

調用 foo 函數時,入棧:

  • main() 運行狀態
  • 返回地址
  • foo() 的參數

調用 func 函數時,入棧:

  • foo() 運行狀態
  • 返回地址
  • func() 的參數

然後再按照相反的順序出棧,從而完成整個進程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章