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 觀察到此行為。)