【专业技术】程序在内存中如何分配的?

好多初学者可能对程序在内存中如何布局都有疑问,在我们和用户的沟通过程中也发现有好多同学问相关的问题。这里转一个文章,讲得很不错的,大家可以看一下。

栈主要用来存放局部变量, 传递参数, 存放函数的返回地址.esp 始终指向栈顶, 栈中的数据越多, esp的值越小.

堆用于存放动态分配的对象, 当你使用 malloc , new 等进行分配时,所得到的空间就在堆中. 动态分配得到的内存附带有分配信息, 所以你能够 realloc 和 free调它们.

全局,静态和常量是分配在数据区中的。数据区包括bss和初始化区。

堆向高内存地址生长 栈向低内存地址生长 堆和栈相向而生,堆和栈之间有个临界点,称为stkbrk

CODE: 进程在内存中的影像.

我们假设现在有一个程序, 它的函数调用顺序如下: main(...) ->; func_1(...) ->; func_2(...) ->; func_3(...) 即: 主函数main调用函数func_1; 函数func_1调用函数func_2; 函数func_2调用函数func_3 。 当程序被操作系统调入内存运行, 其相对应的进程在内存中的影像如下图所示.: (内存高址) +--------------------------------------+ | ...... | ... 省略了一些我们不需要关心的区 +--------------------------------------+ | env strings (环境变量字串) | / +--------------------------------------+ / | argv strings (命令行字串) | / +--------------------------------------+ / | env pointers (环境变量指针) | SHELL的环境变量和命令行参数保存区 +--------------------------------------+ / | argv pointers (命令行参数指针) | / +--------------------------------------+ / | argc (命令行参数个数) | / +--------------------------------------+ | main 函数的栈帧 | / +--------------------------------------+ / | func_1 函数的栈帧 | / +--------------------------------------+ / | func_2 函数的栈帧 | / +--------------------------------------+ / | func_3 函数的栈帧 | Stack (栈) +......................................+ / | | / ...... / | | / +......................................+ / | Heap (堆) | / +--------------------------------------+ | Uninitialised (BSS) data | 非初始化数据(BSS)区 +--------------------------------------+ | Initialised data | 初始化数据区 +--------------------------------------+ | Text | 文本区 +--------------------------------------+ (内存低址)

这里需要说明的是:

i) 随着函数调用层数的增加, 函数栈帧是一块块地向内存低地址方向延伸的. 随着进程中函数调用层数的减少, 即各函数调用的返回, 栈帧会一块块地 被遗弃而向内存的高址方向回缩. 各函数的栈帧大小随着函数的性质的不同而不等, 由函数的局部变量的数目决定.

ii) 进程对内存的动态申请是发生在Heap(堆)里的. 也就是说, 随着系统动态分 配给进程的内存数量的增加, Heap(堆)有可能向高址或低址延伸, 依赖于不 同CPU的实现. 但一般来说是向内存的高地址方向增长的.

iii) 在BSS数据或者Stack(栈)的增长耗尽了系统分配给进程的自由内存的情况下, 进程将会被阻塞, 重新被操作系统用更大的内存模块来调度运行. (虽然和exploit没有关系, 但是知道一下还是有好处的) iv) 函数的栈帧里包含了函数的参数(至于被调用函数的参数是放在调用函数的栈 帧还是被调用函数栈帧, 则依赖于不同系统的实现), 它的局部变量以及恢复调用该函数的函数的栈帧(也就是前一个栈帧)所需要的 数据, 其中包含了调用函数的下一条执行指令的地址.

v) 非初始化数据(BSS)区用于存放程序的静态变量, 这部分内存都是被初始化为零的. 初始化数据区用于存放可执行文件里的初始化数据. 这两个区统称为数据区.

vi) Text(文本区)是个只读区, 任何尝试对该区的写操作会导致段违法出错. 文本区 是被多个运行该可执行文件的进程所共享的. 文本区存放了程序的代码.

