5
(gdb) disas func
Dump of assembler code for function func:
0x00000000004004b8 <func+0>:    push   %rbp
0x00000000004004b9 <func+1>:    mov    %rsp,%rbp
0x00000000004004bc <func+4>:    movl   $0x64,0xfffffffffffffff0(%rbp)
0x00000000004004c3 <func+11>:   movb   $0x61,0xfffffffffffffff4(%rbp)
0x00000000004004c7 <func+15>:   mov    0xfffffffffffffff0(%rbp),%rax
0x00000000004004cb <func+19>:   leaveq
0x00000000004004cc <func+20>:   retq
End of assembler dump.


t_test func()
{
    t_test t;
    t.i = 100;
    t.c = 'a';
    return t;
}

所以看起来它正在返回局部变量t,但是这种工作是否可以保证工作,返回时不应该不引用任何局部变量吗?

4

5 回答 5

6

根据我的经验,没有标准的 C 如何返回结构。为了能够传递结构,编译器通常(对用户不可见)传递一个指向该结构的指针,函数可以将内容复制到该结构。该指针的传递方式(堆栈上的第一个或最后一个)取决于实现。一些编译器,如 32 位 MSVC++,在 EAX 和 EDX 等寄存器中返回小结构。显然,GCC 在 RAX 中以 64 位模式返回这样的结构。

但是,再一次,没有标准的方法是如何完成的。当使用函数的其余代码也由同一个编译器编译时,这没有问题,但如果该函数是 DLL 或 lib 的导出函数,则会出现问题。当使用来自不同语言(Delphi)或来自 C 和不同编译器的此类函数时,我已经被这个咬了几次。也见这个链接

于 2011-07-18T10:46:17.910 回答
5

RAX 大到足以容纳整个结构。在 0x00000000004004c7 处,您正在加载整个结构(使用 mov),而不是其地址(您将使用 lea 代替)。

x86-64 System V ABI 的调用约定在 RDX:RAX 或 RAX 中返回最多 16 个字节的 C 结构。 x86-64 上的 C++:何时在寄存器中传递和返回结构/类?

对于较大的结构,调用者传递了一个“隐藏的”输出指针 arg。

于 2011-07-18T09:53:10.317 回答
3

返回东西的方式根本不是标准的,但通常在 RAX 中。在您的示例中,假设 t_test::i 和 t_test::c 是 t_test 的唯一成员,并且每个成员最多为 32 位,则整个结构可以放入 64 位寄存器,因此它只是通过 RAX 直接返回值,并且通常可以放入 2 个寄存器的东西在 RAX:RDX (或 RDX:RAX,我忘记了常见的顺序)中返回。

对于大于两个的寄存器,它通常涉及作为第一个参数传递的隐藏指针参数,该参数指向调用函数中的一个对象(通常是直接分配返回值的对象)。然后在从被调用函数返回之前写入该对象(通常从被调用函数中使用的本地结构复制),并且通常在 RAX 中返回传递的相同指针。

EAX/EDX 可以替代 32 位 x86 系统上的 RAX/RDX。

通过在堆栈上传递“this”指针的约定(如标准 x86 GCC 约定),返回值指针通常作为隐藏的第二个参数而不是第一个参数传递。

于 2011-07-18T11:30:09.010 回答
1

您的原始代码正在返回在函数中创建的结构的副本 - 因为您正在返回结构类型,而不是指向结构的指针。看起来整个结构是通过值传递的rax。一般来说,编译器可以为此生成各种汇编代码,这取决于调用者和被调用者的行为和调用约定。

处理结构的正确方法是将它们用作输出参数:

void func(t_test* t)
{
    t->i = 100;
    t->c = 'a';
}
于 2011-07-18T09:57:45.770 回答
0

堆栈指针在函数开始时不会更改,因此t_test不会在函数内进行分配,因此不会被函数释放。如何处理取决于使用的调用约定。如果您查看该函数的调用方式,则可以更容易地了解它是如何完成的。

于 2011-07-18T09:55:20.700 回答