2015-06-23 80 views
5

我想設計一個函數,它帶有可變數量的參數,其中一個參數本身就是va_list;但我的代碼中出現錯誤,我不明白是什麼...把一個va_list變量放在一個變量參數列表(!)裏面

警告 - 我的問題不是關於設計一個代碼做我想做的事情(我找到了一種方法來繞過這個問題),但只有理解我做錯了什麼......

解釋我的問題,讓我們先從一個簡單的例子,即:一個功能ffprintf這就像fprintf,但是寫的內容下載到若干字符串,字符串,其數量爲由ffprintf的第一個參數指示,其身份由下一個參數給出(這些參數的數量可能因呼叫而異,因此您必須使用變量argum ent list)。這樣的功能會被這樣使用:

FILE *stream0, *stream1, *stream2; 
int a, b; 
ffprintf (3, stream0, stream1, stream2, "%d divided by %d worths %f", a, b, (double)a/b); 

而且它的代碼是:

void ffprintf (int z, ...) 
{va_list vlist, auxvlist; 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    va_start (vlist, z); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist, FILE *); // Getting the next stream argument 
    } 
    char const *format = va_arg (vlist, char const *); // Getting the format argument 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxvlist, vlist); // You have to work on a copy "auxvlist" of "vlist", for otherwise "vlist" would be altered by the next line 
    vfprintf (streams[i], format, auxvlist); 
    va_end (auxvlist); 
    } 
    va_end (vlist); 
    free (streams); 
} 

這工作正常。現在,也有標準功能vfprintf,它的原型是vfprintf (FILE *stream, char const* format, va_list vlist);,並且你使用這樣來創建具有可變參數列表中的其他功能:

void fprintf_variant (FILE *stream, char const* format, ...) 
{ 
    va_list vlist; 
    va_start (vlist, format); 
    vfprintf (stream, format, vlist); 
    va_end (vlist); 
} 

這太正常工作。現在,我的目標是這兩個觀念結合起來,創造一個功能,我會打電話vffprintf,你會使用這樣的:

FILE *stream0, *stream1, *stream2; 
void fprintf_onto_streams012 (char const *format, ...) 
{va_list vlist; 
    va_start (vlist, format); 
    vffprintf (3, stream0, stream1, stream2, format, vlist); 
    va_end (vlist); 
} 

我設計了下面的代碼:

void vffprintf (int z, ...) 
{va_list vlist, auxvlist, auxauxvlist; 
    va_start (vlist, z); 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist, FILE *); 
    } 
    char const *format = va_arg (vlist, char const *); 
    va_copy (auxvlist, va_arg (vlist, va_list)); // Here I get the next argument of "vlist", knowing that this argument is of "va_list" type 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxauxvlist, auxvlist); 
    vfprintf (streams[i], format, auxvlist); 
    va_end (auxauxvlist); 
    } 
    va_end (auxvlist); 
    va_end (vlist); 
    free (streams); 
} 

此代碼編譯順利,但它不能正常工作...例如,如果我寫了下面的完整代碼:

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

void vffprintf (int z, ...) 
{va_list vlist, auxvlist, auxauxvlist; 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    va_start (vlist, z); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist, FILE *); 
    } 
    char const *format = va_arg (vlist, char const *); 
    va_copy (auxvlist, va_arg (vlist, va_list)); 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxauxvlist, auxvlist); 
    vfprintf (streams[i], format, auxauxvlist); 
    va_end (auxauxvlist); 
    } 
    va_end (auxvlist); 
    va_end (vlist); 
    free (streams); 
} 

void printf_variant (char const *format, ...) 
{va_list vlist; 
    va_start (vlist, format); 
    vffprintf (1, stdout, format, vlist); 
    va_end (vlist); 
} 

int main (void) 
{printf_variant ("Ramanujan's number is %d.\n", 1729); 
    return 0; 
} 

,我收到了段錯誤...爲什麼?!

P.-S .:對不起,這個很長的問題;但我希望它非常清楚,因爲它是相當技術性的...

P.-S.2:我故意爲這個問題使用了兩個標籤「va-list」和「variableargumentlists」,因爲這對我感興趣是va_list,看作是類型,在(其他)變量參數列表中,被看作列表 ...因此,這些實際上是兩個不同的概念。

+0

所以問題是爲什麼最後一個例子失敗? – this

+0

是的,這正是:-) –

+0

如果您忽略內存泄漏,看起來很好。 – this

回答

2

va_arg在C11(N1570)的最後草案說明包含(類型是第二個參數):

如果類型是不符合實際的下一個的類型兼容參數(根據默認參數促銷推廣),行爲未定義

