2011-08-22 51 views
1

請看下面的測試案例:傳遞一個參數列表,以兩個V * printf()的調用

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

void test(char **outa, char **outb, const char* fstra, const char* fstrb, ...) { 
    va_list ap; 

    va_start(ap, fstrb); 
    vasprintf(&outa, fstra, ap); 
    vasprintf(&outb, fstrb, ap); 
    va_end(ap); 
} 

int main(void) { 
    char *a, *b; 
    test(&a, &b, "%s", " %s\n", "foo", "bar"); 
    /* ... */ 
} 

這裏的意圖是,test()功能需要對他們倆兩種格式字符串和參數列表。第一個格式字符串應該「吃」儘可能多的參數,其餘的應該用於第二個格式字符串。

所以,這裏的預期結果是foo & bar這就是我用glibc得到的結果。但AFAICS機器運行鍵盤(猜猜它是一些* BSD),給出foo & foo並且我的猜測是它在參數列表上使用了va_copy()

我想我在這裏打一個不確定的(和醜陋的)行爲;所以問題是:有沒有辦法實現雙格式字符串printf()而不從頭重新實現?有沒有一種很好的方式來檢查使用autoconf的行爲,而不使用AC_RUN_IFELSE()

我想一些掃描格式字符串的消耗參數的數量的快速方法也可以在這裏工作(+ va_copy())。

回答

1

由於其他答案已經指出,通過ap到AV *()函數離開ap處於不確定的狀態。所以,解決方案是不依賴於這個狀態。我建議一個替代解決方法。

首先,正常初始化ap。然後使用vsnprintf(NULL, 0, fstra, ap)確定第一個格式化字符串的長度。連接格式字符串,重新初始化ap,並使用第一個格式化字符串的預定長度拆分輸出。

它應該看起來像下面這樣:

void test(const char* fstra, const char* fstrb, ...) { 
    char *format, *buf; 
    char *a, *b; 
    int a_len, buf_len; 
    va_list ap; 

    va_start(ap, fstrb); 
    a_len = vsnprintf(NULL, 0, fstra, ap); 
    va_end(ap); 

    asprintf(&format, "%s%s", fstra, fstrb); 

    va_start(ap, fstrb); 
    buf_len = vasprintf(&buf, format, ap); 
    va_end(ap); 
    free(format); 

    a = malloc(a_len + 1); 
    memcpy(a, buf, a_len); 
    a[a_len] = '\0'; 

    b = malloc(buf_len - a_len + 1); 
    memcpy(b, buf + a_len, buf_len - a_len); 
    b[buf_len - a_len] = '\0'; 
    free(buf); 
} 

也如the other answer討論,這種方法不開位置printf風格的佔位符("%1$s. I repeat, %1$s.")。所以接口的文檔應該明確指出兩個格式字符串共享相同的位置佔位符名稱空間 - 並且如果其中一個格式字符串使用位置佔位符,則兩者都必須。

4

當您調用v*printf函數之一時,此函數使用va_arg,這意味着ap的值在返回時是不確定的。

相關位在於部分7.19.6.8 The vfprintf function在C99,它引用了註腳:

As the functions vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf, and vsscanf invoke the在va_arg macro, the value of ARG after the return is indeterminate.

這一直延續C1X的最新草案我也一樣,所以我懷疑它不會很快改變。

沒有可移植的方法做你使用更高級的功能v*printf什麼企圖,雖然你可能度假使用低層次的東西。

該標準非常明確,因爲調用者使用va_list變量時使用va_arg的被調用函數會使其不確定。從C99 7.15 Variable Arguments <stdarg.h>

The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.

然而,單一功能內使用它va_arg當的ap值是確定的(否則整個可變參數處理就會崩潰)。所以你可以編寫一個單獨的函數,用這些低級函數輪流處理兩個格式串。

隨着更高級別的內容(按照腳註),您需要va_end/va_startap變量重新置於確定狀態,這將不幸重置爲參數列表的開始。

我不確定你提供的示例代碼有多少簡化,但如果這與接近實際的情況相同,可以通過事先將兩個格式字符串合併並使用它傳遞給vprintf來獲得相同的結果,像:

void test(const char* fstra, const char* fstrb, ...) { 
    char big_honkin_buff[1024]; // Example, don't really do this. 
    va_list ap; 

    strcpy (big_honkin_buff, fstra); 
    strcat (big_honkin_buff, fstrb); 

    va_start(ap, big_honkin_buff); 
    vprintf(big_honkin_buff, ap); 
    va_end(ap); 
} 
+0

嗯,你在最後一句話中並不完全正確。一種可移植的方式是將'v * printf()'完全重新實現爲一次使用兩個格式字符串並直接獲取參數。 –

+0

@Michal,好點。我已經澄清它將該聲明限制在腳註中列出的更高級功能。 – paxdiablo

+0

嗯,我想過串接,這可能是我最終使用的。但我認爲沒有辦法將兩個結果分開(我在實際代碼中使用'vasprintf()'),而沒有冒着相同的sep被'%s'格式之一返回的風險。 –

0

爲了完成其他答案,這是正確的,關於常見實現中會發生什麼的一個詞。

在32位的Linux(我認爲Windows也是如此),通過相同的ap實際工作的兩個功能。
這是因爲va_list只是指向堆棧中參數所在的位置的指針。 v*rintf函數得到它,但不要改變它(它們不能,它通過值傳遞)。

在64位Linux(不知道Windows)中,它不起作用。
va_list是一個結構,並且v*printf獲取一個指向它的指針(因爲實際上它是一個結構體大小爲1的數組)。當參數被消耗時,結構被修改。所以對v*printf的另一個調用將不會從一開始就得到參數,而是在最後一個消耗之後。

當然,這並不意味着您應該在32位Linux中使用兩次va_list。這是未定義的行爲,這在一些實現中發生。不要依賴它。

相關問題