comment c compilateur traite une valeur de retour de structure à partir d’une fonction, en ASM

Lorsque vous parlez de la valeur de retour de la fonction C, celle-ci est stockée dans le registre EAX . Supposons que nous parlions de registre 32 bits, les entiers sont les bienvenus, mais que se passe-t-il lorsque nous renvoyons ces types: long long , long double , une struct / union supérieure à 32 bits.

Dans les conventions d’appel x86 communes, les objects qui correspondent à deux registres sont retournés dans RDX:RAX . Il s’agit de la même paire de registres qui constitue une entrée / sortie implicite pour les instructions div et mul et pour cdq / cqo (le signe étendre e / rax dans e / rdx).

La convention d’appel i386 Linux (SysV) ne renvoie que les entiers 64 bits de cette façon. Les structures (même une structure constituée d’un seul int32_t ) utilisent la méthode des parameters cachés au lieu d’être empaquetées eax ou edx:eax . Linux 64 bits et le __vectorcall standard actuel de Microsoft __vectorcall les structures dans e/rax ou e/rdx:e/rax .

De nombreuses conventions d’appel gèrent des objects plus volumineux en ajoutant un paramètre caché supplémentaire: un pointeur sur l’espace pour stocker la valeur de retour. Consultez la documentation ABI pour l’ABI spécifique que vous utilisez. (liens sur dans le wiki x86 ).

Le passage d’un pointeur peut enregistrer une copie, car il peut pointer vers la destination finale plutôt que vers l’espace de travail de la stack, par rapport à d’autres conventions d’appel décrites dans les commentaires (par exemple, utiliser implicitement de l’espace sur la stack pour stocker les objects volumineux renvoyés).

Considérez ce programme:

 struct object_t { int m1; int m2; int m3; }; struct object_t test1(void) { struct object_t o = {1, 2, 3}; return o; } long long test2(void) { return 0LL; } long double test3(void) { return 0.0L; } 

compilé sous Windows avec (fichier object, instructions minimales, sans instruction x87):

 $ gcc -Wall -c -O2 -mno-80387 test.c -o test.o 

La première fonction:

 00000000 <_test1>: 0: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4] 4: c7 00 01 00 00 00 mov DWORD PTR [eax],0x1 a: c7 40 04 02 00 00 00 mov DWORD PTR [eax+0x4],0x2 11: c7 40 08 03 00 00 00 mov DWORD PTR [eax+0x8],0x3 18: c3 ret 

L’appelant fournira un pointeur test1 où se trouve sa structure sur la stack en tant que premier argument et test1 le remplira à l’aide de ce pointeur.

Deuxième fonction ( sizeof(long long) == 8 ):

 00000020 <_test2>: 20: 31 c0 xor eax,eax 22: 31 d2 xor edx,edx 24: c3 ret 

Le résultat sera renvoyé sur deux registres eax et edx , pas seulement eax .

Troisième fonction ( sizeof(long double) == 12 ):

 00000030 <_test3>: 30: 31 c0 xor eax,eax 32: 31 d2 xor edx,edx 34: 31 c9 xor ecx,ecx 36: c3 ret 

La valeur de retour sera passée sur trois registres, eax , edx , ecx .