gpt4 book ai didi

c - C结构如何传递给汇编函数?

转载 作者:行者123 更新时间:2023-12-04 10:53:39 25 4
gpt4 key购买 nike

1)C结构如何传递给汇编函数。我的意思是按值传递,而不是按引用传递。
2)顺便说一下,被调用者如何将结构返回给其调用者?
由于我的母语不是英语,所以我为自己的表现不好而感到抱歉。

我编写了一个简单的程序来验证C结构如何传递给函数。但是结果令人惊讶。寄存器传递了一些值,但将它们压入堆栈则传递了一些值。这是代码。

源代码

#include <stdio.h>

typedef struct {
int age;
enum {Man, Woman} gen;
double height;
int class;
char *name;
} student;

void print_student_info(student s) {
printf("age: %d, gen: %s, height: %f, name: %s\n",
s.age,
s.gen == Man? "Man":"Woman",
s.height, s.name);
}

int main() {
student s;
s.age = 10;
s.gen = Man;
s.height = 1.30;
s.class = 3;
s.name = "Tom";
print_student_info(s);
return 0;
}


汇编

 6fa:   55                      push   %rbp
6fb: 48 89 e5 mov %rsp,%rbp
6fe: 48 83 ec 20 sub $0x20,%rsp
702: c7 45 e0 0a 00 00 00 movl $0xa,-0x20(%rbp)
709: c7 45 e4 00 00 00 00 movl $0x0,-0x1c(%rbp)
710: f2 0f 10 05 00 01 00 movsd 0x100(%rip),%xmm0 # 818 <_IO_stdin_used+0x48>
717: 00
718: f2 0f 11 45 e8 movsd %xmm0,-0x18(%rbp)
71d: c7 45 f0 03 00 00 00 movl $0x3,-0x10(%rbp)
724: 48 8d 05 e5 00 00 00 lea 0xe5(%rip),%rax # 810 <_IO_stdin_used+0x40>
72b: 48 89 45 f8 mov %rax,-0x8(%rbp)
72f: ff 75 f8 pushq -0x8(%rbp)
732: ff 75 f0 pushq -0x10(%rbp)
735: ff 75 e8 pushq -0x18(%rbp)
738: ff 75 e0 pushq -0x20(%rbp)
73b: e8 70 ff ff ff callq 6b0 <print_student_info>
740: 48 83 c4 20 add $0x20,%rsp
744: b8 00 00 00 00 mov $0x0,%eax
749: c9 leaveq
74a: c3 retq
74b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)


我期望使用堆栈将结构传递给函数,但是上面的代码表明事实并非如此。

最佳答案

正如其他人所指出的那样,在大多数情况下,按值传递结构通常会受到质疑,但是C语言仍然允许这样做。我将讨论您使用过的代码,即使这不是我本来会做的。



如何传递结构取决于ABI /调用约定。今天有两种主要的64位ABI正在使用(可能还有其他)。 64-bit Microsoft ABIx86-64 System V ABI。 64位Microsoft ABI很简单,因为按值传递的所有结构都在堆栈中。在x86-64 System V ABI(由Linux / MacOS / BSD使用)中,由于存在一种递归算法,该递归算法用于确定结构是否可以在通用寄存器/矢量寄存器/ X87 FPU的组合中传递,因此更加复杂。堆栈寄存器。如果确定该结构可以在寄存器中传递,则该对象不会出于调用函数的目的而放在堆栈中。如果根据规则它不适合寄存器,那么它将在堆栈的内存中传递。

有迹象表明,您的代码未使用64位Microsoft ABI,因为在进行函数调用之前编译器未保留32字节的影子空间,因此几乎可以肯定这是针对x86-64 System V的编译器ABI。我可以使用禁用了优化功能的GCC编译器,使用在线Godbolt编译器生成问题中的same assembly code

遍历algorithm for passing aggregate types(如结构和联合)超出了此答案的范围,但是您可以参考3.2.3节“参数传递”,但是我可以说,由于后清理规则,该结构在堆栈上传递说:


如果聚合的大小超过两个8字节,并且前8个字节不是SSE,或者其他8个字节不是SSEUP,则整个参数都将在内存中传递。


碰巧是您的结构曾试图将前两个32位int值打包在64位寄存器中,而double放在向量寄存器中,然后将int放在64位寄存器中位寄存器(由于对齐规则),并且指针在另一个64位寄存器中传递。您的结构将超过两个八个字节(64位)的寄存器,而第一个八个字节(64位)的寄存器不是SSE寄存器,因此编译器会将结构传递到堆栈上。

