Rexdf

The devil is in the Details.

C如何返回double与longlong

| Comments

我们学过汇编的都应该知道C语言的返回值,在x86机器上面都是用eax来实现的。而eax是32位的,所以返回值只能是int/long类型或者指针类型,在汇编里面都是dword。可是突然想到怎么实现返回64位值呢?难道会是指针?显然不太可能这样做。

MSVC

返回double

实际上我们来试一下就知道了。

a.cMyBlog
#include <stdio.h>
double test(){
return 1.0;
}
int main(){
double x = test();
printf("%f", x);
return 0;
}

执行Cl /Fa a.c

a.asmMyBlog
; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.31101.0
TITLE a.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
_DATA SEGMENT
$SG3047 DB '%f', 00H
_DATA ENDS
PUBLIC _test
PUBLIC _main
PUBLIC __real@3ff0000000000000
EXTRN _printf:PROC
EXTRN __fltused:DWORD
; COMDAT __real@3ff0000000000000
CONST SEGMENT
__real@3ff0000000000000 DQ 03ff0000000000000r ; 1
CONST ENDS
; Function compile flags: /Odtp
_TEXT SEGMENT
_x$ = -8 ; size = 8
_main PROC
; File a.c
; Line 7
push ebp
mov ebp, esp
sub esp, 8
; Line 8
call _test
fstp QWORD PTR _x$[ebp]
; Line 9
sub esp, 8
movsd xmm0, QWORD PTR _x$[ebp]
movsd QWORD PTR [esp], xmm0
push OFFSET $SG3047
call _printf
add esp, 12 ; 0000000cH
; Line 10
xor eax, eax
; Line 11
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
; Function compile flags: /Odtp
_TEXT SEGMENT
_test PROC
; File a.c
; Line 3
push ebp
mov ebp, esp
; Line 4
fld1
; Line 5
pop ebp
ret 0
_test ENDP
_TEXT ENDS
END

我们可以发现实际上是利用的X87的ST(0)栈寄存器来传递的,然后编译器自动在call语句后面加了一句fstp来从X87里面取出来。

返回 long long

我们再来看看longlong的。

b.cMyBlog
#include <stdio.h>
long long test(){
return 1e10;
}
int main(){
long long x = test();
printf("%lld", x);
return 0;
}

汇编代码

b.asmMyBlog
; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.31101.0
TITLE b.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
_DATA SEGMENT
$SG3047 DB '%lld', 00H
_DATA ENDS
PUBLIC _test
PUBLIC _main
PUBLIC __real@4202a05f20000000
EXTRN _printf:PROC
EXTRN __dtol3:PROC
; COMDAT __real@4202a05f20000000
CONST SEGMENT
__real@4202a05f20000000 DQ 04202a05f20000000r ; 1e+010
CONST ENDS
; Function compile flags: /Odtp
_TEXT SEGMENT
_x$ = -8 ; size = 8
_main PROC
; File b.c
; Line 7
push ebp
mov ebp, esp
sub esp, 8
; Line 8
call _test
mov DWORD PTR _x$[ebp], eax
mov DWORD PTR _x$[ebp+4], edx
; Line 9
mov eax, DWORD PTR _x$[ebp+4]
push eax
mov ecx, DWORD PTR _x$[ebp]
push ecx
push OFFSET $SG3047
call _printf
add esp, 12 ; 0000000cH
; Line 10
xor eax, eax
; Line 11
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
; Function compile flags: /Odtp
_TEXT SEGMENT
_test PROC
; File b.c
; Line 3
push ebp
mov ebp, esp
; Line 4
movsd xmm0, QWORD PTR __real@4202a05f20000000
call __dtol3
; Line 5
pop ebp
ret 0
_test ENDP
_TEXT ENDS
END

这里返回前call了一个__dtol3的函数,从main里面我们可以看到long long值是被它转到了eax edx里面去了。s虽然我们不知道这个dtol3干啥用的,但是它作用应该是清晰的,从xmm0取出64位整数,放到eax edx中。 所以返回64位整数是用的eax 和edx来做的。

返回结构体呢?也可以类似分析,只是代码比较多。这里就不贴出来了。看VC++实现的是caller预分配自己栈空间,通过lea eax, DWORD PTR $T1[ebp]; push eax; call _test; add esp, 4传入一个buffer来让callee填。

然后我们来看看gcc和clang是怎么处理的吧!

gcc

下面是gcc3.4.4的结果

double

a.asmMyBlog
.file "a.c"
.intel_syntax
.text
.globl _test
.def _test; .scl 2; .type 32; .endef
_test:
push ebp
mov ebp, esp
fld1
pop ebp
ret
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC3:
.ascii "%lld\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
push ebp
mov ebp, esp
sub esp, 24
and esp, -16
mov eax, 16
call __alloca
call ___main
call _test
fstp QWORD PTR [esp+4]
mov DWORD PTR [esp], OFFSET FLAT:LC3
call _printf
mov eax, 0
leave
ret
.def _printf; .scl 3; .type 32; .endef

long long

double的

b.asmMyBlog
.file "b.c"
.intel_syntax
.text
.globl _test
.def _test; .scl 2; .type 32; .endef
_test:
push ebp
mov ebp, esp
mov eax, 1
mov edx, 0
pop ebp
ret
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%lld\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
push ebp
mov ebp, esp
sub esp, 24
and esp, -16
mov eax, 16
call __alloca
call ___main
call _test
mov DWORD PTR [esp+4], eax
mov DWORD PTR [esp+8], edx
mov DWORD PTR [esp], OFFSET FLAT:LC0
call _printf
mov eax, 0
leave
ret
.def _printf; .scl 3; .type 32; .endef

可以发现gcc的代码直白很多,没有msvc的那种负担,但是二进制代码和汇编代码好像还不完全一样,最终代码似乎并没有完全按照他编译出来的汇编翻译,应该是还有后续的优化过程。之所以我不贴gcc4.9的代码,是因为它优化比较厉害,直接函数都不调用了。

clang

下面是clang 3.4.2

double

a.asmMyBlog
.def @feat.00;
.scl 3;
.type 0;
.endef
.globl @feat.00
@feat.00 = 1
.def _test;
.scl 2;
.type 32;
.endef
.text
.globl _test
.align 16, 0x90
_test:
push ebp
mov ebp, esp
and esp, -8
sub esp, 8
mov eax, 1
cvtsi2sd xmm0, eax
fld1
movsd qword ptr [esp], xmm0
mov esp, ebp
pop ebp
ret
.def _main;
.scl 2;
.type 32;
.endef
.globl _main
.align 16, 0x90
_main:
push ebp
mov ebp, esp
and esp, -8
sub esp, 40
call ___main
mov dword ptr [esp + 36], 0
call _test
fstp qword ptr [esp + 16]
movsd xmm0, qword ptr [esp + 16]
lea eax, dword ptr [L_.str]
movsd qword ptr [esp + 24], xmm0
movsd xmm0, qword ptr [esp + 24]
mov dword ptr [esp], eax
movsd qword ptr [esp + 4], xmm0
call _printf
mov ecx, 0
mov dword ptr [esp + 12], eax
mov eax, ecx
mov esp, ebp
pop ebp
ret
.section .rdata,"r"
L_.str:
.asciz "%f"

long long

double的

b.asmMyBlog
.def @feat.00;
.scl 3;
.type 0;
.endef
.globl @feat.00
@feat.00 = 1
.def _test;
.scl 2;
.type 32;
.endef
.text
.globl _test
.align 16, 0x90
_test:
push ebp
mov ebp, esp
mov eax, 1
xor edx, edx
pop ebp
ret
.def _main;
.scl 2;
.type 32;
.endef
.globl _main
.align 16, 0x90
_main:
push ebp
mov ebp, esp
and esp, -8
sub esp, 40
call ___main
lea eax, dword ptr [L_.str]
mov dword ptr [esp + 36], 0
mov dword ptr [esp + 20], eax
call _test
mov dword ptr [esp + 28], edx
mov dword ptr [esp + 24], eax
mov ecx, esp
mov dword ptr [ecx + 8], edx
mov dword ptr [ecx + 4], eax
mov dword ptr [ecx], L_.str
call _printf
mov ecx, 0
mov dword ptr [esp + 16], eax
mov eax, ecx
mov esp, ebp
pop ebp
ret
.section .rdata,"r"
L_.str:
.asciz "%lld"

Comments