本文是對這篇文章的一個補充。
首先我們知道:
- 程序是靜態的
- 源碼經過預處理、編譯、彙編、鏈接變成可執行文件,該可執行文件可以認爲就是程序
- 可執行文件能夠多次執行,但並不意味着每次使用的資源是一樣的
- 進程是動態的
- 當程序加載到內存中開始運行,直到運行結束,這樣從開始到結束的過程就是進程
- 程序位於存儲設備上,此時不叫做進程,當加載到內存上開始執行才轉變爲進程
進程空間
上圖給出了進程空間的一個模型。在 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() 的參數
然後再按照相反的順序出棧,從而完成整個進程。