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() 的参数

然后再按照相反的顺序出栈,从而完成整个进程。

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