2008-09-03 33 views
43

Tôi đang tìm cách làm điều này trong C/C++.Làm thế nào để bọc một hàm với các đối số độ dài thay đổi?

Tôi đã xem qua Variable Length Arguments nhưng điều này gợi ý giải pháp với Python & C bằng cách sử dụng libffi.

Bây giờ, nếu tôi muốn quấn printf chức năng với myprintf

Những gì tôi làm là như dưới đây:

void myprintf(char* fmt, ...) 
{ 
    va_list args; 
    va_start(args,fmt); 
    printf(fmt,args); 
    va_end(args); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int a = 9; 
    int b = 10; 
    char v = 'C'; 
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n",a, v, b); 
    return 0; 
} 

Nhưng kết quả không như mong đợi!

This is a number: 1244780 and 
this is a character: h and 
another number: 29953463 

Bất kỳ điểm nào tôi đã bỏ lỡ ??

+2

Câu trả lời cho câu hỏi này là _very_ khác với C++ 11. –

+0

@MooingDuck Thật vậy, tôi đã thêm một câu trả lời 'Variadic templates', bạn có nghĩ rằng có một cách hay hơn trong C++ 11 không? –

+0

@MooingDuck Chức năng vararg không phải là hàm mẫu variadic. Chúng khác nhau về bản chất và chủng loại. – rubenvb

Trả lời

63

vấn đề là bạn không thể sử dụng 'printf' với va_args. Bạn phải sử dụng vprintf nếu bạn đang sử dụng danh sách đối số biến. vprint, vsprintf, vfprintf, vv (có cũng được phiên bản 'an toàn' trong thời gian chạy C của Microsoft sẽ ngăn chặn vượt đệm, vv)

Bạn công trình mẫu như sau:

void myprintf(char* fmt, ...) 
{ 
    va_list args; 
    va_start(args,fmt); 
    vprintf(fmt,args); 
    va_end(args); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int a = 9; 
    int b = 10; 
    char v = 'C'; 
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n",a, v, b); 
    return 0; 
} 
+0

Bạn có thể làm sáng tỏ một số lý do tại sao "bạn không thể sử dụng' printf' với 'va_args'"? Tại sao 'vprintf'? –

+1

@JohnStrood Để trả lời câu hỏi này trực tiếp: vì printf() không chấp nhận một 'va_list' làm đối số, nó mong đợi một số đối số thay đổi (ví dụ: "...") khác nhau. Xem trang hướng dẫn cho printf() và vprintf(). Không có nơi nào nó nói printf() chấp nhận 'va_list' như một đối số, chỉ có một số biến của các đối số của kiểu mã định dạng% mong đợi (int, long, float, vv). Chỉ có họ vprintf() mới chấp nhận một va_list. – erco

1

Bạn đang sử dụng C hoặc C++? Phiên bản C++ tiếp theo, C++ 0x, sẽ hỗ trợ variadic templates cung cấp giải pháp cho vấn đề đó.

workaround khác có thể đạt được bằng cách khai thác thông minh quá tải để đạt được một cú pháp như thế này:

void f(varargs va) { 
    BOOST_FOREACH(varargs::iterator i, va) 
     cout << *i << " "; 
} 

f(args = 1, 2, 3, "Hello"); 

Để có được điều này để làm việc, lớp varargs phải được thực hiện để ghi đè operator = trả về một đối tượng proxy thay vào đó, ghi đè operator ,. Tuy nhiên, làm cho loại biến thể này an toàn trong C++ hiện tại là không thể được như xa như tôi biết vì nó sẽ phải làm việc bằng loại xóa.

+1

C++ 03 có thể sử dụng 'boost :: tuple' có cách để thực hiện điều này một cách an toàn. –

7

Tôi cũng không chắc chắn những gì bạn có nghĩa là bởi tinh khiết

Trong C++ chúng ta sử dụng

#include <cstdarg> 
#include <cstdio> 

class Foo 
{ void Write(const char* pMsg, ...); 
}; 

void Foo::Write(const char* pMsg, ...) 
{ 
    char buffer[4096]; 
    std::va_list arg; 
    va_start(arg, pMsg); 
    std::vsnprintf(buffer, 4096, pMsg, arg); 
    va_end(arg); 
    ... 
} 
0
void myprintf(char* fmt, ...) 
{ 
    va_ list args; 
    va_ start(args,fmt); 
    printf(fmt,args); ----> This is the fault. vprintf(fmt, args); should have been used. 
    va_ end(args); 
} 
If you're just trying to call printf, 
there's a printf variant called vprintf that takes 
the va_list directly : vprintf(fmt, args); 
9

Trong C++ 11 này là một trong những giải pháp có thể sử dụng Variadic templates:

template<typename... Args> 
void myprintf(const char* fmt, Args... args) 
{ 
    std::printf(fmt, args...) ; 
} 

EDIT

Vì @rubenvb chỉ ra rằng có sự cân nhắc để cân nhắc, ví dụ bạn sẽ tạo mã cho mỗi trường hợp sẽ dẫn đến mã bloat.

2

Thực ra, có cách gọi một hàm không có phiên bản va_list từ trình bao bọc.Ý tưởng là sử dụng trình kết hợp, không chạm vào các đối số trong ngăn xếp và tạm thời thay thế địa chỉ trả về hàm.

Ví dụ về Visual C x86. call addr_printf gọi printf():

__declspec(thread) static void* _tls_ret; 

static void __stdcall saveret(void *retaddr) { 
    _tls_ret = retaddr; 
} 

static void* __stdcall _getret() { 
    return _tls_ret; 
} 

__declspec(naked) 
static void __stdcall restret_and_return_int(int retval) { 
    __asm { 
     call _getret 
     mov [esp], eax ; /* replace current retaddr with saved */ 
     mov eax, [esp+4] ; /* retval */ 
     ret 4 
    } 
} 

static void __stdcall _dbg_printf_beg(const char *fmt, va_list args) { 
    printf("calling printf(\"%s\")\n", fmt); 
} 

static void __stdcall _dbg_printf_end(int ret) { 
    printf("printf() returned %d\n", ret); 
} 

__declspec(naked) 
int dbg_printf(const char *fmt, ...) 
{ 
    static const void *addr_printf = printf; 
    /* prolog */ 
    __asm { 
     push ebp 
     mov ebp, esp 
     sub esp, __LOCAL_SIZE 
     nop 
    } 
    { 
     va_list args; 
     va_start(args, fmt); 
     _dbg_printf_beg(fmt, args); 
     va_end(args); 
    } 
    /* epilog */ 
    __asm { 
     mov esp, ebp 
     pop ebp 
    } 
    __asm { 
     call saveret 
     call addr_printf 
     push eax 
     push eax 
     call _dbg_printf_end 
     call restret_and_return_int 
    } 
} 
+1

Tôi thậm chí sẽ không bao giờ dám viết điều này, nhưng tôi không thể ngưỡng mộ nó. –

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