Pourquoi ma fonction variadique fonctionne-t-elle avec int et long long?

Selon cette réponse, les constantes numériques passées aux fonctions variadiques sont toujours traitées comme des int si elles entrent dans une. Cela me fait me demander pourquoi le code suivant fonctionne à la fois avec int et long long . Considérons l’appel de fonction suivant:

 testfunc(4, 1000, 1001, 1002, 1003); 

testfunc ressemble à ceci:

 void testfunc(int n, ...) { int k; va_list marker; va_start(marker, n); for(k = 0; k < n; k++) { int x = va_arg(marker, int); printf("%d\n", x); } va_end(marker); } 

Cela fonctionne bien. Il imprime 1000, 1001, 1002, 1003. Mais à ma grande surprise, le code suivant fonctionne également:

 void testfunc(int n, ...) { int k; va_list marker; va_start(marker, n); for(k = 0; k < n; k++) { long long x = va_arg(marker, long long); printf("%lld\n", x); } va_end(marker); } 

Pourquoi donc? Pourquoi ça marche aussi long long ? Je pensais que les constantes entières numériques étaient passées comme entières si elles entraient dans une seule? (cf. lien ci-dessus) Alors, comment se fait-il qu’il fonctionne aussi long long ?

Zut, ça marche même quand on alterne entre int et long long . C’est déroutant le diable hors de moi:

 void testfunc(int n, ...) { int k; va_list marker; va_start(marker, n); for(k = 0; k < n; k++) { if(k & 1) { long long x = va_arg(marker, long long); printf("B: %lld\n", x); } else { int x = va_arg(marker, int); printf("A: %d\n", x); } } va_end(marker); } 

Comment se peut-il? Je pensais que tous mes parameters avaient été passés en tant que int … Pourquoi puis-je basculer arbitrairement entre int et long long sans aucun problème? Je suis vraiment confus maintenant …

Merci pour cette lumière!

Cela n’a rien à voir avec C. C’est juste que le système que vous avez utilisé (x86-64) passe les premiers arguments dans les registres 64 bits, même pour les arguments variadiques.

Sur l’architecture que vous avez utilisée, le compilateur produit un code qui utilise un registre 64 bits complet pour chaque argument, y compris des arguments variadiques. C’est l’ABI qui a convenu de l’architecture et qui n’a rien à voir avec C en tant que tel; tous les programmes, quelle que soit leur production, sont censés suivre l’ABI sur l’architecture qu’il est censé exécuter.

Si vous utilisez Windows, x86-64 utilise rcx , rdx , r8 et r9 pour les quatre premiers arguments (entier ou pointeur), dans cet ordre, et empilé pour le rest. Sous Linux, BSD, Mac OS X et Solaris, x86-64 utilise rdi , rsi , rdx , rcx , r8 et r9 pour les six premiers arguments (entier ou pointeur), dans cet ordre, et stack pour le rest.

Vous pouvez le vérifier avec un exemple de programme sortingvial:

 extern void func(int n, ...); void test_int(void) { func(0, 1, 2); } void test_long_long(void) { func(0, 1LL, 2LL); } 

Si vous comstackz l’assemblage ci-dessus en assemblage x86-64 (par exemple, gcc -Wall -O2 -march=x86-64 -mtune=generic -S ) sous Linux, BSD, Solaris ou Mac OS (X ou version ultérieure), vous obtenez environ ( Syntaxe AT & T, source, ordre d’opérande cible )

 test_int: movl $2, %edx movl $1, %esi xorl %edi, %edi xorl %eax, %eax jmp func test_long_long: movl $2, %edx movl $1, %esi xorl %edi, %edi xorl %eax, %eax jmp func 

c’est-à-dire que les fonctions sont identiques et ne poussent pas les arguments dans la stack . Notez que jmp func est équivalent à call func; ret call func; ret , juste plus simple.

Cependant, si vous comstackz pour x86 ( -m32 -march=i686 -mtune=generic ), vous obtenez environ

 test_int: subl $16, %esp pushl $2 pushl $1 pushl $0 call func addl $28, %esp ret test_long_long: subl $24, %esp pushl $0 pushl $2 pushl $0 pushl $1 pushl $0 call func addl $44, %esp ret 

ce qui montre que les conventions d’appel x86 sous Linux / BSD / etc. implique de passer les arguments variadiques sur la stack et que la variante int pousse les constantes 32 bits vers la stack ( pushl $x pousse une constante 32 bits x vers la stack), et la variante long long pousse les constantes 64 bits vers la stack .

Par conséquent, en raison de l’ABI sous-jacente du système d’exploitation et de l’architecture que vous utilisez, votre fonction variadique indique «l’anomalie» que vous avez observée. Pour voir le comportement que vous attendez de la norme C uniquement, vous devez contourner le paradoxe ABI sous-jacent – par exemple, en démarrant vos fonctions variadiques avec au moins six arguments, pour occuper les registres des architectures x86-64, de sorte que rest, vos arguments vraiment variadic, sont passés sur la stack.