【问题标题】:What happens in assembly language when you call a method/function?当您调用方法/函数时,汇编语言会发生什么?
【发布时间】:2010-12-07 19:03:27
【问题描述】:

如果我有一个 C++/C 程序(语言无关紧要,只需要说明一个概念):

#include <iostream>    

void foo() {
    printf("in foo");
}

int main() {
    foo();
    return 0;
}

程序集中会发生什么?我实际上并不是在寻找汇编代码,因为我还没有深入了解它,但基本原理是什么?

【问题讨论】:

  • 请注意,您不会在 c 中调用“方法”,而只是调用“函数”。这就是为什么认为 c 和 c++ 是同一种语言是没有意义的。
  • '语言无关紧要':编译语言的概念非常相似。解释性语言(如 php、javascript)可能完全不同。我相信您正在寻找编译语言的行为?
  • C++没有方法的概念,只有成员函数

标签: c++ c assembly function


【解决方案1】:

你可以自己看看:

在 Linux 下“编译”你的程序:

gcc -S myprogram.c

您将获得汇编程序 (myprogram.s) 中的程序列表。

当然,您应该了解一点汇编程序才能理解它(但它值得学习,因为它有助于理解您的计算机是如何工作的)。调用函数(在 x86 架构上)基本上是:

  • 将变量 a 入栈
  • 将变量 b 入栈
  • 将变量 n 放入堆栈
  • 跳转到函数地址
  • 从堆栈加载变量
  • 在函数中做事
  • 清理堆栈
  • 跳回主目录

【讨论】:

  • 我发现这个选项很有趣,但是有没有办法以 intel 风格而不是 at&t 风格生成汇编代码?
  • @Alex 评论来得太晚了(大约 6 年,但谁在乎 :-P),但您可以在 gcc 命令中添加 -masm=intel
【解决方案2】:

一般情况下是这样的:

  1. 函数的参数存储在堆栈中。按平台特定顺序。
  2. 返回值的位置在堆栈上“分配”
  3. 函数的返回地址也存储在堆栈或专用 CPU 寄存器中。
  4. 函数(或者实际上是函数的地址)被调用,通过 CPU 特定的 call 指令或通过普通的 jmpbr 指令(跳转/分支)
  5. 函数从堆栈中读取参数(如果有)并运行函数代码
  6. 函数的返回值存储在指定位置(堆栈或专用 CPU 寄存器)
  7. 执行跳回调用者并清除堆栈(通过将堆栈指针恢复为其初始值)。

上述细节因平台而异,甚至因编译器而异(参见例如 STDCALL 与 CDECL 调用约定)。例如,在某些情况下,使用 CPU 寄存器而不是在堆栈上存储内容。不过总体思路是一样的

【讨论】:

  • 第7步中函数的栈区真的被清空了,还是只是栈指针恢复到了之前的状态?似乎每次都将函数的剩余堆栈内存归零会相当低效......
  • 不,通常堆栈指针(通常只是一个 CPU 寄存器)会简单地恢复到其初始值(因此这是一条 CPU 指令)。我已经更新了我的答案以澄清这一点
  • 记住:在 x64 平台上只有单一的调用约定。
  • 不,大多数操作系统(使用 AMD 指定的约定)和 Windows(使用自己不同的约定)之间的 x64 调用约定是不同的。
  • 返回值呢,什么时候赋值给变量“等待”的返回值呢?如果返回值在函数堆栈上,这是否意味着在调用代码中“等待”返回值的变量在函数堆栈被清除之前被赋值?
【解决方案3】:

会发生什么?

C 模仿汇编中会发生的事情...

它离机器如此之近,你可以意识到会发生什么

void foo() {
    printf("in foo");

/*

db mystring 'in foo'
mov eax, dword ptr mystring
mov edx , dword ptr _printf
push eax
call edx
add esp, 8
ret
//thats it
*/

}

int main() {
    foo();
    return 0;
}

【讨论】:

  • 旁白:请停止您的其他垃圾邮件/巨魔帖子...它们将被删除。
【解决方案4】:

【讨论】:

    【解决方案5】:

    通常的想法是调用方法中使用的寄存器被压入堆栈(堆栈指针在ESP寄存器中),这个过程称为“压入寄存器”。有时它们也会归零,但这取决于。汇编程序员倾向于释放更多寄存器而不是常见的 4 个寄存器(EAXEBXECXEDX 在 x86 上),以便在函数中拥有更多可能性。

    当函数结束时,反过来也会发生同样的情况:堆栈恢复到调用之前的状态。这称为“弹出寄存器”。

    更新:这个过程不一定要发生。编译器可以优化它并内联你的函数。

    更新:函数的参数通常以相反的顺序压入堆栈,当它们从堆栈中检索时,它们看起来就像以正常顺序一样。 C 不保证此顺序。(参考:Rick Booth 的Inner Loops

    【讨论】:

      【解决方案6】:

      一般的想法是你需要

      1. 保存当前本地状态
      2. 将参数传递给函数
      3. 调用实际函数。这涉及将返回地址放在某处,以便RET 指令知道从哪里继续。

      具体情况因架构而异。更具体的细节可能因各种语言而异。尽管通常有一些方法可以在一定程度上控制这一点,以实现不同语言之间的互操作性。

      一个非常有用的起点是Wikipedia article on calling conventions。例如,在 x86 上,堆栈几乎总是用于将参数传递给函数。然而,在许多 RISC 架构中,主要使用寄存器,而堆栈仅在特殊情况下才需要。

      【讨论】:

        【解决方案7】:

        会发生什么?在 x86 中,主函数的第一行可能类似于:

        call foo

        call 指令会将返回地址压入堆栈,然后将jmp 压入 foo 的位置。

        【讨论】:

        • 干得好。如果问题是 call 指令是做什么的,那么它是真正处理这个问题的少数答案之一。
        【解决方案8】:

        1- 在栈上建立一个调用上下文

        2-参数入栈

        3- 对方法执行“调用”

        【讨论】:

          【解决方案9】:

          程序集中会发生什么?

          简要说明:保存当前堆栈状态,创建新堆栈并加载并运行要执行的函数的代码。这包括给微处理器的一些寄存器带来不便,对内存进行一些疯狂的来回读/写,一旦完成,调用函数的堆栈状态就会恢复。

          【讨论】:

            【解决方案10】:

            我认为您想查看调用堆栈以更好地了解函数调用期间发生的情况:http://en.wikipedia.org/wiki/Call_stack

            【讨论】:

              【解决方案11】:

              参数被压入堆栈并发出“调用”指令

              调用是一个简单的“jmp”,将指令地址压入堆栈(方法末尾的“ret”弹出并跳转)

              【讨论】:

              • 参数不一定被压入堆栈。这在很大程度上取决于架构、调用约定、编译器等......
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-07-12
              • 1970-01-01
              • 2021-07-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-09-26
              相关资源
              最近更新 更多