关于 Linux c 程序的五大内存分区的疑问

2020-11-09 11:01:11 +08:00
 fffang

一个典型的 C 程序的内存布局是以下几个分区(section)组成:

  1. 代码段(Text segment)
  2. 数据段(Initialized data segment)
  3. 未初始化的数据段(Uninitialized data segment)
  4. 堆(Stack)
  5. 栈(Heap)

对于这些分区的理解是:

代码段顾名思义储存的是二进制的可执行指令。数据段储存的是已初始化的内容,例如字符串,数字等,ex.char s [] =“ hello world”;。未初始化的数据段,声明一个全局变量但不赋值,将存储在 bss 段,static int i;。程序员 malloc/new 的区域会在堆上,堆可伸缩,同时会产生大量的碎片内存。栈的结构是 LIFO 的结构,存储着临时变量,指针,由 OS 进行管理。数据段和代码段是在可执行文件中,运行时由 OS 读取到内存中的。BSS 段不在可执行文件中,可执行文件中 BSS 段仅仅是一个数字,标记所有未初始化的变量占用空间的总和。在读取可执行文件时,OS 在数据段旁边,开辟这些区域并将它们赋值为 0 。

这是我的理解,有错误的地方还请指教。以下是我的问题:

如果一个未初始化的变量定义如,static int i;,然后用 malloc 进行分配,这块内存位于何处呢?还有如char s [];,从硬盘读取文件内容到内存,应该会在堆区,然后赋值给s,这块内存位于何处呢?

1098 次点击
所在节点    问与答
9 条回复
codehz
2020-11-09 11:36:05 +08:00
1. 全局变量不是从 malloc 获取的,malloc 拿到的是新的指针。。。
2. char s[]; 是非法的变量声明,直接编译不过
chinuno
2020-11-09 11:37:23 +08:00
1.静态变量编译好位置就是在 bss 里,malloc 要怎么给他分配? malloc 出来的是个指针,顶多取值给他赋值
2.char s[];你试试能不能编译。后面带字符串的情况下才可以省略长度,因为静态字符串数据以及固定了,编译的时候确定下来了。假设你制定了 s 的长度特别长够你用了,那你只能把堆里的文件数据复制到 s 本身的内存空间( bss 段)内
codehz
2020-11-09 11:52:34 +08:00
3. 硬盘读取文件内容到内存的过程,不会帮你分配内存,所以你可以直接传递一个够大的全局变量数组。。。
fffang
2020-11-09 11:56:06 +08:00
fffang
2020-11-09 11:57:43 +08:00
@chinuno
@codehz
想了下应该这么描述:定义一个未初始化的全局变量,那它显然应该在 BSS 段内,但是长度不确定,在运行时通过才能知道它的长度,这时会如何分配 BSS 段呢?
XiaoxiaoPu
2020-11-09 12:04:10 +08:00
@fffang 全局变量定义时就确定了长度,不可能运行时才确定长度
codehz
2020-11-09 12:06:31 +08:00
@fffang 不能做到,bss 段的东西长度必须是编译的时候已知的,没有任何例外。。。
但是这不妨碍你放一个指针,指针的大小是固定的,但指向的内容可以不确定大小(也不确定是哪个内存区域,或者无效)
这时候就可以 malloc 了
raaaaaar
2020-11-09 12:07:45 +08:00
有书专门讲这些东西么,要看内存管理还是啥,我以前也看过这个,不过不是很透彻
sockpuppet9527
2020-11-09 12:39:33 +08:00
1. malloc 分出来的东西是逻辑地址,给到你返回的是一个指向该逻辑地址的指针,具体硬件上的地区要经过 MMU 。
2. 硬盘读取的内容你可以放到堆,也可以放到栈。栈是要先声明长度,比如 char s[100]; ,这就是栈和堆最大的不同,栈的深浅是固定的,堆是动态的,就算到了汇编层面,你写一个方法,栈里面变量的使用也需要先声明长度,越了栈就违法了。

另外关于内存(逻辑地址)在何处,这个是动态的,参考一本内核书上的 hello world 例子:

```
08048368 <greeting>:
8048368: 55 push %ebp
8048369: 89 e5 mov %esp,%ebp
804836b: 83 ec 08 sub $0x8,%esp
804836e: 83 ec 0c sub $0xc,%esp
8048371: 68 84 84 04 08 push $0x8048484
8048376: e8 35 ff ff ff call 80482b0 <printf@plt>
804837b: 83 c4 10 add $0x10,%esp
804837e: c9 leave

804837f: c3 ret
08048380 <main>:
8048380: 55 push %ebp
8048381: 89 e5 mov %esp,%ebp
8048383: 83 ec 08 sub $0x8,%esp
8048386: 83 e4 f0 and $0xfffffff0,%esp
8048389: b8 00 00 00 00 mov $0x0,%eax
804838e: 83 c0 0f add $0xf,%eax
8048391: 83 c0 0f add $0xf,%eax
8048394: c1 e8 04 shr $0x4,%eax
8048397: c1 e0 04 shl $0x4,%eax
804839a: 29 c4 sub %eax,%esp
804839c: e8 c7 ff ff ff call 8048368 <greeting>
80483a1: c9 leave
80483a2: c3 ret
80483a3: 90 nop
```

你可以看到 main 的入口是 0x8048380,这其实是给到 CPU 的 EIP 寄存器使用的,指向的虚拟地址。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/723111

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX