Intel x86 Assembly& Microarchitecture 32位cdecl —处理结构

示例

填充

请记住,通常会填充结构的成员以确保它们在其自然边界上对齐:

struct t
{
    int a, b, c, d;    // a在偏移量0处,b在4,处,c在8,处,d在0ch
    char e;            // e在10h
    short f;           // f在12h(自然对齐)
    long g;            // g在14h
    char h;            // h在18h
    long i;            // 我处于1声道(自然对齐)
};

作为参数(通过引用传递)

通过引用传递时,指向内存中结构的指针将作为堆栈上的第一个参数传递。这等效于传递自然大小(32位)的整数值;有关详细信息,请参见32位cdecl。

作为参数(按值传递)

当按值传递时,结构将完全复制到堆栈上,并遵守原始内存布局(,第一个成员将位于较低的地址)。

int __attribute__((cdecl)) foo(struct t a);

struct t s = {0, -1, 2, -3, -4, 5, -6, 7, -8};
foo(s);
; Assembly call

push DWORD 0fffffff8h    ; i (-8)
push DWORD 0badbad07h    ; h (7), pushed as DWORD to naturally align i, upper bytes can be garbage
push DWORD 0fffffffah    ; g (-6)
push WORD 5              ; f (5)
push WORD 033fch         ; e (-4), pushed as WORD to naturally align f, upper byte can be garbage
push DWORD 0fffffffdh    ; d (-3)
push DWORD 2             ; c (2)
push DWORD 0ffffffffh    ; b (-1)
push DWORD 0             ; a (0)
call foo
add esp, 20h

作为返回值

除非它们是琐碎的1,否则在返回之前将结构复制到调用方提供的缓冲区中。这等效于具有隐藏的第一个参数struct S *retval(其中struct Sstruct的类型)。

函数必须使用该指针返回eax;中的返回值;允许调用者依赖于eax保持指向返回值的指针,该指针在返回值之前被推送call。

struct S
{
    unsigned char a, b, c;
};

struct S foo();         // 编译为struct S * foo(struct S * _out)

出于堆栈清理的目的,不会将隐藏参数添加到参数计数中,因为它必须由被调用方处理。

sub esp, 04h        ; allocate space for the struct

; call to foo
push esp            ; pointer to the output buffer
call foo
add esp, 00h        ; still as no parameters have been passed

在上面的示例中,结构将保存在堆栈的顶部。

struct S foo()
{
    struct S s;
   s.a= 1;s.b= -2;s.c= 3;
    return s;
}
; Assembly code
push ebx
mov eax, DWORD PTR [esp+08h]   ; access hidden parameter, it is a pointer to a buffer
mov ebx, 03fe01h               ; struct value, can be held in a register
mov DWORD [eax], ebx           ; copy the structure into the output buffer 
pop ebx
ret 04h                        ; remove the hidden parameter from the stack
                               ; EAX = pointer to the output buffer


1 “平凡的”结构是仅包含一个非结构,非数组类型的成员(最大32位)。对于此类结构,该成员的值仅在eax寄存器中返回。(在针对Linux的GCC中已观察到此行为)

Windows版本的cdecl与System V ABI的调用约定不同:“平凡”结构最多可包含两个非结构,非数组类型的成员(最大32位)。这些值在eax和中返回edx,就像64位整数一样。(已观察到针对Win32的MSVC和Clang的这种行为。)