2009-02-17 12 views
16

A previous question pokazał ładny sposób drukowania na ciąg. Odpowiedź zaangażowany va_copy:va_copy - przeniesienie do wizualnego 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]); 
    } 

}

Problem mam jest, że powyższy kod nie portu do Visual C++, ponieważ nie zapewniają va_copy (lub nawet __va_copy). Czy ktoś wie, jak bezpiecznie przenieść powyższy kod? Przypuszczam, że potrzebuję wykonać kopię va_copy, ponieważ vsnprintf destruktywnie modyfikuje przekazaną va_list.

+0

Zaimplementowałem podobne rzeczy w VC++ i nigdy nie musiałem używać 'va_copy()'. Co stanie się, gdy spróbujesz go bez korzystania z kopii? –

+1

Kto wie ... Może wydawać się działać. Nawet jeśli tak, to nie znaczy, że jest bezpieczny. – user48956

+1

Podobno va_copy() to rzecz C99. W przypadku VC++, będziesz dobrze wykorzystywać oryginalną va_list więcej niż raz, nie martwiąc się o kopię. vsnprintf nie będzie próbował modyfikować przekazanej listy. –

Odpowiedz

12

Powinieneś być w stanie uciec z po prostu robi regularne zadania:

va_list apcopy = ap; 

To technicznie non-przenośne i niezdefiniowane zachowanie, ale to będzie działać z większością kompilatorów i architektur. W konwencji wywoływania x86, va_list s są tylko wskaźnikami do stosu i są bezpieczne do skopiowania.

+1

True - va_copy jest powszechnie definiowana jako "#define va_copy (d, s) ((d) = (s)) ", więc najlepiej byłoby po prostu wrzucić to do nagłówka" przenośności "chronionego przez" #ifndef va_copy " –

+1

To łamie AMD64 z GCC. –

+1

@Alex B: Następnie użyj 'va_copy', który obsługuje GCC. To pytanie dotyczyło w szczególności Visual C++. –

5

Jedną rzeczą, jaką można zrobić, to jeśli nie inaczej trzeba funkcję vformat() przesuń jego wdrożenie do format() funkcji (nietestowanego):

#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]); 
} 
+0

Musiałbyś zadzwonić va_end i va_start ponownie przed drugim wywołaniem do vsnprintf, wouldn ' t-ty –

+0

Hmm - wygląda na to, że masz rację, nigdy wcześniej nie zdawałem sobie z tego sprawy - Naprawiono (myślę). –

+0

@Zenikoder: 'auto_buffer <>' nie jest częścią STL. –

8

dla Windows, można po prostu określić va_copy siebie:

#define va_copy(dest, src) (dest = src) 
+0

Poszedłbym z tym rozwiązaniem, jeśli va_copy nie jest jeszcze zdefiniowany. MSVC definiuje go tylko na rok 2013. Proste zadanie zadziała, jeśli implementacja jest wskaźnikiem stosu, który jest tym, co robi msvc, ale spowoduje problemy w gcc i klang w architekturze 64-bitowej. Zobacz http://www.bailopan.net/blog/?p=30 –

1

va_copy() jest obsługiwany bezpośrednio począwszy od Visual Studio 2013. Jeśli więc możesz polegać na tym, że jest dostępny, nie musisz nic robić.

Powiązane problemy