2011-09-06 77 views
3

可能重複:
What are variadic functions in accordance with C and C++?如何使用「...」(變量)參數?

我見過printf()功能...說法。像printfscanf這樣的函數的工作原理是什麼?他們如何擁有無限的輸入值?

+5

您正在尋找的術語是「可變參數函數」。請搜索一下。 – Mat

+2

你的意思是*無限數量的*輸入參數是你嗎?即使這是一個詩意的誇張,在實踐中參數的個數,雖然可以很高,肯定是有限的:-) –

+1

不要在C++中使用,它不是類型安全的,並且有辦法更好的選擇。 –

回答

3

這依賴於C調用約定,即調用者從堆棧彈出參數。

因爲知道有多少個參數(以及它們的大小)的調用者必須以某種方式將其傳遞給被調用者。使用printf()和family,這是通過格式字符串。對於execv和family,這通過具有終止NULL參數來指示。

被調用者使用標準頭文件stdarg.h中定義的宏從堆棧中讀取參數。

從存儲器(粗略),需要定義類型va_list的,這是從前述參數使用的va_start初始化,的變量如:

void print(const char* format, ...) 
{ 
    va_list args; 

    va_start(args, format); 


    /* read an int */ 
    int i = va_arg(args, int); 

    /* read an char* */ 
    char* pc = va_arg(args, char*); 


    va_end(args); 
} 

顯然,你必須格式字符串解析到知道是否讀一個int,或一個char *,或雙,或...

HTH

0

manual to va_*

技術上的樂趣被調用的ction必須具有關於在每個特定情況下調用的參數的確切信息(在* printf()的情況下,信息以格式字符串的形式傳遞)。具有這樣的信息,函數可以使用普通指針算術從其棧幀中提取參數。

2

爲此你必須知道一些關於底層函數調用和參數傳遞的堆棧。

當您調用一個函數時,需要在堆棧上寫入一些東西,例如返回地址,指向前一個堆棧幀的指針等。堆棧中寫入的另一部分由函數的參數組成。在C中,參數從右到左被推入棧中(不像Pascal)。這樣,函數的第一個參數就位於堆棧上參數列表的頂部。

現在,方式printfscanf的工作很簡單。他們得到一個字符串作爲第一個參數(它位於堆棧的頂部(我的意思是堆棧中的參數列表,但我簡單地寫在堆棧頂部))。在該字符串的引導下,他們嘗試從格式字符串的下面(在堆棧上)檢索其他值。因此,舉例來說,如果你撥打:

printf("%d %c %s\n", 0x12345678, 'a', "str"); 

printf看到在棧上是什麼(堆棧的頂部左側):

<address of format string (4 bytes)>|0x78|0x56|0x34|0x12|0x61|<address of str (4 bytes)> 

所以,它的作用是讀取格式串,達到%d,然後讀取來自格式字符串的地址以下4個字節(以便它讀取| 0x78 | 0x56儲存| 0x34 | 0×12 |一部分)然後看見'%c」和從​​後(讀取一個字符的| 0x61 | )等

你應該注意的,就是printfscanf一味地做這個。因此,如果您發送double作爲參數printf並讀取%d,它會讀取相同數量的字節(4),但會將這些位解釋爲int而不是double - >您得到的是亂碼。另外,如果你有很多% S(像%d,%C etc) but not enough arguments,的printf and scanf`盲目追求比其他堆棧變量並將其解釋爲數據/指針。

最後,通常編譯器現在每天閱讀格式字符串你並做適當的鑄造自己(雖然他們不應該,但他們受到很好的)。他們通常也給你一個警告,如果發送的實際參數格式字符串參數預期數量之間的不匹配。然而,你應該總是投的參數你想,如果他們不同打印類型。一個例子是發送一個char和打印%d如果你測試,它只會工作,因爲你的編譯器自動轉換它。

+0

謝謝@GriffinHeart,我總是混淆我的左右(也是對的,寫)。 – Shahbaz