2009-02-17 137 views
16

A previous question顯示了一個打印到字符串的好方法。答案涉及va_copy:va_copy - 移植到Visual C++?

std::string format (const char *fmt, ...); 
{ 
    va_list ap; 
    va_start (ap, fmt); 
    std::string buf = vformat (fmt, ap); 
    va_end (ap); 
    return buf; 
} 


std::string vformat (const char *fmt, va_list ap) 
{ 
    // Allocate a buffer on the stack that's big enough for us almost 
    // all the time. 
    s ize_t size = 1024; 
    char buf[size]; 

    // Try to vsnprintf into our buffer. 
    va_list apcopy; 
    va_copy (apcopy, ap); 
    int needed = vsnprintf (&buf[0], size, fmt, ap); 

    if (needed <= size) { 
     // It fit fine the first time, we're done. 
     return std::string (&buf[0]); 
    } else { 
     // vsnprintf reported that it wanted to write more characters 
     // than we allotted. So do a malloc of the right size and try again. 
     // This doesn't happen very often if we chose our initial size 
     // well. 
     std::vector <char> buf; 
     size = needed; 
     buf.resize (size); 
     needed = vsnprintf (&buf[0], size, fmt, apcopy); 
     return std::string (&buf[0]); 
    } 

}

我遇到的問題是,上面的代碼沒有端口到Visual C++,因爲它不提供va_copy(甚至__va_copy)。那麼,有沒有人知道如何安全地移植上述代碼?據推測,我需要做一個va_copy副本,因爲vsnprintf破壞性地修改了傳遞的va_list。

+0

我在VC++中實現了類似的東西,並且從來不需要使用`va_copy()`。當您嘗試使用而不使用副本時會發生什麼? – 2009-02-17 18:57:05

+1

誰知道... 它似乎工作。即使這樣做,但並不意味着它是安全的。 – user48956 2009-02-17 19:07:32

+1

顯然va_copy()是一個C99的東西。對於VC++來說,你可以多次使用原始的va_list,而不必擔心副本。 vsnprintf不會嘗試修改傳遞的列表。 – 2009-02-17 19:50:16

回答

12

你應該能夠逃脫只是做了常規分配:

va_list apcopy = ap; 

這在技術上是不可移植的和不確定的行爲,但它會與大多數編譯器和架構工作。在x86調用約定中,va_list只是指向堆棧的指針,並且可以安全地進行復制。

5

一兩件事你可以做的是,如果你不以其他方式需要vformat()功能,將其實施到format()功能(未經測試):

#include <stdarg.h> 
#include <string.h> 
#include <assert.h> 
#include <string> 
#include <vector> 


std::string format(const char *fmt, ...) 
{ 
    va_list ap; 

    enum {size = 1024}; 

    // if you want a buffer on the stack for the 99% of the time case 
    // for efficiency or whatever), I suggest something like 
    // STLSoft's auto_buffer<> template. 
    // 
    // http://www.synesis.com.au/software/stlsoft/doc-1.9/classstlsoft_1_1auto__buffer.html 
    // 
    std::vector<char> buf(size); 

    // 
    // where you get a proper vsnprintf() for MSVC is another problem 
    // maybe look at http://www.jhweiss.de/software/snprintf.html 
    // 

    // note that vsnprintf() might use the passed ap with the 
    // va_arg() macro. This would invalidate ap here, so we 
    // we va_end() it here, and have to redo the va_start() 
    // if we want to use it again. From the C standard: 
    // 
    //  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. 
    // 
    // Thanks to Rob Kennedy for pointing that out. 
    // 
    va_start (ap, fmt); 
    int needed = vsnprintf (&buf[0], buf.size(), fmt, ap); 
    va_end(ap); 

    if (needed >= size) { 
     // vsnprintf reported that it wanted to write more characters 
     // than we allotted. So do a malloc of the right size and try again. 
     // This doesn't happen very often if we chose our initial size 
     // well. 
     buf.resize(needed + 1); 

     va_start (ap, fmt); 
     needed = vsnprintf (&buf[0], buf.size(), fmt, ap); 
     va_end(ap); 

     assert(needed < buf.size()); 
    } 

    return std::string(&buf[0]); 
} 
8

對於Windows,你可以簡單地定義va_copy自己:

#define va_copy(dest, src) (dest = src) 
1

va_copy()直接支持從Visual Studio 2013開始。所以,如果你可以依靠這個可用的,你不需要做任何事情。