2) 函数的栈帧. 函数调用时所建立的栈帧包含了下面的信息:

i) 函数的返回地址. 返回地址是存放在调用函数的栈帧还是被调用函数的栈帧里, 取决于不同系统的实现.

ii) 调用函数的栈帧信息, 即栈顶和栈底.

iii) 为函数的局部变量分配的空间

iv) 为被调用函数的参数分配的空间--取决于不同系统的实现.

另外: 返回值即使放在栈中也未必不行。因为每个进程拥有自己的栈空间,只要在其它函数运行之前,把返回值取出来就行。栈中的数据一般不会被自动销毁,栈指针动了一下而已,数据还在那里。

返回值如何实现取于编译器和采用的编译规则, 其中并没有通用的标准. 比如要从寄存器返回, 但不同体系的机器,其寄存器是不同的, 无法统一. Stroustrup 说有些系统中c++是解析的, 那么这些c++的解析实现采用的返回方式与编译实现采用的返回方式可能也不同. 所以我们只能讨论范围限制在某种特定机器上的某个编译器上.在这里,我们约定是x86/gcc3 (linux)

当返回值能容纳在一个寄存器中时, 通常都用一个寄存器返回.这是没有问题的. 当返回值足够小能容纳在两个寄存器中, 比如 edx:eax时, 通过这两个寄存器返回. 当要求返回的对象比较大时, 比如 x = foo (), 而 sizeof (x) 比较大, 则调用者将 x 的地址通过栈传递给被调用函数 foo, foo 把返回值写到 x 中. 有点象这样:

将 x = foo (); 转化为 (void) foobar (&x); 再强调一次, 如何返回并不是c的一个组成部分. c标准是抽象的, 并不关心"实际如何返回". 实际如何返回, 是编译器的事.

视情况的不同, 调用者在调用前, 也许要为被调用者在堆栈中提供一些空间,供被调用者使用. bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小。

data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-10-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

【干货】Python自动化审计及实现

本文由马哥教育Python自动化实战班导师wayne推荐,作者为SSS团队,转载自互联网,内容略经小编改编和加工,观点跟作者无关,最后感谢作者的辛苦贡献与付出。...

393100
来自专栏林德熙的博客

win10 UWP 序列化 BinaryXML序列化

将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新...

9720
来自专栏IT技术篇

gcc 编译参数 -fno-strict-aliasing

  最近在项目中遇到一个问题,当使用double类型数据时,在进行jce编解码后会出现乱数据问题,比如encode一个数据.

659270
来自专栏我是攻城师

理解Java里面的代理模式

代理模式是23种设计模式中非常经典的一种模式,在日常生活中到处充满了代理模式的痕迹,常见的比如火车代售点买票,各种公共服务大厅,以及各种网上购物平台其实都可以看...

44110
来自专栏木木玲

设计模式 ——— 状态模式

12820
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第4章 4.2 ruleflow-group&salience

ruleflow-group 在使用规则流的时候要用到ruleflow-group属性,该属性的值为一个字符串,作用是将规则划分为一个个的组,然后在规则流当中通...

25570
来自专栏java达人

多线程设计模式解读1—Guarded Suspension(保护性暂挂模式)

大家好,今天我们给大家介绍一个多线程设计模式的一个概念,我们平时业务代码写得比较多,因此,如果刚上手写比较复杂多线程代码,很有可能会埋下一些坑,而这些坑一时之间...

10440
来自专栏hbbliyong

设计模式名录

本文给出了经典的23种设计模式的名录,包括他们的分类、名称、定义以及简要说明,方便大家能够快速的回忆起他们。也是前面写过的或者后面将要写的设计模式的一个目录。...

29370
来自专栏炉边夜话

JNI设计实践之路

本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。本文中的示例使用 Sun公司的 Java Development...

18630
来自专栏C/C++基础

C/C++ volatile

volatile是“易变的”、“不稳定”的意思。volatile是C的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现读取错误的问题。

15130

扫码关注云+社区

领取腾讯云代金券