C语言直接返回结构体汇编后结果

时间:2022-06-04 12:44:09

(以下为使用vc以及debug编译下的结果)


一般来说,返回小于等于4字节的基本类型,例如short,int,都是直接使用eax寄存器返回,而指针,也是使用eax寄存器返回。
如果是8字节的基本类型,例如c++的long long 类型,很可能是使用两个寄存器,如eax和edx返回结果。
实际上,如果结构体或者说类只占8字节或者更小的话,也是直接使用寄存器返回的。
但是如果结构体占用空间比较大,又不返回指针,那么是怎么返回的呢?


今天想到了这个问题,特此记录一下
源代码很简单

struct TestStruct {
    int a;
    int b;
    int c;
};

TestStruct t() {
    TestStruct testStruct = { 10,20,30};
    return testStruct;
}
int main()
{
    TestStruct tt= t();
}

重点在于调用t方法时的处理,该方法在源代码中是没有参数的,但是汇编之后,会发现传入了一个隐式的参数,这个参数是一个地址,而且是当前方法的局部变量栈的一个地址。

t()
对应于
LEA EAX,[LOCAL.62]
PUSH EAX //传入了隐式的地址
CALL 00E61569    

然后进入t方法中,该方法直接返回一个结构体,该结构体的大小为12字节,如果小于等于8字节汇编后就不是这种形式了。

关键汇编代码如下
首先在局部变量栈中初始化结构体,其实就相当于初始化结构体内的各个变量。

TestStruct testStruct = { 10,20,30};
对应于
MOV DWORD PTR SS:[LOCAL.4],0A
MOV DWORD PTR SS:[LOCAL.3],14
MOV DWORD PTR SS:[LOCAL.2],1E

接下来就是返回这个结构体了,如果是返回指针的话直接存到eax里返回就是了,但是直接返回结构体明显要进行复制,那么复制到哪里呢?
关键汇编代码如下

MOV EAX,DWORD PTR SS:[ARG.1]
MOV ECX,DWORD PTR SS:[LOCAL.4]
MOV DWORD PTR DS:[EAX],ECX
MOV EDX,DWORD PTR SS:[LOCAL.3]
MOV DWORD PTR DS:[EAX+4],EDX
MOV ECX,DWORD PTR SS:[LOCAL.2]
MOV DWORD PTR DS:[EAX+8],ECX
MOV EAX,DWORD PTR SS:[ARG.1]

可以看到,是将结构体复制到了arg1指向的地址,arg1就是刚刚传进来的eax,此时我们就知道,这个返回的结构体,是直接复制到了上一个方法的局部变量区。

关键内存区如下
C语言直接返回结构体汇编后结果
od已经为我们标好了每个方法的局部栈,可以看到上方和下方分别属于不同的方法栈,但是可以注意到,结构体已经被复制了两份。

最后,返回到主方法,最后进行一次复制,这次复制的目的是将返回值再赋值给主方法的结构体。
关键汇编代码如下,可以看到,这次复制都是在局部变量栈中进行,这说明t方法的返回值确实是直接返回到了上一个方法的局部变量栈中。

MOV ECX,DWORD PTR DS:[EAX]
MOV DWORD PTR SS:[LOCAL.57],ECX
MOV EDX,DWORD PTR DS:[EAX+4]
MOV DWORD PTR SS:[LOCAL.56],EDX
MOV EAX,DWORD PTR DS:[EAX+8]
MOV DWORD PTR SS:[LOCAL.55],EAX
MOV ECX,DWORD PTR SS:[LOCAL.57]
MOV DWORD PTR SS:[LOCAL.4],ECX
MOV EDX,DWORD PTR SS:[LOCAL.56]
MOV DWORD PTR SS:[LOCAL.3],EDX
MOV EAX,DWORD PTR SS:[LOCAL.55]
MOV DWORD PTR SS:[LOCAL.2],EAX

虽然这样的代码(直接返回结构体而不是指针引用)比较少见,但是了解下编译器怎么处理这种情况也是比较好的。


欢迎关注我的github
https://github.com/luckyCatMiao