va_list允許爲數組類型(th e標準要求它是一個所謂的「完整對象類型」),看起來你的實現使用了這種可能性。您可能知道在C數組中,不能將它們作爲參數傳遞,因爲它們會衰減爲指針,並且此類指針的類型與原始數組類型不兼容。例如:int *int [1]不兼容。所以如果你真的需要傳遞一個數組或者一個va_list,那麼用一個va_list成員來定義一個struct並且通過它(見Why can't we pass arrays to function by value?)。

+0

Hello cremno, 我還沒有掌握所有的細節,但我有一個印象,你的觀點與標準中的下列子句相聯繫:「參數'type'是一個指定的類型名稱,因此指針的類型到一個具有指定類型的對象可以簡單地通過給'type'添加'*'來獲得。但是沒有什麼能保證'va_list'會是這樣一種類型,例如數組類型不起作用......它是正確的嗎? 另一方面,這似乎並不能解釋Eric Tsui的「void *」技巧是如何工作的...... –

+1

@ Nancy-N:這是一個**技巧**。但他必須解釋這一點,因爲這是他答案的一部分。它可能會在你永遠關心的任何地方工作。這並不會改變它是UB的事實(他發佈的描述也是錯誤的)。 [這裏](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50581)是海灣合作委員會的一個錯誤報告,他的記者想要做類似的事情,甚至與你一樣。它甚至在C90中提到了一個邊緣案例!也許閱讀它會清除比我的答案更多的東西。它還提到使用結構作爲可能的解決方案。 – cremno

+0

@cremno,感謝您的糾正。所以,在不同的'va_list'實現下,我對'va_list'和'va_list *'做了更多的研究,這確實是個問題。 –

1
void vffprintf (int z, ...) 
{ 
    //... 
    va_copy (auxvlist, va_arg (vlist, va_list));//this line has the problem 
    //... 
} 

只是一個快速和棘手的方式,它會工作。

void vffprintf (int z, ...) 
{ 
    //... 
    va_copy (auxvlist, va_arg (vlist, void*)); 
    //... 
} 

以下是有關var_argva_list一定的參考,這應該提供全面細緻的解釋。

1)Pass va_list or pointer to va_list?

2)Is GCC mishandling a pointer to a va_list passed to a function?

3)What is the format of the x86_64 va_list structure?

希望它們是有幫助的。

+1

確實有效;了不起!:-D如果你有更詳細的解釋,我很想聽到它:爲什麼前一行失敗?爲什麼新的工作?它這個「無效*技巧」是否便攜?... –

+0

對我而言還不清楚。當'printf_variant'調用'vffprintf'時,'vffprintf'的變量參數列表是{'stdout','format','vlist_0'},'vlist_0'是'printf_variant'的變量參數列表,即{'1929 '}。所以,當調用'va_arg(vlist_1,va_list)'(我已經在後綴'_0'和'_1'中設置了兩個'vlist'清除之間的區別時),'vlist_1'的下一個參數是{'1929'}具體可能是'struct {char *,int}'。但是這不是一個整數,是嗎?......無論如何,這怎麼可能轉化爲一個'void *'?! 對不起,沒有更容易理解... –

+1

@ Nancy-N,我試圖做的就是這樣'va_copy(auxvlist,va_arg(vlist,va_list *))',但它會給出警告。所以,我把它改成''va_copy(auxvlist,va_arg(vlist,void *))'' –

1

您可能需要包裝類型va_list成一個結構,如果你想使用的va_arg()來檢索它:

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

typedef struct 
{ 
    va_list list ; 
} my_list ; 

void vffprintf (int z, ...) 
{my_list vlist, auxvlist, auxauxvlist; 
    FILE **streams = malloc (z * sizeof(FILE *)); 
    va_start (vlist.list, z); 
    for (int i = 0; i < z; ++i) 
    {streams[i] = va_arg (vlist.list, FILE *); 
    } 
    char const *format = va_arg (vlist.list, char const *); 
    my_list parent = va_arg (vlist.list, my_list) ; 
    va_copy (auxvlist.list, parent.list); 
    for (int i = 0; i < z; ++i) 
    {va_copy (auxauxvlist.list, auxvlist.list); 
    vfprintf (streams[i], format, auxauxvlist.list); 
    va_end (auxauxvlist.list); 
    } 
    va_end (auxvlist.list); 
    va_end (vlist.list); 
    free (streams); 
} 

void printf_variant (char const *format, ...) 
{my_list vlist; 
    va_start (vlist.list, format); 
    vffprintf (1, stdout , format, vlist); 
    va_end (vlist.list); 
} 

int main (void) 
{printf_variant ("Ramanujan's number is %d.\n", 1729); 
    return 0; 
} 

問題來自這樣一個事實陣列和相同類型的指針是不兼容,並且va_list被定義爲一個數組。然後,你試圖獲取類型:

va_arg (vlist, va_list) 

所以,你告訴va_arg你得到一個數組,但如果實際上傳遞va_list已經衰減到一個指針。你應該使用指針版本va_list,但你不知道va_list的真正定義,所以你不能獲得它的指針版本。

解決方案是將va_list包裝成您控制的類型,一個結構。

+0

@ this - 謝謝你繞過這個問題(我不知道)和寫下代碼的方式! :-) –

+0

@ Nancy-N那麼這段代碼是否確實解決了這個問題? – this

+0

@這 - 確實 - 至少在我自己的機器上;-) –