2016-10-30 4 views
2

Gemäß this answer werden numerische Konstanten, die an variadische Funktionen übergeben werden, immer als int behandelt, wenn sie in eins passen. Das lässt mich fragen, warum der folgende Code mit beiden funktioniert: int und long long. Betrachten Sie die folgende Funktionsaufruf:Warum funktioniert meine variadische Funktion sowohl mit int als auch mit long long?

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

testfunc sieht wie folgt aus:

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); 
} 

Dies funktioniert gut. Er druckt 1000, 1001, 1002, 1003. Aber zu meiner Überraschung, der folgende Code funktioniert auch:

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); 
} 

Warum ist das so? Warum funktioniert es auch mit long long? Ich dachte, dass numerische Integer-Konstanten als int übergeben wurden, wenn sie in einem passen? (siehe Link oben) Wie kann es also sein, dass es auch mit long long funktioniert?

Verdammt, es funktioniert sogar, wenn man zwischen int und long long wechselt. Dies ist verwirrend, das Heck aus mir:

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); 
} 

Wie kann das sein? Ich dachte alle meine Parameter wurden als int übergeben ... Warum kann ich beliebig zwischen int und long long beliebig hin und her wechseln? Ich bin jetzt wirklich verwirrt ...

Danke für jedes Licht vergossen auf diese!

+3

Welchen Chip verwenden Sie? Was ist das ABI (Application Binary Interface) dafür? Sie können damit davonkommen, weil Sie sich auf einem 64-Bit-Rechner befinden und die ersten N Argumente der Variadic-Argumente in Registern übertragen werden, die 64-Bit-Register sind, groß genug für 'long long'. Versuchen Sie, die Funktion mit 20 Argumenten - oder mehr als 32 Argumenten - aufzurufen. Die Idee ist, dass, wenn es zu viele Argumente gibt, um in die Register zu passen, die Extras als "int" auf dem Stapel übergeben werden. –

+0

Ich bin auf 64-Bit-Ubuntu mit gcc. – Andreas

+2

Undefiniertes Verhalten umfasst Verhalten, das zufällig funktioniert. Es wird sicherlich auf einem 64-Bit-Intel-Prozessor, der ABI fordert 8 Bytes pro Argument und die oberen 32-Bits werden immer Null und der Prozessor ist Little-Endian. Tinker mit diesen zufälligen Eigenschaften und Sie werden einen Unfall haben. –

Antwort

5

Das hat nichts mit C zu tun. Es ist nur so, dass das System (x86-64) die ersten paar Argumente in 64-Bit-Registern übergibt, sogar für variadische Argumente.

Im Wesentlichen erzeugt der Compiler auf der von Ihnen verwendeten Architektur Code, der für jedes Argument ein vollständiges 64-Bit-Register verwendet, einschließlich variadischer Argumente. Dies ist der ABI stimmte der Architektur zu und hat mit C per se nichts zu tun; Alle Programme, egal wie produziert, sollen dem ABI auf der Architektur folgen, die es ausführen soll.

Wenn Sie Windows verwenden, x86-64 rcx verwendet, rdx, r8 und r9 für die ersten vier (integer oder Zeiger) Argumente, in dieser Reihenfolge, und Stack für den Rest. In Linux, BSD, Mac OS X und Solaris x86-64 verwendet rdi, rsi, rdx, rcx, r8 und r9 für die ersten sechs (integer oder Zeiger) Argumente, in dieser Reihenfolge, und der Stapel für den Rest.

Sie können dies überprüfen, mit einem trivialen Beispiel Programm:

extern void func(int n, ...); 

void test_int(void) 
{ 
    func(0, 1, 2); 
} 

void test_long_long(void) 
{ 
    func(0, 1LL, 2LL); 
} 

Wenn Sie die oben kompilieren Baugruppe x86-64 (zB gcc -Wall -O2 -march=x86-64 -mtune=generic -S) in Linux, BSDs, Solaris oder Mac OS (X oder höher) erhalten Sie ca.

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 

dh die Funktionen identisch sind (AT & T-Syntax, Quelle, Operanden, um Ziel) und Sie die Argumente der stac nicht drücken k.Beachten Sie, dass jmp func entspricht call func; ret, nur einfacher.

Wenn Sie jedoch für x86 (-m32 -march=i686 -mtune=generic) kompilieren, erhalten Sie ca.

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 

, die zeigt, dass die x86-Konventionen in Linux/BSD-Systemen/etc aufrufen. Dazu gehört das Übergeben der variadischen Argumente auf dem Stapel, und dass die int Variante 32-Bit-Konstanten an den Stapel (pushl $x schiebt eine 32-Bit-Konstante x an den Stapel) und die long long Variante 64-Bit-Konstanten an den Stapel schiebt.

Daher zeigt Ihre Variadic-Funktion aufgrund der zugrunde liegenden ABI des verwendeten Betriebssystems und der Architektur die von Ihnen beobachtete "Anomalie" an. Um das Verhalten zu sehen, das Sie allein vom C - Standard erwarten, müssen Sie die zugrundeliegende ABI - Eigenart umgehen, indem Sie beispielsweise Ihre Variadic - Funktionen mit mindestens sechs Argumenten starten, um die Register auf x86 - 64 - Architekturen zu belegen Ruhe, Ihre wirklich variadischen Argumente, werden auf dem Stapel weitergegeben.

Verwandte Themen