第2篇:C/C++ 内存布局与程序栈

如果你对C/C++基本数据类型的内存模型没概念的话,可以先查看该传送门《开篇1:C/C++ 内存中的数据表示》,反正我觉得

  • 先掌握了基本数据的内存模型
  • 再理解计算机的寻址模型和程序的内存布局
    这样能够对C/C++内存管理方面的认知起到以小见大的效果。

可寻址模型和内存布局

我们知道,内存是由操作系统统一管理的,内存里面一个字节就等于8个二进制位,然后操作系统就为内存空间进行编号,这就是我们所说寻址模型。那么我们经常说的32位指的是什么呢?其实操作系统给内存编号最大只编号到2的32次方(即只能编42,9496,7296个地址编号),而每个编号逻辑上喜欢使用十六进制来表示,并且用于表示内存的具体位置。形式通俗点说就是4GB的内存大小。为什么32位x86的操作系统无法使用大于4GB的内存条的额外空间?原因就在这里。


计算机内存寻址模型

另外,像其他任何程序一样,BIOS和OS都需要内存(废话),此处为了表示计算机内存模型的完整性。我们都把所有内存相关的内存区域都一一列出了,对于程序员感兴趣的主要内存区域是代码段,数据段和字面量池和bss,堆栈和堆。


从程序的组织的方式来查看程序的内存布局

  • 代码段:程序的所有指令会存放在这个区域,这是已经编译后的机器码。
  • 字面量池是程序初始化时的一些字符串字面量,在程序中用于显示文字
  • 全局数据段:程序初始化时的常量和全局/静态的变量。C/C++ 用global/static声明的变量都存放在这个区域,对所有函数公开可见。
  • :这里保存的数据只是为了临时存储一些值而创建的,而我们可能在程序运行过程中可能会回收此内存。因为我们在程序执行期间不需要很长时间,所以使用C中的new或malloc这类内存分配程序来为我们所需的特定数据类型提供新的空间,并且随着我们要求越来越多的动态数据空间而该区域不断扩大,并且在内存中逐渐增长到更高的地址。
  • :当我们执行这些过程调用时,堆的基本特性是LIFO,存储着该程序“上下文”,它将从内存的高层地址开始,然后向另一个方向向下扩展。上下文其实就是程序中各个函数之间调用的先后顺序

这种典型的内存布局有一个比较有趣的地方,实际上栈向低层地址不断增长,动态数据会向高层地址增长,只要你的程序足够糟糕,例如用无止境的递归和不断抢占堆可用的空间,这两个货始终会碰面,这将是一件非常糟糕的事情。这是一种严重的错误,这种情况操作系统说它内存不足时,例如Windows臭名招嘱的蓝屏提示...!!

IA32平台的程序栈

让我们看一下ia-32体系结构的调用堆栈,我们将堆栈的底部放在内存的顶部,并将堆栈的顶部放在内存的底部。 这只是我们使用的约定,因为我就喜欢使用倒置的形式,也有人喜欢将栈顶定于为上方且栈底定义在下方,但如果没有显式标注高地址和低地址,那就“误人子弟”了。争论这些毫无意义。


唯一要记住的是栈是朝着内存低地址方向增长,iA32栈中有一个特殊的寄存器,称为esp。该寄存器始终指向堆栈的顶部元素,即放置在堆栈上的最后一个元素。

push操作

好的,所以我们要看的第一个堆栈操作是push指令,这里我们展示的是

pushl 寄存器名称 或  push 某个类型的指针

表示一个32位的值,并为其指定了要入栈的源寄存器或内存位置,基本上它是它会从该源获取值,无论它是寄存器还是内存位置都会推入到栈顶。它还会将栈指针递减4,为什么要减4,因为pushl刚好是4个字节,并且是超低地址方向增长的,因此栈指针递减,

如下图所示,现在栈指针指向内存中已将该值添加或复制到内存中的新位置。


pop操作

popl 寄存器名称 或  popl  某个类型的指针

popl指令将数据从堆栈中移出。在这种情况下,我们还为它提供了一个dst参数,以获取从栈中弹出的值,然后将该值放入某个内存地址指向的位置或CPU中的寄存器。


我们从堆栈顶删除某个值,并再次为该32位字的esp向上调整堆栈指针。

我们pop操作的时候是真的“删除”原先的值吗?
这个值并未删除,它仍然存在于内存中,只是我们不再引用它了。因为我们已经调整了堆栈指针,使其指向栈中的下一个值。 但是原先这些位的数据仍然驻留在原先的内存位置,只是程序不再解释解析这些位中的二进制码。 已经在某种意义上有效地删除了它们,因为我们可以回收该空间并将新数据压入栈并覆盖这些位。因此需要保留被弹出的数据,只需将它们拷贝到指定的位置即可。

让我们看看如何使用堆栈来跟踪过程调用,以及如何记住过程调用结束时需要返回的返回地址以及需要从该过程获取的返回值。

程序的过程调用概述

下面是一个过程调用的概述,而且是一个很简陋的例子,有经验的程序员可能已经看出很多漏洞了-_-b!!我说明在先这个例子仅仅起到抛转引玉的作用并且在最后通过该例子提出几个问题,而这些问题会在以后的文章里会详细得到解答,那么我们将从调用者和被调用者这两个程序开始。

  1. 调用者将设置一些参数,并在执行call指令后,该指令将控制流跳转到被调用被调用者的函数,之前在被调用者初始化的参数也一同传递给被调用者。


  2. 此时控制权在被调用者的函数中,被调用者会创建一些局部变量,在执行一些运算的操作,并且运算的结果设为一个返回值,该返回值是被调用函数返回给调用者函数的。


  3. 在被调用者函数执行return之前,要清理创建的局部变量,并回收空间,最后执行return指令以告诉CPU要把控制权交还给调用者函数。


  4. 并转到调用者函数原先执行点之后的下一条指令,由于调用者的下一条指令后没有其他指令了就开始清理空间,该空间最初用于设置参数所占用的空间都会被回收。此处,我们应该要清楚原先被调用者函数所占用的空间已被回收,并且调用者函数再执行后也会销毁自己,这就是调用过程设置。

以上的例子很简单,基本稍微有一些代码基础的读者不用看都知道,但我的目的是导出如下几个问题点。

  • 被调用者函数必须知道从哪里获取参数?
  • 被调用者必须知道从哪里获取返回地址
  • 调用者必须知道从哪里获取返回值?

由于调用和被调用方在同一个CPU上运行,因此它们当然使用该CPU中的同一寄存器,因此要有一种机制确保两者之间不会同时争夺CPU的资源。这种机制就是:

  • 如果调用者要使用某个寄存器,而刚好被调用者也需要使用该寄存器,调用者在交出该寄存器的控制权之前,它会先保存该寄存器(通常是一个地址),当这一步完成后,就将寄存器让给被调用者。
  • 同理,被调用者也可能会保存当前使用的寄存器的地址后,才让出寄存器的控制权。

这里也引出一个问题:究竟要赋予所有职责给调用者还是被调用者?或所有职责由两者共同承担?这就跟调用机制扯不上关系了,而是考验程序员如何合理设计函数的功能,明确函数之间的分工主次的问题了!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343