Linux下应用程序运行时的内存空间分布详细分析

首先来看一个简单的程序,引入一些基本概念:

#include <unistd.h>
#include <stdio.h>
 
int main()
{
    printf("%d\n", getpid());
  
    while(1)
	{

	}

	return 0;
}

第6行:getpid()函数可以获得当前进程的PID号,然后将当前进程的PID号打印出来。
第8行:通过while(1)这个死循环让程序一直运行而不退出。
在这里插入图片描述在Linux下有一个/proc/pid目录,在这个目录下保存了进程号为pid的进程运行时的所有信息,其中maps文件记录了程序运行过程中的内存空间情况:
第一列:一个段的起始地址和结束地址;
第二列:这个段的权限,r表示可读,w表示可写,x表示可执行,p表示受保护(只对本进程有效,不共享);
第三列:段内的相对偏移地址;
第六列:这个段内存放内容所对应的文件。

现在通过具体的程序分析应用程序运行时内存空间分布。
(1)bss段
bss段中存放没有初始化或初始化为0的全局变量,全局变量在程序运行的整个生命周期内都存在于内存中。但是bss段中的全局变量并不占用我们编译生成的可执行程序文件中的存储空间,只在可执行程序文件运行时占据内存空间,下面通过一个简单的程序来讲解为什么这样说:

#include <stdio.h>

int bssData[1024 * 1024];
 
int main(int argc ,char *argv[])
{
	while(1)
	{

	}
    
	return 0;
}

第3行:定义一个int类型的数组bssData,数组大小为1024 * 1024,但是没有对其进行初始化,由于是int类型的数组,数组中每一个数组项占据4字节数据存储空间,所以整个数组大小为4MB。
第7行:while(1)这个死循环是为了让程序一直运行而不退出,这样在后面才能查看程序运行过程中的内存空间情况。
在这里插入图片描述生成可执行程序文件的大小为7.2KB,说明bss段中的全局变量并不占用编译生成的可执行程序文件中的存储空间。
在这里插入图片描述可执行程序文件名为bss且第二列为rw-p的内存空间就是bss段;
0x804b000 - 0x804a000 = 0x1000 = 4KB,bss段中的全局变量在可执行程序文件运行时占据内存空间,但是我们可以看到只占据了4KB的内存空间。

(2)data段
data段就是静态数据段,又称之为全局数据段,存放初始化不为0的全局变量、静态变量(初始化/未初始化的全局静态变量/局部静态变量)和常量(例如字符串常量)。
data段中存放初始化不为0的全局变量,全局变量在程序运行的整个生命周期内都存在于内存中,但是data段中的全局变量既占用我们编译生成的可执行程序文件中的存储空间,在可执行程序文件运行时也占据内存空间。

#include <stdio.h>

int bssData[1024 * 1024] = {1};
	 
int main(int argc ,char *argv[])
{
	while(1)
	{

	}
   
	return 0;
}

第3行:定义一个int类型的数组bssData,数组大小为1024 * 1024,数组中第0个数组项的初始值设置为1,数组中其他数组项的初始值默认设置为0,数组的地址是连续的,所以数组中只要有一个数组项位于data段中,那么数组的其他数组项也必然位于data段中。
第7行:while(1)这个死循环是为了让程序一直运行而不退出,这样在后面才能查看程序运行过程中的内存空间情况。
在这里插入图片描述生成可执行程序文件的大小为4.1MB,说明data段中的全局变量占用编译生成的可执行程序文件中的存储空间。
在这里插入图片描述可执行程序文件名为data且第二列为rw-p的内存空间就是data段,它与bss段在内存中实际上是共用一段内存的,不同的是bss段中的全局变量不占用编译生成的可执行程序文件中的存储空间,data段中的全局变量占用编译生成的可执行程序文件中的存储空间。
0x844b000 - 0x804a000 = 0x401000 = 4MB,data段中的全局变量在可执行程序文件运行时占据内存空间,我们可以看到确实占据了4MB的内存空间。
注意:

char str[] = "abc"
char *p = "abc"

第1行:定义一个字符数组str来保存"abc"这个字符串。
从内存分配上分析:
定义一个字符数组str来保存"abc"这个字符串就是在栈区中分配一块保存"abc"这个字符串的内存空间。

第2行:“abc"表示定义一个字符串常量"abc”,char *p表示定义一个字符指针p,来指向这个字符串常量。
从内存分配上分析:
定义一个字符串常量就是在静态数据区中分配一块保存字符串常量"abc"的内存空间,定义一个字符指针p就是在栈区中分配一块内存空间来保存字符指针p的值,char *p = “abc”
表示。

(3)rodata段
rodata段就是只读数据段,在rodata段中存放const修饰的全局变量,而const修饰的局部变量保存在栈区中,并不是所有的常量数据都存放在rodata段中,特殊情况如下:
①有些立即数与指令编译在一起被直接放在text段中;
②编译器会去掉从重复的字符串常量,让程序中的每个字符串常量只有一份;
③有些系统中rodata段的多个进程共享的,目的是提高内存空间的利用率。

