2016-10-30 44 views
2

根據傳遞給可變參數功能this answer數字常量,如果他們以一個總是被視爲int。這讓我想知道爲什麼下面的代碼同時適用於intlong long。考慮下面的函數調用:爲什麼我的可變參數函數同時適用於int和long long?

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

testfunc看起來是這樣的:

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

這工作得很好。它打印1000,1001,1002,1003。但令我驚訝的是,下面的代碼也適用:

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

這是爲什麼?爲什麼它也可以與long long一起使用?我認爲如果數字整型常量適合於一個,則它們被傳遞爲int? (參見上面的鏈接)那麼它怎麼能和long long一起工作呢?

哎呀,在intlong long之間交替時,它甚至可以工作。這讓我很困惑:

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

這怎麼可能?我認爲是int我的所有參數都過去了......爲什麼我隨意,沒有麻煩來回切換intlong long之間呢?我真的很困惑,現在...

感謝任何輕灑在這!

+3

你使用什麼芯片?什麼是ABI(應用程序二進制接口)?您可能會因爲使用64位計算機而失控,並且可變參數的前N個參數將傳遞到64位寄存器的寄存器中,這些寄存器足夠長以用於「long long」。嘗試用20個參數或32個以上的參數調用該函數。這個想法是,如果有太多的參數適合在寄存器中,那麼額外的數據將作爲'int'傳遞到堆棧上。 –

+0

我在使用gcc的64位Ubuntu上。 – Andreas

+2

未定義的行爲包括偶然工作的行爲。它肯定會在64位英特爾處理器上實現,ABI需要每個參數8個字節,而高32位將始終爲零,處理器爲小端。修補任何這些意外的財產,你會發生事故。 –

回答

5

這與C無關。只是您使用的系統(x86-64)會傳遞64位寄存器中的前幾個參數,即使是可變參數也是如此。

本質上,在您使用的體系結構中,編譯器生成的代碼爲每個參數使用完整的64位寄存器,包括可變參數。這是ABI同意的架構,與C本身無關;所有的程序,無論如何產生,都應該遵循它應該運行的體系結構上的ABI。

如果您使用的是Windows,X86-64使用rcxrdxr8r9四個第一(整數或指針)參數,按照這個順序和堆棧的其餘部分。在Linux中,BSD的,Mac OS X的,和Solaris,X86-64使用rdirsirdxrcxr8r9首六個月(整數或指針)參數,按照這個順序和堆棧的其餘部分。

你可以用一個簡單的例子程序驗證這一點:

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

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

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

如果您編譯上面X86-64在Linux中,BSD系統,Solaris上,或Mac OS組件(如gcc -Wall -O2 -march=x86-64 -mtune=generic -S)(X或更高版本) ,你會得到約(AT & T語法,源,目標操作順序)

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 

即功能是相同的,不參數推到STAC k。請注意0​​相當於call func; ret,只是更簡單。

不過,如果你編譯的x86(-m32 -march=i686 -mtune=generic),你會得到大約

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 

這表明在x86調用的Linux/BSD系統的/ etc約定。涉及將可變參數傳遞到堆棧,並且變體將32位常量推送到堆棧(pushl $x將32位常量x推送到堆棧),並且long long變體將64位常量推送到堆棧。

因此,由於您使用的操作系統和體系結構的底層ABI,您的可變參數函數會顯示您觀察到的「異常」。要單獨看到你期望從C標準中得到的行爲,你需要解決底層的ABI問題 - 例如,通過啓動至少有六個參數的可變參數函數來佔用x86-64體系結構上的寄存器,這樣休息,你的真正可變參數,傳遞到堆棧上。

相關問題