2016-09-27 59 views
1

爲什麼下面的代碼不工作?我可以通過引用來傳遞va_start()嗎?

#include <stdarg.h> 
#include <stdio.h> 

// People are missing this in their reponses.... 'fmt' here is passed by 
// reference, not by value. So &fmt in _myprintf is the same as &fmt in 
// myprintf2. So va_start should use the address of the fmt char * on the 
// stack passed to the original call of myprintf2. 
void _myprintf(const char *&fmt, ...) 
{ 
    char buf[2000]; 
//--- 
    va_list ap; 
    va_start(ap, fmt); 
    vsnprintf(buf, sizeof(buf), fmt, ap); 
    va_end(ap); 
//--- 
    printf("_myprintf:%sn", buf); 
} 

void myprintf2(const char *fmt, ...) 
{ 
    _myprintf(fmt); 
} 

void myprintf(const char *fmt, ...) 
{ 
    char buf[2000]; 
//--- 
    va_list ap; 
    va_start(ap, fmt); 
    vsnprintf(buf, sizeof(buf), fmt, ap); 
    va_end(ap); 
//--- 
    printf(" myprintf:%sn", buf); 
} 

int main() 
{ 
    const char *s = "string"; 
    unsigned u = 11; 
    char c = 'c'; 
    float f = 2.22; 
    myprintf("s='%s' u=%u c='%c' f=%fn", s, u, c, f); 
    myprintf2("s='%s' u=%u c='%c' f=%fn", s, u, c, f); 
} 

我期望輸出的兩行是相同的,但它們的區別:

myprintf:s='string' u=11 c='c' f=2.220000 
_myprintf:s='string' u=2020488703 c='c' f=0.000000 

我想va_start()使用的fmt變量的地址,這應該是字符串的地址指針上堆棧。

+0

偏題:請謹慎使用前面的下劃線。它們通常意味着圖書館實施層面的一些東西。在這種情況下,我認爲您已經違反了在全局名稱空間中保留前面的下劃線以供實現使用的規則。更多這裏:http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-ac-identifier/228797#228797 – user4581301

+1

不是一個無效C代碼的C問題' _myprintf(const char *&fmt,...)'建議選擇一種語言。如果C使用'void _myprintf(const char * fmt,...)' – chux

+0

以下劃線開頭的名字在文件級保留。不要使用它們。 – Olaf

回答

4

va_start確實使用您給它的變量的地址。使用myprintf2時,只傳遞一個參數到myprintf,所以當您嘗試訪問第二個參數(傳遞的s的值)時,它不存在,並且您看到保存的寄存器,返回地址或其他正在處理的內容在堆棧上。

要做你想做的事情,你需要將va_list變量傳遞給你的兩個printf類函數調用的一個公共函數。

編輯:從C++語言標準中,「如果參數parmN是引用類型的類型,或者與傳遞沒有參數的參數傳遞的類型不兼容的類型,則行爲是未定義「。 (parmN是傳遞給的va_start參數。)

編輯2:樣品未編譯執行:

void myprintf_core(const char *fmt, va_list ap); 

void myprintf2(const char *fmt, ...) { 
    //... 
    va_list ap; 
    va_start(ap, fmt); 
    myprintf_core(fmt, ap); 
    va_end(ap);  // could be included in myprintf_core 
} 

myprintf_core是你_myprintf但沒有3條va_線,已經被移動到myprintf2

+0

不,我通過一個指針的引用,而不是按值的指針。所以&fmt在myprintf2和_myprintf中是一樣的。我不得不在_myprintf中使用......的唯一原因是編譯器試圖太聰明,並且看到我在沒有'...'的函數中使用了va_start,並且不會讓它編譯。 –

+0

因此... _myprintf中使用的fmt的地址是由myprintf2獲取的堆棧上原始格式字符串的const char *的地址。請參閱....另外,如果您查看輸出,則%s和%c參數會正確打印。只有%u和%f沒有。 –

+0

@MarshallJobe您無法將引用傳遞給va_start;看我的編輯。 – 1201ProgramAlarm

1

當調用函數時,所謂的堆棧框架在堆棧上創建,它包含返回地址,參數以及可能生成的代碼所需的其他元數據。當前函數的參數是而不是傳遞給新函數。

因此在myprintf2當您致電_myprintf時只傳遞fmt參數,其他人都不會傳遞。所以你的vsnprintf調用將導致未定義的行爲,因爲它試圖訪問不存在的參數。

半的圖形堆棧上幀可以被視爲是這樣的:

 
| .          | 
| .          | 
| .          | 
+----------------------------------------+ 
| arguments for the _myprintf function | 
| .          | 
| .          | 
| .          | 
| return address       | 
| Stack frame for the _myprintf function | 
+----------------------------------------+ 
| arguments for the myprintf2 function | 
| .          | 
| .          | 
| .          | 
| return address       | 
| Stack frame for the myprintf2 function | 
+----------------------------------------+ 
| arguments for the main function  | 
| .          | 
| .          | 
| .          | 
| return address       | 
| Stack frame for the main function  | 
+----------------------------------------+ 
| .          | 
| .          | 
| .          | 

應該使它很容易看到爲什麼參數myprintf2不提供給_myprintf

堆棧幀的確切格式和佈局當然是依賴於系統和編譯器的。

+0

我完全瞭解堆棧幀。你錯過了一件非常重要的事情。我正在傳遞一個對fmt字符串的引用,而不是一個指針。 va_start使用fmt字符串的地址來確定格式字符串後面的參數的位置。 主要調用myprintf2並將它們壓入堆棧'float,char,unsigned,char *,char *(格式)。這些都有地址。所有在堆棧上。 –

+0

fmt的地址告訴va_start第一個參數'char *'在哪裏,然後是第二個(未簽名)等等。 通過將_myprintf的引用傳遞給fmt字符串,_myprintf中的&fmt與myprintf2中的&fmt相同。 –

+0

@MarshallJobe如果沒有黑客入侵併且基本上重新實現了'va_'宏,那麼它將無法工作。 –

1

見C++ 14 [support.runtime]/3:

參數parmN是在函數定義的可變參數列表中的最右邊的參數的標識符(在一個只是...之前) 。如果參數parmN是引用類型,或者與傳遞沒有參數的參數時導致的類型不兼容的類型,則行爲是未定義的。

所以,你的代碼會導致不確定的行爲,因爲在_myprintf...前的最後一個參數的引用類型。

相關問題