您有未优化的代码,但是我们可以将代码分解为大块。首先是构建堆栈框架并为局部变量分配空间。如果未启用优化(此处就是这种情况),结构变量s将构建在堆栈上,然后将该结构的副本推入堆栈以调用print_student_info

这将构建堆栈帧并为局部变量分配32字节(0x20)(并保持16字节对齐)。在这种情况下,您的结构恰好是32个字节,遵循natural alignment rules

 6fa:   55                      push   %rbp
6fb: 48 89 e5 mov %rsp,%rbp
6fe: 48 83 ec 20 sub $0x20,%rsp


您的变量 s将以RBP-0x20开始,并以RBP-0x01(包括结尾)结束。该代码在堆栈上构建并初始化 s变量( student结构)。 age字段的32位int 0xa(10)放在RBP-0x20结构的开头。 Man的32位枚举放在RBP-0x1c的字段 gen中:

 702:   c7 45 e0 0a 00 00 00    movl   $0xa,-0x20(%rbp)
709: c7 45 e4 00 00 00 00 movl $0x0,-0x1c(%rbp)


常量值1.30(类型 double)由编译器存储在内存中。您无法在Intel x86处理器上使用一条指令在内存之间移动,因此编译器将双精度值1.30从内存位置RIP + 0x100移至矢量寄存器XMM0,然后将XMM0的低64位移至了RBP-0x18处的堆栈:

 710:   f2 0f 10 05 00 01 00    movsd  0x100(%rip),%xmm0        # 818 <_IO_stdin_used+0x48>
717: 00
718: f2 0f 11 45 e8 movsd %xmm0,-0x18(%rbp)


在RBP-0x10的 height字段中,将值3放在堆栈上:

 71d:   c7 45 f0 03 00 00 00    movl   $0x3,-0x10(%rbp)


最后,字符串 class的64位地址(在程序的只读数据部分中)被加载到RAX中,然后最终以RBP-0x08的形式移入堆栈中的 Tom字段。尽管 name的类型仅为32位( class类型),但它被填充为8个字节,因为以下字段 int必须自然地在8字节边界上对齐,因为指针的大小为8字节。

 724:   48 8d 05 e5 00 00 00    lea    0xe5(%rip),%rax        # 810 <_IO_stdin_used+0x40>
72b: 48 89 45 f8 mov %rax,-0x8(%rbp)


至此,我们有了一个完全建立在堆栈上的结构。然后,编译器通过将结构的所有32个字节(使用4个64位推送)压入堆栈以进行函数调用来复制它:

 72f:   ff 75 f8                pushq  -0x8(%rbp)
732: ff 75 f0 pushq -0x10(%rbp)
735: ff 75 e8 pushq -0x18(%rbp)
738: ff 75 e0 pushq -0x20(%rbp)
73b: e8 70 ff ff ff callq 6b0 <print_student_info>


然后是典型的堆栈清理和功能结尾:

 740:   48 83 c4 20             add    $0x20,%rsp
744: b8 00 00 00 00 mov $0x0,%eax
749: c9 leaveq


重要说明:在这种情况下,使用的寄存器并不是为了传递参数,而是初始化堆栈上 name变量(结构)的代码的一部分。



返回结构

这也取决于ABI,但是在这种情况下,我将重点介绍x86-64 System V ABI,因为这就是您的代码所使用的。

通过引用:在RAX中返回指向结构的指针。返回指向结构的指针是首选。

按值:按值返回的C语言中的结构强制编译器为调用方中的返回结构分配额外的空间,然后将该结构的地址作为RDI中的隐藏第一个参数传递给该函数。完成后,被调用函数会将在RDI中作为参数传递的地址放入RAX中作为返回值。从函数返回后,RAX中的值是指向存储返回结构的地址的指针,该地址始终是在隐藏的第一个参数RDI中传递的地址。 ABI在“返回值”子标题下的“ 3.2.3参数传递”一节中对此进行了讨论,该参数表示:



如果类型具有MEMORY类,则调用方为返回提供空间
值,并以%rdi的形式传递此存储的地址,就好像它是第一个
该函数的参数。实际上,该地址成为“隐藏”的第一个参数。此存储不得与通过以下方式被调用方可见的任何数据重叠
除此参数外的其他名称。
返回时,%rax将包含由
%rdi中的呼叫者。

关于c - C结构如何传递给汇编函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57766693/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com