RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 629285
Accepted
Andrew Kachalin
Andrew Kachalin
Asked:2020-02-18 00:16:12 +0000 UTC2020-02-18 00:16:12 +0000 UTC 2020-02-18 00:16:12 +0000 UTC

函数堆栈在 C++ 中实际上是如何工作的?

  • 772

亲爱的同事们!我想了解堆栈的工作原理,使用三个变量的函数示例。

 void f2() 
 {
        int B = 5;
        int *pB = &B; // задание адреса в указатель
        int &sB = B; //задание объекта в ссылку
        cout << "addres of  B = " << &B << "\n";
        cout << "value of  B = " << B << "\n";
        cout << "addres of  pB = " << &pB << "\n";
        cout << "value of  pB = " << pB << "\n";
        cout << "addres of  sB = " << &sB << "\n";
        cout << "value of  sB = " << sB << "\n";            
    }

    int main()
    {
        f2();
        system("PAUSE");
        return 0;
    }

输出:

addres of  B = 0038F644
value of  B = 5
addres of  pB = 0038F638
value of  pB = 0038F644
addres of  sB = 0038F644
value of  sB = 5

1.一线三线的误解。在地址空间上,我认为新变量的地址应该大于旧变量的地址。在实践中:

 `0038F638 - 0038F644 = -C;` 

这是向后移动 12 个位置。尽管其中一位经验丰富的程序员表示,移位将向前移动 4 个位置 - 按维度int,以字节表示。

2.地址sB——一般与地址重合B!而价值sB——与价值重合B!对我来说很明显,放入的值sB应该等于地址B。并且地址sB必须sizeof(int)相对于前一个变量的地址向前移动四个位置 ( )。

问题:

如何解释观察到的行为?相对于前一个变量,下一个变量的地址分配(实际上)如何?是否有可能以比我所做的更智能的方式打印堆栈?(我想将堆栈视为三个条目 -类型 - 值 - 地址)。

c++
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    AnT stands with Russia
    2020-02-18T00:59:18Z2020-02-18T00:59:18Z

    在地址空间上,我认为新变量的地址应该大于旧变量的地址。

    在大多数“传统”平台中,堆栈从上到下增长:从高地址到低地址。因此,即使编译器“按顺序”分配您的变量,如果“较新”变量的地址小于“较旧”变量的地址也不足为奇。

    但实际上,各个变量的放置是没有顺序的,你比较地址是没有意义的。

    在传统的实现中,一个函数的所有局部变量的内存是在函数开头的一个“堆栈帧”中一次性分配的。在这个堆栈框架内,编译器会在编译阶段制定一些局部变量位置的固定映射。同时,它可以(并且将会)以完全任意的方式在这个映射中安排局部变量,以对齐、内存节省等优化考虑为指导。等等 因此,您的变量声明顺序根本没有任何意义,两个“相邻”变量的地址之间的差异可以是任何大小和符号。

    地址 sB - 通常与地址 B 重合!

    “地址sB”是什么?sB- 关联。引用类型不是对象类型,概念上它不占用内存,也没有地址。C++ 语言中没有而且也不可能有“地址 sB”。声明后,int &sB = B;表达式&sB将准确给出地址B,这就是您正在观察的内容。Baddress 与 address相同这一点并不奇怪B。

    相对于前一个变量,下一个变量的地址分配(实际上)如何?

    没有特别的。正如编译器在这种特殊情况下所希望的那样,就这样吧。这些决定是由编译器根据在语言级别不可见的逻辑做出的。

    C++ 语言中的某些顺序仅存在(可能存在)在同一数组的元素之间或同一类的字段之间。

    是否有可能以比我所做的更智能的方式打印堆栈?(我想将堆栈视为三个条目 - 类型 - 值 - 地址)。

    在语言层面,当然不是。

    然后您可以研究编译器生成的调试信息的格式,以及您的实现提供(如果提供)用于访问此调试信息的 API。一切都会在那里。

    • 23
  2. Dmitri Chubarov
    2020-02-18T02:07:41Z2020-02-18T02:07:41Z

    我们将稍微看一下编译器是如何分配变量的。我将利用原始帖子没有说明作者使用哪个编译器这一事实,因此,正如数学家所说,在不失一般性的情况下,我们可以假设编译器是 gcc。

    让我们将原始帖子中的代码保存到一个文件prnlocal.cpp中,并添加到其中

    #include <iostream>
    
    using namespace std;
    

    并注释掉

    /* system("PAUSE"); */
    

    许多人认为 Intel 汇编代码比 AT&T 汇编代码更容易阅读,因此我们将使用以下命令生成汇编代码

    g++ -masm=intel -S prnlocal.cpp
    

    让我们获取一个文件prnlocal.s,我们在其中找到标签_Z2f2v- 这就是 C++ 对函数 f2 的名称进行编码的方式,该函数具有参数(void).

             ....
    _Z2f2v:
    .LFB971:
            .cfi_startproc
            ......
            mov     DWORD PTR [rbp-28], 5 ; вот это - наша инициализация B=5
            lea     rax, [rbp-28]
            mov     QWORD PTR [rbp-40], rax ; вот это - инициализация pB = &B
            ....
    

    让我们更详细地处理这里写的内容。在我们的架构中,堆栈是使用两个专用寄存器实现的:rbp 和 rsp。rbp 指向栈底,rsp 指向栈顶。

    我们的栈从大地址增长到小地址,也就是在正常情况下rbp > rsp。

    第一个有趣的运算符:

        mov     DWORD PTR [rbp-28], 5 
    

    在[rbp-28 ... rbp-25]位于address的4个字节中,需要写入数字5。这样一来,数字5将落入位于address的字节中rbp-28,其余三个字节将被初始化为0。

    下一个命令

        lea     rax, [rbp-28]
    

    将地址加载[rbp-28]到寄存器rax中。

    第三条语句将从寄存器中加载值rax到八个字节 [rbp-40...rbp-33]。

        mov     QWORD PTR [rbp-40], rax
    

    有趣的是,我们的程序不使用地址 [rbp-32...rbp-29] 处的字节。这是将 64 位变量放置在 8 的倍数的地址上的结果——所谓的数据对齐在 8 的倍数的地址上。

    结果我们得到了这样一个栈

            +0 +1 +2 +3 +4 +5 +6 +7
    rbp-40  44 F6 38 00 00 00 00 00
    rbp-32  ZZ ZZ ZZ ZZ 05 00 00 00  
    rbp-24
    rpb-16
    rbp-08
    

    这里,rbp-40变量的值放在地址处,变量的值放在pB地址处 。rbp-28B

    您还可以了解在汇编代码之间rbp-24和rbp从汇编代码中放置的内容,但这是一个单独的故事。

    • 9

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5