【问题标题】:how are structs passed as parameters in assembly结构如何在程序集中作为参数传递
【发布时间】:2015-07-16 00:44:14
【问题描述】:

结构体在汇编中如何作为参数传递?

由于结构的大小比正常情况大,各个字段是否按顺序传递?

如果是,它们是否像正常参数一样以相反的顺序排列?

cdecl 和 stdcall 有什么区别吗?

【问题讨论】:

  • 这是 x86 还是 x86_64,您使用的是什么汇编程序?
  • 它只是标准的 x86,虽然我对 x86_64 很好奇?
  • 之所以有所不同,是因为两者之间的调用约定不同,x86_64通过寄存器传递前六个整数参数(从左起)以 RDI、RSI、RDX、RCX、R8 和 R9 的顺序传递,而 x86 以相反的顺序在堆栈上传递。我没有非标准类型大小结构的答案。我会四处逛逛。
  • 显然结构是通过引用(通过指针)传递的,它是在 EAX/AX 中传递还是在堆栈中传递取决于编译器。显然,由于创建的非线程安全条件,大多数编译器不遵循关于类和结构的调用约定。这是一个讨论 gcc 和 Visual c++ 之间区别的链接X86 Assembly/High-Level Languages 它包含更多指向维基百科等的链接。
  • 您也许应该添加一个您正在考虑的示例代码 sn-p...您是在询问 C 结构和数组还是其他什么?

标签: assembly x86 stdcall cdecl


【解决方案1】:

在大多数情况下,结构体作为指向结构体开头的指针传递。

然后该函数将该指针加载到某个寄存器中,并通过它们的偏移量来寻址结构的字段。

【讨论】:

  • 澄清一下:这是你可能在汇编中这样做的一种方式(也是我推荐的方式)。这不是 C 的做法。 C 会将整个结构推下管道。在 C 中,最好将指针传递给结构而不是结构本身。
  • @EdwardFalk:有趣的说明:C ABI 通常通过引用返回大型结构(将指针作为隐藏的第一个参数传递)。
  • 好吧,我们在谈论组装,不是吗?在汇编中,一般情况下你可以为所欲为,但我描述的是最自然的方式。
【解决方案2】:

结构和数组一样,通过引用传递,因此作为参数:它们只是一个 32 位参数(指向第一个结构成员的指针), 在cdeclstdcall 的情况下,该指针被压入堆栈。

如果您通过传递数据结构,这意味着您必须将堆栈上的每个结构成员推送到被调用方,这对性能有很大影响——特别是对于大型结构。

myarray dword 300 dup(?)
push offset myarray

现在这个数组已经被引用(第一个成员的指针)推送了。

【讨论】:

  • "如果您通过值传递数据结构...",请注意 C 确实将结构作为值传递(实际上 C 通过值传递 everything,因为甚至考虑了数组衰减到指针,然后按值传递),即函数不能修改原来的。
  • 错了。结构永远不会通过引用传递;这样做会完全违背使用指向结构(或 C++ 引用)的显式指针的目的。就像 hyde 所说,你从来没有真正“传递”一个数组,只有一个指向它的第一个成员的指针。这只是数组的一个怪癖,不适用于其他任何东西。如果您看到其他情况,这可能是您的编译器引入的优化,它注意到它可以在不破坏语言语义的情况下做到这一点。
  • @Martin:当 C 按值传递结构时,Windows x64 确实通过隐藏引用传递结构。 godbolt.org/z/hnxdTa。 (请注意,它为 arg 指向的命名本地复制了一份副本,因为调用者“拥有”该内存。这是一个错过的优化,因为调用者的副本在调用后已死,它可以让被调用者销毁它,并且没有其他线程引用它。)所以无论如何,它不是对调用者原始对象的引用,我认为这是你的观点。其他调用约定,如 x86-64 System V 将整个结构复制到堆栈。
【解决方案3】:

在汇编中,所有的赌注都是关闭的,你可以以任何你喜欢的方式传递参数,只要调用者和被调用者就如何完成达成一致。

将参数放入堆栈,将指向它们的指针放入堆栈,将它们放入寄存器,将它们存储在固定的内存位置,这一切都取决于你。我见过一些参数在寄存器中传递的情况,而另一些是在堆栈上传递或通过引用传递的。

您如何转移控制权也取决于您。执行“调用”指令或软件中断。旧的 PDP-10 架构有五种不同的调用子程序的方式,你必须知道使用哪一种。 IBM-360 架构也有多种方式。

(你想看疯狂吗?阅读著名的Interrupt List,它收集了所有可用于 286 架构的已知软件中断调用。实际上,您在 MS-DOS 下安装的每个软件都添加了一些新的软件中断到底池,他们每个人都有自己的跟注约定,其中许多相互冲突。)

一般来说,最好的方法是找出其他程序员在做什么,然后做同样的事情。要么这样,要么很好地记录您的函数,以便用户知道如何调用它。


现在,如果您的程序集要调用或被另一种语言调用,例如 C、C++、Fortran 等,那么您需要研究由语言设计者建立的标准调用约定,这些约定通常也取决于架构。例如,对于 32 位 x86 上的 C,参数将在堆栈上传递,而对于 Sparc,最多五个参数将在寄存器中传递,超出的任何参数都将在堆栈上。

对于结构,C 标准要求将它们解包,并将单个元素作为单独的参数传递,由被调用者重新组装成一个结构。如果结构非常大,这可能会非常浪费,因此最好将指针传递给结构。

如果函数返回一个结构,调用者分配空间来接收它,并将指向该空间的指针作为“秘密”参数传递给函数。

数组总是作为指针传递。

对于 Fortran,所有内容都通过引用传递,这意味着可以将值返回给任何参数。甚至常量也被存储在内存中的某个地方,指向它们的指针被传递给被调用的子例程。 (因此可能会意外更改常量的值。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-16
    • 1970-01-01
    • 1970-01-01
    • 2020-05-04
    • 2018-04-17
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    相关资源
    最近更新 更多