#include <stdio.h>

const int DAYS_IN_WEEK = 7;
 
int main(int argc ,char *argv[])
{
	while(1)
	{

	}
    
	return 0;
}

常量一旦被创建后其值就不能再改变,所以常量在定义的同时必须对其进行初始化,后面对常量的任何赋值行为都将引发错误,创建常量的格式为:
const int DAYS_IN_WEEK = 7和int const DAYS_IN_WEEK = 7都是可以的。
第3行:定义一个常量DAYS_IN_WEEK,初始值设置为7。
第7行:while(1)这个死循环是为了让程序一直运行而不退出,这样在后面才能查看程序运行过程中的内存空间情况。
在这里插入图片描述生成可执行程序文件的大小为7.2KB,rodata段中的常量占用编译生成的可执行程序文件中的存储空间。
在这里插入图片描述可执行程序文件名为rodata且第二列为r–p的内存空间就是rodata段,它只能被读取,从而提高程序的稳定性。

(4)text段
test段就是代码段,在代码段中存放程序的代码和部分整数常量(在rodata段那一部分我们讲过,特殊情况下有些立即数与指令编译在一起被直接放在text段中),text段是可以执行的,所以他不能被不同的进程共享。
在这里插入图片描述可执行程序文件名为rodata且第二列为r-xp的内存空间就是text段,p表示text段是受保护的,是不能被修改的。

在Linux PC中输入man malloc命令查看malloc()函数的简单描述:
在这里插入图片描述参数:分配内存空间的大小,以字节为单位
返回值:如果分配成功则返回内存空间的起始地址,但是返回的这个被分配内存空间的起始地址是一个void *类型的指针,所以返回的时候需要将其强制转换成指定类型的指针;如果分配失败则返回NULL(NULL也是指针,#define NULL ((void *)0))。
下面用一个例子来具体讲解malloc()函数的使用:

#define NULL ((void *)0)
	
int *pi = (int *)NULL;
pi = (int *)malloc(sizeof(int));
memset(pi, 0, sizeof(int));
free(pi);

第1行:宏定义后NULL来代替((void *)0),void *表示未确定类型的指针。
第3行:定义一个int类型的指针变量pi,将NULL强制转换为int *类型后赋值给pi。
第4行:使用malloc()函数来分配内存空间时要指定大小,sizeof(int) = 4个字节,然后就会在内存中找到一块4个字节大小的内存空间,最后将这块4个字节大小的内存空间的起始地址强制转换为int *类型后赋值给pi。
第5行:malloc()函数只管分配内存,并不会对其进行初始化,也就是说使用malloc()函数分配一块内存空间后里面的值是任意的,所以我们可以使用memset()函数将这块内存空间全部中的值全部设置为0;
第6行:使用malloc()函数分配内存后,如果不再使用这块内存空间了则使用free()函数将这块内存空间给释放掉,否则就会造成内存泄漏。

(5)stack段和heap段
stack段(栈段)用来保存临时变量和函数参数。程序中的函数调用就是以栈的方式来说实现的,通常栈是向下增长(即向低地址)的,当向栈中push一个元素后栈顶指针就会向低地址移动,当从栈中pop处一个元素后栈顶指针就会向高地址移动。栈中的数据只在当前函数或下一层函数中有效,当函数返回后这些数据就会自动被释放,如果继续对这些数据进行访问将会发生未知的错误。
heap段(堆段)是最自由的内存空间,在程序中通过malloc()函数来申请内存空间,然后又可以通过free()函数来释放申请的内存空间,并且对heap段的使用没有大小的限制。

#include <stdlib.h>
 
int main(int argc ,char *argv[])
{
	int error = 0;
	int *pi = (int *)malloc(sizeof(int));
	
	while(1)
	{

	}

	free(pi);  
	return 0;
}

第5行:定义一个int类型的局部变量error,初始值设置为0。
第6行:定义一个int类型的局部指针变量pi,在C语言中通过malloc()函数在内存中找到一片指定大小(sizeof(int))的空间,然后将这个空间的首地址强制转换为int *类型后赋值给指针变量pi。
第8行:while(1)这个死循环是为了让程序一直运行而不退出,这样在后面才能查看程序运行过程中的内存空间情况。
int类型的局部变量error和int类型的局部指针变量pi都存放在stack段中,而malloc()函数在内存中找到一片指定大小的空间存放在heap段中。
第13行:将在内存中通过malloc()函数申请的内存空间释放掉,否则的话会造成内存泄漏。
在这里插入图片描述生成可执行程序文件的大小为7.2KB,堆栈段是一个动态存储空间,程序运行时局部变量和函数参数会动态的在堆栈段中存放和释放。
在这里插入图片描述文件名为stack的内存空间就是stack段,文件名为heap的内存空间就是heap段;
0x9e7e000 - 0x9e5d000 = 0x21000 = 132KB(一页的大小)
我们在内存中通过malloc()函数申请了4个字节数据的内存空间,但是在操作系统中内存是以页的方式进行管理的,所以还是一次给我们分配了一页的内存空间。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值