linux应用程序地址分布

学习linux时会经常遇到如下概念:代码段、数据段、BBS、堆、栈等,同时也会经常需要知道全局变量、局部变量有是怎么存放。本文的作用就是阐述自己对这些概念的认识。

先来说明linux中内存的分布情况和各个字段的作用,如下图所示是linux中内存中个组成部分示意图:

linux应用程序地址分布_第1张图片

Text:代码段,这一部分存储的是CPU执行的机器指令,这部分是共享的,也就是在存储中只会存在一份该正文段。同时,该段需要满足可执行和只读性,这样可以防止程序遭到意外的修改。

Data:初始化数据段,这一部分存储全局作用域中明确赋初值的变量。

BBS:未初始化数据段,存放全局作用域中没有明确赋初值的变量,在程序开始执行之前,内核会将这段中数据初始化为0或者空指针。

Heap:堆,用于动态存储的分配,是从低地址到高地址。什么叫动态存储?动态存储就是变量空间大小会随程序变化、创建和消失。这一部分相关操作函数有:malloc/calloc/realloc/free

Stack:栈,用于存放局部变量已经函数执行时所需要保存的信息。每次函数调用都会创建一个新的栈帧,因此每个函数调用的过程都是单独,这也是为什么局部变量的范围只局限该函数或者改代码块的原因。栈的地址是从改地址向低地址方向增长。


代码区所在的地址空间最低,往上依次是Data区和BSS区,并且Data区和BSS区在内存中是紧挨着的。


其中,Text和Data段都是位于可执行的文件中,编译时已经分配了空间,启动程序时会从源文件中读入。而BBS段则不占用可执行文件的大小只包含BBS段的大小,然后链接器根据这个大小在数据段后面分配一块相应大小的内存空间,并在内存进入程序时全部清空为零。


下面通过程序距离来说明一下,程序如下:

#include 
#include 

int global_init_a = 1; //init global variable
int global_uninit_a;  //uninit global variable
static int static_global_init_a = 1;  //static init global variable
static int static_global_uninit_a;  //static uninit global variable
const int const_global_a = 1;  //const global variable
char *global_p = "abcd";  //global pointer variable

int main()
{
    int local_init_a = 1;  //init local variable
    int local_uninit_a;  //uninit global variable
    static int static_local_init_a = 1;  //static init local variable
    static int static_local_uninit_a;  //static uninit local variable
    const int const_local_a = 1;  //const local variable
    char *local_p = "abcd";  //local pointer variable

    int *malloc_p_a;
    malloc_p_a = (int *)malloc(sizeof(int));  //heap
    
    printf("&global_init_a = %p, global_init_a = %d\n", &global_init_a, global_init_a);
    printf("&global_uninit_a = %p, global_uninit_a = %d\n", &global_uninit_a, global_uninit_a);
    printf("&static_global_init_a = %p, static_global_init_a = %d\n", &static_global_init_a, static_global_init_a);
    printf("&static_global_uninit_a = %p, static_global_uninit_a = %d\n", &static_global_uninit_a, static_global_uninit_a);
    printf("&const_global_a = %p, const_global_a = %d\n", &const_global_a, const_global_a);
    printf("&global_p = %p, global_p = %p, *global_p = %s\n", &global_p, global_p, global_p);

    printf("&local_init_a = %p, local_init_a = %d\n", &local_init_a, local_init_a);
    printf("&local_uninit_a = %p, local_uninit_a = %d\n", &local_uninit_a, local_uninit_a);
    printf("&static_local_init_a = %p, static_local_init_a = %d\n", &static_local_init_a, static_local_init_a);
    printf("&static_local_uninit_a = %p, static_local_uninit_a = %d\n", &static_local_uninit_a, static_local_uninit_a);
    printf("&const_local_a = %p, const_local_a = %d\n", &const_local_a, const_local_a);
    printf("&local_p = %p, local_p = %p, *local_p = %s\n", &local_p, local_p, local_p);
    
    printf("&malloc_p_a = %p, malloc_p_a = %d\n", &malloc_p_a, malloc_p_a);
    
    while(1)
        ;
    
    return 0;
}

说明:程序末尾加上while(1)的循环的目的是保持程序一直在运行,这样就可以通过ps获取当前的实际内存映射情况。

执行结果如下:

linux应用程序地址分布_第2张图片
然后去获取当前程序和库文件在内存中的映射区域及访问权限:

linux应用程序地址分布_第3张图片

由内存映射关系可知,只读和可执行数据区(Text&Data)的地址范围是:08048000 - 08049000; 可读写数据区(Data和BBS):08049000 - 0804a000; Heap: 08235000 - 08256000; Stack: bfb18000 - bfb2d000. 奇怪的是Text和Data以及Data和BBS合在一起,我们怎么确认那一部分是属于Text,那一部分属于Data,那一部分又属于BBS呢?

下面我们通过读取section-headers的信息来具体确定Data和BBS的具体内存空间:

linux应用程序地址分布_第4张图片

有这部分信息可以知道:Text的空间是: 08048300 - 08048638,只读Data的空间是:08048654 - 080488DB,可读写Data的空间是:08049a4c - 08049a60(08049a4c(addr) + 14(size)), BBS的空间是:08049a60 - 08049a74, 其中off表示该段数据在整个Data空间的起始偏移量,例如:08049a4c = 08049000 + 000a4c。

具体地址空间如下:

Text: 08048300 - 08048638

Rodata(Read only data, Data的一种): 08048654 - 080488DB

Data(可读可写Data): 08049a4c - 08049a60

BBS: 08049a60 - 08049a74

Heap: 08235000 - 08256000

Stack: bfb18000 - bfb2d000

下面我们根据执行结果来确认一下各个变量都在什么区域:

linux应用程序地址分布_第5张图片

总结如下:

read only data: 存放全局常量和字符串常量

data:存放初始化的全局变量和初始化的静态变量(全局和局部)

bbs:存放未初始化的全局变量和静态变量(全局和局部)

stack: 局部变量(初始化和未初始化的,但不包含静态变量)以及局部只读变量

heap: 动态分配的内存


你可能感兴趣的:(c语言,linux)