32 位 cdecl 处理结构

填充

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

struct t
{
    int a, b, c, d;    // a is at offset 0, b at 4, c at 8, d at 0ch
    char e;            // e is at 10h
    short f;           // f is at 12h (naturally aligned)
    long g;            // g is at 14h
    char h;            // h is at 18h
    long i;            // i is at 1ch (naturally aligned)
};

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

通过引用传递时,指向内存中结构的指针将作为堆栈上的第一个参数传递。这相当于传递一个自然大小(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 S 是结构的类型)。

该函数必须返回此指针,指向 eax 中的返回值; 允许调用者依赖 eax 将指针指向返回值,该指针在 call 之前推动。

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

struct S foo();         // compiled as 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 位)。这些值在 eaxedx 中返回,就像 64 位整数一样。 (已针对 MSVC 和 Clang 定位 Win32 观察到此行为。)