C语言程序在内存中是怎样布局的

理论

我们假设在32位Linux下进行编程,首先要明确,我们的虚拟地址空间有4G,4G的地址空间的寻址范围就是 0x0000_0000 ~ 0xFFFF_FFFF,在Linux下,高地址的1G内存是给操作系统使用的,也就是 0xC000_0000 ~ 0xFFFF_FFFF,这一段地址我们无法使用,可以使用的只有 0x0000_0000~0xBFFF_FFFF这一段地址。

其次要知道程序会分为那些段

  • .text 代码段:可执行程序代码(只读)
  • .rodata 只读数据段: 程序中的常量(只读)
  • .data 初始化数据段: 程序中已经初始化的全局变量。(可读可写)
  • .bss 未初始数据段: 程序中未初始的全局变量。(可读可写)
  • heap 堆区:程序中动态分配的内存。堆的内存向上增长。(可读可写)
  • 共享数据区:
  • stack 栈区:程序中的函数、函数的形参、函数的局部变量 。(可读可写)

我们的程序,编译为汇编文件的过程中,会有很多的节(section),链接器将这些目标文件中属性相同的节(section)合并成段(segment),因此一个段是由多个节组成的,我们平时所说的C程序内存空间中的数据段、代码段就是指合并后的segment。

为什么要将section合并成segment?这么做的原因也很简单,一是为了保护模式下的安全检查,二是为了操作系统在加载程序时省事。在保护模式下对内存的访问必须要经过段描述符,段描述符用来描述一段内存区域的访问属性,其中的S位和TYPE位可组合成多种权限属性,处理器用这些属性来限制程序对内存的使用,如果程序对某片内存的访问方式不符合该内存所对应的段描述符(由访问内存时所使用的选择子决定)中设置的权限,比如对代码这种具备只读属性的内存区域执行了写操作,处理器会检查到这种情况并抛出GP异常。

扯远了,那么我们的内存布局就可以看下面这个图:

请添加图片描述

其中多了一个区域 dynamic library 动态装载区,这个区域用于映射装载的动态链接库。在Linux下,如果可执行文件依赖其他共享库,那么系统就会为它在从0x40000000开始的地址分配相应的空间,并将共享库载入到该空间。

其中我们发现,蓝色的是只读的,橘色的是可读可写的数据,红色的是堆栈。其实其存放是按照权限来的,这也符合我们上面将的和操作系统对权限的检查相吻合。

实例

只讲理论是很难理清的,我们举个例子

我们写个c的程序

#include <stdlib.h>

int main(){
    
    
	int i = 0;
	while (1) {
    
    
		sleep(1000);
	}
	return 0;
}

编译一下

扫描二维码关注公众号,回复: 16145325 查看本文章
gcc temp.c -o temp.out -fno-pic -m32

其中 -no-pie 是不将程序地址随机化,-m32是将程序编译为32位程序,因为现在使用的都是64位系统,我们这里只讲32位,实际上本质是一样的。

执行这个程序

./temp.out

找到这个程序的pid

ps -C temp.out

得到程序的PID

➜  ~ ps -C temp.out
    PID TTY          TIME CMD
  33862 pts/2    00:00:00 temp.out

根据PID获取程序的内存映射

pmap -d 33862

得到内存映射关系

➜  ~ pmap -d 34204                      
34204:   ./temp.out
Address           Kbytes Mode  Offset           Device    Mapping
0000000008048000       4 r---- 0000000000000000 008:00005 temp.out
0000000008049000       4 r-x-- 0000000000001000 008:00005 temp.out
000000000804a000       4 r---- 0000000000002000 008:00005 temp.out
000000000804b000       4 r---- 0000000000002000 008:00005 temp.out
000000000804c000       4 rw--- 0000000000003000 008:00005 temp.out
00000000f7d25000     100 r---- 0000000000000000 008:00005 libc-2.31.so
00000000f7d3e000    1388 r-x-- 0000000000019000 008:00005 libc-2.31.so
00000000f7e99000     464 r---- 0000000000174000 008:00005 libc-2.31.so
00000000f7f0d000       4 ----- 00000000001e8000 008:00005 libc-2.31.so
00000000f7f0e000       8 r---- 00000000001e8000 008:00005 libc-2.31.so
00000000f7f10000       4 rw--- 00000000001ea000 008:00005 libc-2.31.so
00000000f7f11000      12 rw--- 0000000000000000 000:00000   [ anon ]
00000000f7f28000       8 rw--- 0000000000000000 000:00000   [ anon ]
00000000f7f2a000      16 r---- 0000000000000000 000:00000   [ anon ]
00000000f7f2e000       8 r-x-- 0000000000000000 000:00000   [ anon ]
00000000f7f30000       4 r---- 0000000000000000 008:00005 ld-2.31.so
00000000f7f31000     120 r-x-- 0000000000001000 008:00005 ld-2.31.so
00000000f7f4f000      44 r---- 000000000001f000 008:00005 ld-2.31.so
00000000f7f5b000       4 r---- 000000000002a000 008:00005 ld-2.31.so
00000000f7f5c000       4 rw--- 000000000002b000 008:00005 ld-2.31.so
00000000fff9c000     132 rw--- 0000000000000000 000:00000   [ stack ]
mapped: 2340K    writeable/private: 164K    shared: 0K

