2009-02-17 33 views
16

A previous question cho thấy một cách in đẹp đến một chuỗi. Câu trả lời có liên quan va_copy:va_copy - chuyển sang hình ảnh 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]); 
    } 

}

Vấn đề tôi đang gặp là các mã trên không cảng để Visual C++ vì nó không cung cấp va_copy (hoặc thậm chí __va_copy). Vì vậy, không ai biết làm thế nào để an toàn cổng mã trên? Có lẽ, tôi cần phải làm một bản sao va_copy vì vsnprintf phá hủy sửa đổi va_list đã qua.

+0

Tôi đã thực hiện những điều tương tự trong VC++ và không bao giờ cần sử dụng 'va_copy()'. Điều gì sẽ xảy ra khi bạn thử nó mà không cần sử dụng bản sao? –

+1

Ai biết được ... Có vẻ như nó hoạt động. Ngay cả khi nó có, nó không có nghĩa là nó an toàn. – user48956

+1

Rõ ràng va_copy() là một điều C99. Đối với VC++, bạn sẽ chỉ sử dụng va_list gốc nhiều lần mà không phải lo lắng về bản sao. vsnprintf sẽ không cố sửa đổi danh sách được chuyển. –

Trả lời

12

Bạn sẽ có thể nhận được ngay với chỉ làm một nhiệm vụ thường xuyên:

va_list apcopy = ap; 

Đó là về mặt kỹ thuật không cầm tay và hành vi không xác định, nhưng nó sẽ làm việc với hầu hết các trình biên dịch và kiến ​​trúc. Trong quy ước gọi điện x86, va_list chỉ là con trỏ vào ngăn xếp và an toàn để sao chép.

+1

True - va_copy thường được định nghĩa là "#define va_copy (d, s) ((d) = (s)) ", do đó, có thể tốt nhất là chỉ cần ném vào tiêu đề 'di động' được bảo vệ bởi" #ifndef va_copy " –

+1

Điều này sẽ phá vỡ trên AMD64 với GCC. –

+1

@ Alex B: Sau đó sử dụng 'va_copy', mà GCC hỗ trợ. Câu hỏi này đặc biệt về Visual C++. –

5

Một điều bạn có thể làm là nếu bạn không có thoả cần vformat() chức năng, di chuyển thực hiện vào format() chức năng (chưa được kiểm tra):

#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

Bạn phải gọi va_end và va_start một lần nữa trước khi cuộc gọi thứ hai đến vsnprintf, wouldn ' –

+0

Hmm - có vẻ như bạn đã đúng Tôi không bao giờ nhận ra điều đó trước đây - Cố định (tôi nghĩ). –

+0

@Zenikoder: 'auto_buffer <>' không phải là một phần của STL. –

8

Đối với Windows, bạn chỉ có thể xác định va_copy mình:

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

Tôi sẽ đi với giải pháp này nếu va_copy chưa được xác định. MSVC chỉ định nghĩa nó cho năm 2013. Một nhiệm vụ đơn giản sẽ làm việc nếu việc thực hiện là một con trỏ ngăn xếp đó là những gì msvc làm, nhưng sẽ gây ra vấn đề trong gcc và clang với một kiến ​​trúc 64bit. Xem http://www.bailopan.net/blog/?p=30 –

1

va_copy() được hỗ trợ trực tiếp bắt đầu từ Visual Studio 2013. Vì vậy, nếu bạn có thể dựa vào đó là có sẵn, bạn không cần phải làm bất cứ điều gì.

Các vấn đề liên quan