【问题标题】:The relation of Stack, Memory Heap and assembly codeStack、Memory Heap和汇编代码的关系
【发布时间】:2019-01-22 01:25:34
【问题描述】:

我正在尝试更详细地了解 C/C++ 代码的编译过程及其内存管理。假设如下代码:

#include <iostream>

int main() {
    int a = 5;
    int *b = (int *) malloc(40);
    return 0;
}

我知道a 和b 将在堆栈上创建,b 的值(它指向的内存)将在堆上。

编译成汇编的代码如下所示:

    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    movl    $40, %eax
    movl    %eax, %edi
    movl    $0, -4(%rbp)
    movl    $5, -8(%rbp)
    callq   _malloc
    xorl    %ecx, %ecx
    movq    %rax, -16(%rbp)
    movl    %ecx, %eax
    addq    $16, %rsp
    popq    %rbp
    retq

我的问题是;

a is on the stack (memory) 在这里是什么意思?根据上面的汇编,a 直接嵌入到指令$5, -8(%rbp) 中,没有对内存位置的引用。如果它在内存中,那么a 的地址是什么?

我知道_malloc 在堆(内存)上创建 40 个字节并返回第一个内存地址,但我看不到堆栈是如何填充的,除了从它获取的指令本身之外,这里没有与内存的交互.

【问题讨论】:

  • "$5, -8(%rbp) - 没有引用..." - 好吧,那么猜猜 "-8(%rbp)" 是什么,这是对堆栈区域和值的引用 @ 987654331@ 正在存储到内存中。 (这是 AT&T 语法)...a 的内存地址是rbp-8。顺便说一句,这是特定于编译器实现的所有内容,并且特定于编译选项,因此编译器可以决定以完全不同的方式编译它(但您没有打开优化,因此默认情况下它使用 for-debugging 方式,将所有值放入内存调试器可以在“watches”窗口和类似窗口中找到它们)
  • 你提到了memory address of a is rbp-8,如果我使用像 GDB 这样的调试器并实际打印出值,我会以0x7ffeefbffac8 为例。什么是将此地址分配给a?操作系统?
  • 当进程启动时,它会从操作系统获取初始堆栈空间(由rsp 寄存器指向)......从那里通过 CRT 初始化代码等......你最终会到达你的代码,其中rbp 相对于当前rsp 设置,subq $16, %rsp 保留16字节的堆栈空间,a 也将降落,所以基本上是的,操作系统分配堆栈地址和所有然后基于堆栈的操作从该初始地址派生。 (下次运行可能会有所不同......同样,如果启用了 ASLR 并且您的可执行文件是 PIE,则代码本身可能位于其他位置)
  • 函数入口时a与当前堆栈位置的相对位置然后由编译器在编译时分配,即它计算出a至少需要4个字节,对于a至少需要8个字节b,4 个字节作为返回值,总共 16 个字节,然后它会对齐以确保堆栈指针将按照 ABI 的要求对齐(在这种特殊情况下再次获得 16),因此它确实会生成执行 rbp= 的代码rsp, rsp-=16 和 &a=rbp-8 .. 这是编译时任务。它将落在物理计算机内存中的位置取决于函数入口时的当前rsp。

标签: c++ assembly stack heap-memory


【解决方案1】:

a is on the stack (memory) 在这里是什么意思?

这意味着由变量命名的对象存储在称为堆栈的内存部分,也称为调用堆栈。调用堆栈包含函数的局部自动变量。

movl    $5, -8(%rbp)

rbp 是帧指针。它指向调用 stack 的当前帧。该指令将常量 5 移动到 rbp 指向的内存中,偏移量为 -8 字节。也就是说,这条指令初始化了栈上的变量a。

如果它在内存中,那么 a 的地址是什么?

a 的地址似乎是rbp - 8,其中rbp 是存储在帧指针中的地址。在C++领域,可以使用addressof操作符来获取地址。


stack(与内存有关)、heap、frame、rbp 等均未定义通过 C++ 语言。这些词在特定 CPU 架构的上下文中具有意义。

【讨论】:

    【解决方案2】:

    语言中没有任何内容表明a 将在堆栈中。编译器有权将它放在任何它想要的地方;特别是,如果它不需要把它放在任何地方,它就不必。

    变量a 在您将 5 放入其中后就不再使用,因此一个体面的优化器会完全抛弃该行。

    但是,在您的特定情况下,在我看来 a 确实在堆栈中。它位于 %rbp 中帧指针的 -8 个字节处。也就是说a的地址是-8(%rbp),也就是“rbp寄存器中的值,负8”,非常接近栈顶。

    为了更详细。堆栈存在于内存中。让我们假设堆栈在内存中向下增长(这是常规的),因此堆栈的“顶部”向更小的地址增长。有一个“堆栈指针”指向堆栈的顶部,即最后添加到堆栈中的地址(我这里是笼统地说)。

    要分配 N 字节的堆栈空间,只需从堆栈指针中减去 N。生成的代码通常在一条指令中执行此操作,因为它知道输入函数所需的总空间。要释放该空间,代码可以 (a) 添加 N,或 (b) 恢复堆栈指针的减法前值,该值已保存在某处。选择实际上是定义了编译器系统想要如何管理堆栈的更详细的细节。

    在堆栈管理中可能会使用一些辅助寄存器。一个常见的就是有一个“帧指针”,这就是 %rbp 在这里所扮演的角色。

    【讨论】:

    • 那么我不明白所有这些命名的目的,例如“heap”、“stack”和“register”。您是在对编译器说这些都只是“空间”和“堆”,“堆栈”实际上不是物理的东西吗?
    • @Joe 恰恰相反,一切都是纯物理的。如果你在rbp中输入了一些值,你确实覆盖了CPU内部的特定64位,如果你将其他值写入rbp,你将丢失之前的值,因为CPU中只有一个rbp寄存器(嗯,实际上这在现代 x86 CPU 中并非如此,但从代码逻辑的角度来看,它是“只有一个”,真正的实现是“5 层”更复杂,但这些都不应该直接观察到运行代码)... 编辑:但我认为您期望 Intel 语法,因此没有看到 AT&T 内存引用?
    • 当然是堆栈。正在操作 %rsp 和 %rbp 的指令正在堆栈上创建空间,堆栈存在于内存中;使它成为堆栈的是软件如何以后进先出的方式使用它。堆存在于内存中;使它成为堆的原因是软件如何使用它,需要显式分配和释放。寄存器是机器中不同的东西,通常速度更快,有时也有专门的用途。
    • OP 清楚地在禁用优化的情况下编译:这就是函数将 RBP 设置为帧指针的原因,以及局部变量没有被优化掉的原因。 (以及为什么首先将 malloc 的 arg 复制到 EAX,然后再复制到 RDI,这是 x86-64 System V 的第一个整数 arg 传递寄存器。)gcc -O0 创建用于一致调试的 asm,其中每个 C++ 变量都有一个地址,并且当在任何行的断点处停止时,可以由调试器修改。
    猜你喜欢
    • 2012-03-09
    • 1970-01-01
    • 2016-12-22
    • 1970-01-01
    • 1970-01-01
    • 2014-02-03
    • 1970-01-01
    • 1970-01-01
    • 2012-01-24
    相关资源
    最近更新 更多