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,否则在返回之前将结构复制到调用方提供的缓冲区中。这等效于具有隐藏的第一个参数structS*retval(其中structSstruct的类型)。
函数必须使用该指针返回eax;中的返回值;允许调用者依赖于eax保持指向返回值的指针,该指针在返回值之前被推送call。
struct S { unsigned char a, b, c; }; struct S foo(); //编译为structS*foo(structS*_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与SystemVABI的调用约定不同:“平凡”结构最多可包含两个非结构,非数组类型的成员(最大32位)。这些值在eax和中返回edx,就像64位整数一样。(已观察到针对Win32的MSVC和Clang的这种行为。)