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

7

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

由于结构体的大小通常比较大,个别字段是否按顺序依次传递?

如果是,它们是否像普通参数一样按相反的顺序传递?

cdecl和stdcall之间是否有任何差异?


这是 x86 还是 x86_64,你使用的汇编器是什么? - David C. Rankin
这只是标准的x86,虽然我对x86_64很好奇? - Computermatronic
1
区别在于两者之间的调用约定不同,其中 x86_64 通过寄存器传递,前六个整数参数(从左至右)依次为 RDI、RSI、RDX、RCX、R8 和 R9。而 x86 则是按相反顺序传递到堆栈中。我手头上没有非标准类型大小结构的答案,我会稍微查一下。 - David C. Rankin
显然结构体是通过引用(指针)传递的,而且它是与编译器相关的,无论是在 EAX/AX 中传递还是在堆栈上传递。 显然,由于创建了非线程安全条件,大多数编译器不会遵循关于类和结构体的调用约定。 这是一个链接,讨论了gcc和visual c++之间的差异X86汇编/高级语言。 它包含进一步的维基百科链接等。 - David C. Rankin
你可以尝试添加一个你正在思考的代码段的示例... 你是在询问C结构体和数组,还是其他什么东西? - hyde
你也可以澄清一下,如果你的确想要“像在C语言中传递结构体一样通过值传递结构体”,还是指“以任何方式传递结构体,包括显式地传递C结构体指针”。 - hyde
3个回答

7
在汇编语言中,只要调用者和被调用者在协商的情况下,你可以按任何方式传递参数。

将参数放在堆栈上,将指向它们的指针放在堆栈上,将它们放入寄存器中,将它们存储在固定的内存位置中,都由你决定。我曾见过某些参数通过寄存器传递,而其他参数则通过堆栈或引用传递。

你如何转移控制权也由你决定。执行“call”指令或软件中断。旧的PDP-10体系结构有五种不同的调用子程序的方法,你需要知道使用哪一种。IBM-360架构也有许多方法。

(你想看疯狂的事吗?阅读著名的中断列表,其中包含了可用于286体系结构的所有已知软件中断调用的集合。你在MS-DOS下安装的几乎每个软件都会为此添加一些新的软件中断,并且它们都有自己的调用约定,其中许多发生了冲突。)

总的来说,最好的方法是找出其他程序员正在做什么,然后也这样做。或者详细记录你的函数,以便用户知道如何调用它。


现在,如果你的汇编语言将调用或被其他语言(如C、C++、Fortran等)调用,则需要研究由语言设计师制定的标准调用约定,这些约定通常也取决于架构。例如,在32位x86中的C语言要求参数通过堆栈传递,而在Sparc中,最多可以将五个参数传递到寄存器中,超过五个的参数则放在堆栈上。

至于结构体,C标准要求将其解包,并将各个元素作为单独的参数传递,由被调用者重新组装成结构体。如果结构非常大,则这可能会极其浪费空间,因此最好传递指向结构体的指针。

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

数组始终作为指针传递。

对于Fortran,所有内容都是按引用传递的,这意味着值可以返回给任何参数。即使常量也会被放置在某个内存中,对它们的指针也会传递给所调用的子程序。(这样就可能意外更改常量的值。)


2
在大多数情况下,结构体作为指向结构体开头的指针传递。
然后,函数将此指针加载到某个寄存器中,并通过它们的偏移地址来访问结构体的字段。

1
只是为了澄清:这是汇编中可能的一种方法(也是我推荐的方法)。这不是C语言的做法;C语言会将整个结构体塞进管道。在C语言中,最好传递指向结构体的指针而不是结构体本身。 - Edward Falk
1
@EdwardFalk:有趣的一点是:C ABI 通常通过引用返回大型结构体(将指针作为隐藏的第一个参数传递)。 - Peter Cordes
1
我们现在谈论的是汇编语言,对于汇编语言而言,你可以做任何你想做的事情,但我描述的是最自然的方式。 - johnfound

0

结构体(如数组)是通过引用传递的,因此作为参数时它们只是一个32位的参数(指向第一个结构体成员的指针),并且在使用cdeclstdcall调用约定时,该指针会被推入堆栈。

如果你通过传递数据结构,这意味着你必须将结构体的每个成员都推入堆栈给被调用者,这对性能有很大影响 - 尤其是对于大型结构体。

myarray dword 300 dup(?)
push offset myarray

现在这个数组已经通过引用推入(第一个成员的指针)。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接