当然我们也可以换一个命令

➜  ~ cat /proc/33862/maps           
08048000-08049000 r--p 00000000 08:05 658197                             /home/lovetzp/Desktop/temp.out
08049000-0804a000 r-xp 00001000 08:05 658197                             /home/lovetzp/Desktop/temp.out
0804a000-0804b000 r--p 00002000 08:05 658197                             /home/lovetzp/Desktop/temp.out
0804b000-0804c000 r--p 00002000 08:05 658197                             /home/lovetzp/Desktop/temp.out
0804c000-0804d000 rw-p 00003000 08:05 658197                             /home/lovetzp/Desktop/temp.out
f7d25000-f7d3e000 r--p 00000000 08:05 569025                             /usr/lib/i386-linux-gnu/libc-2.31.so
f7d3e000-f7e99000 r-xp 00019000 08:05 569025                             /usr/lib/i386-linux-gnu/libc-2.31.so
f7e99000-f7f0d000 r--p 00174000 08:05 569025                             /usr/lib/i386-linux-gnu/libc-2.31.so
f7f0d000-f7f0e000 ---p 001e8000 08:05 569025                             /usr/lib/i386-linux-gnu/libc-2.31.so
f7f0e000-f7f10000 r--p 001e8000 08:05 569025                             /usr/lib/i386-linux-gnu/libc-2.31.so
f7f10000-f7f11000 rw-p 001ea000 08:05 569025                             /usr/lib/i386-linux-gnu/libc-2.31.so
f7f11000-f7f14000 rw-p 00000000 00:00 0 
f7f28000-f7f2a000 rw-p 00000000 00:00 0 
f7f2a000-f7f2e000 r--p 00000000 00:00 0                                  [vvar]
f7f2e000-f7f30000 r-xp 00000000 00:00 0                                  [vdso]
f7f30000-f7f31000 r--p 00000000 08:05 569021                             /usr/lib/i386-linux-gnu/ld-2.31.so
f7f31000-f7f4f000 r-xp 00001000 08:05 569021                             /usr/lib/i386-linux-gnu/ld-2.31.so
f7f4f000-f7f5a000 r--p 0001f000 08:05 569021                             /usr/lib/i386-linux-gnu/ld-2.31.so
f7f5b000-f7f5c000 r--p 0002a000 08:05 569021                             /usr/lib/i386-linux-gnu/ld-2.31.so
f7f5c000-f7f5d000 rw-p 0002b000 08:05 569021                             /usr/lib/i386-linux-gnu/ld-2.31.so
fff9c000-fffbd000 rw-p 00000000 00:00 0                                  [stack]

也可以得到内存映射关系。

从内存的分布中我们可以找到栈区,以及动态链接的一些库,以及头部的五个来自temp.out的区,这个内存映射分布与我们图并不是那么一致,是因为操作系统在处理segment段的时候,有自己的一些理解在里面,具体的就得看操作系统的实现了。

而且我们没有发现有堆区,这个我们可以在程序中加一个malloc,再看一下内存布局就有了。
库,以及头部的五个来自temp.out的区,这个内存映射分布与我们图并不是那么一致,是因为操作系统在处理segment段的时候,有自己的一些理解在里面,具体的就得看操作系统的实现了。

而且我们没有发现有堆区,这个我们可以在程序中加一个malloc,再看一下内存布局就有了。

猜你喜欢

转载自blog.csdn.net/weixin_43903639/article/details/131443538