2008-09-29 77 views
143

Trong C, có thể chuyển tiếp lời gọi hàm variadic không? Như trong,Chuyển tiếp lời gọi hàm variadic trong C

int my_printf(char *fmt, ...) { 
    fprintf(stderr, "Calling printf with fmt %s", fmt); 
    return SOMEHOW_INVOKE_LIBC_PRINTF; 
} 

Chuyển tiếp invocation theo cách trên rõ ràng là không thực sự cần thiết trong trường hợp này (kể từ khi bạn có thể đăng nhập lời gọi theo những cách khác, hoặc sử dụng vfprintf), nhưng codebase tôi đang làm việc trên đòi hỏi các wrapper để làm một số công việc thực tế, và không có (và không thể có thêm) một chức năng trợ giúp giống như vfprintf.

[Cập nhật: dường như có một số nhầm lẫn dựa trên các câu trả lời đã được cung cấp cho đến thời điểm này. Để cụm từ câu hỏi một cách khác:. Nói chung, bạn có thể quấn một số chức năng variadic tùy ý mà không sửa đổi định nghĩa của hàm]

+1

tuyệt vời câu hỏi - may mắn thay tôi có thể thêm một tình trạng quá tải vFunc mà phải mất một va_list. Cảm ơn vì đăng. – Gishu

Trả lời

122

Nếu bạn không có chức năng tương tự như vfprintf phải mất va_list thay vì số lượng đối số, bạn không thể làm điều đó. Xem http://c-faq.com/varargs/handoff.html.

Ví dụ:

void myfun(const char *fmt, va_list argp) { 
    vfprintf(stderr, fmt, argp); 
} 
+0

Cảm ơn, trả lời câu hỏi của tôi một cách hoàn hảo. – Patrick

+2

Tùy thuộc vào mức độ quan trọng của vấn đề, lời gọi bằng cách lắp ráp nội tuyến vẫn cung cấp khả năng này. – daluege

3

Sử dụng vfprintf:

int my_printf(char *fmt, ...) { 
    va_list va; 
    int ret; 

    va_start(va, fmt); 
    ret = vfprintf(stderr, fmt, va); 
    va_end(va); 
    return ret; 
} 
+1

Cảm ơn, nhưng từ câu hỏi: "codebase tôi đang làm việc trên [...] không có (và không thể thêm) một hàm trợ giúp giống như vfprintf." – Patrick

10

Hầu như, sử dụng cơ sở vật chất có sẵn trong <stdarg.h>:

#include <stdarg.h> 
int my_printf(char *format, ...) 
{ 
    va_list args; 
    va_start(args, format); 
    int r = vprintf(format, args); 
    va_end(args); 
    return r; 
} 

Lưu ý rằng bạn sẽ cần phải sử dụng phiên bản vprintf thay vì đồng bằng printf. Không có cách nào để gọi trực tiếp hàm variadic trong trường hợp này mà không sử dụng va_list.

+1

Cảm ơn, nhưng từ câu hỏi: "codebase tôi đang làm việc trên [...] không có (và không thể thêm) một hàm trợ giúp giống như vfprintf." – Patrick

2

Không có cách nào để chuyển tiếp các cuộc gọi chức năng như vậy vì vị trí duy nhất mà bạn có thể truy xuất các phần tử ngăn xếp nguyên là ở số my_print(). Cách thông thường để bao bọc các cuộc gọi như vậy là có hai hàm, một hàm chỉ chuyển đổi các đối số thành các cấu trúc varargs khác nhau và một hàm khác thực sự hoạt động trên các cấu trúc đó. Sử dụng mô hình chức năng kép như vậy, bạn có thể (ví dụ) quấn printf() bằng cách khởi tạo các cấu trúc trong my_printf() với va_start() và sau đó chuyển chúng đến vfprintf().

+0

Vì vậy, không có cách nào để chuyển tiếp lời gọi nếu bạn không thể sửa đổi việc triển khai hàm bạn muốn bọc? – Patrick

+0

Phải. Đặt cược tốt nhất của bạn là để đẩy cho một wrapper kiểu vprintf được thêm vào. –

44

Không trực tiếp, tuy nhiên nó là phổ biến (và bạn sẽ tìm thấy hầu như các trường hợp trong thư viện tiêu chuẩn) cho các chức năng variadic để đi từng cặp với một chức năng thay thế varargs phong cách. ví dụ. printf/vprintf

Chức năng v ... có tham số va_list, việc thực hiện thường được thực hiện với thuật toán 'macro ma thuật' cụ thể, nhưng bạn được đảm bảo gọi hàm v ... từ hàm variadic như thế này sẽ hoạt động:

#include <stdarg.h> 

int m_printf(char *fmt, ...) 
{ 
    int ret; 

    /* Declare a va_list type variable */ 
    va_list myargs; 

    /* Initialise the va_list variable with the ... after fmt */ 

    va_start(myargs, fmt); 

    /* Forward the '...' to vprintf */ 
    ret = vprintf(fmt, myargs); 

    /* Clean up the va_list */ 
    va_end(myargs); 

    return ret; 
} 

Điều này sẽ cho bạn hiệu quả mà bạn đang tìm kiếm.

Nếu bạn đang cân nhắc việc viết một hàm thư viện variadic, bạn cũng nên cân nhắc tạo một kiểu đồng hành va_list có sẵn như một phần của thư viện. Như bạn có thể thấy từ câu hỏi của mình, nó có thể hữu ích cho người dùng của bạn.

+0

Tôi khá chắc chắn bạn cần phải gọi 'va_copy' trước khi chuyển biến' myargs' sang một hàm khác. Vui lòng xem [MSC39-C] (https://www.securecoding.cert.org/confluence/display/c/MSC39-C.+Do+not+call+va_arg() + trên + a + va_list + that + has + một + giá trị không xác định), trong đó nó nói rằng những gì bạn đang làm là hành vi không xác định. – aviggiano

+1

@aviggiano: Quy tắc là "Không gọi' va_arg() 'trên' va_list' có giá trị không xác định ", tôi không làm điều đó vì tôi không bao giờ sử dụng' va_arg' trong hàm gọi của mình. Các _value_ của 'myargs' sau khi cuộc gọi (trong trường hợp này) để' vprintf' là _indeterminate_ (giả định rằng nó hiện chúng tôi 'va_arg'). Tiêu chuẩn nói rằng 'myargs'" sẽ được chuyển tới macro 'va_end' trước khi có bất kỳ tham chiếu nào khác tới [it] "; đó là chính xác những gì tôi làm. Tôi không cần phải sao chép mà arg vì tôi không có ý định lặp qua chúng trong chức năng gọi. –

+0

Bạn nói đúng. Trang [Linux man] (http://linux.die.net/man/3/va_arg) xác nhận điều đó. _Nếu 'ap' được truyền cho một hàm sử dụng' va_arg (ap, type) 'thì giá trị của' ap' không được xác định sau khi hàm đó trả về._ – aviggiano

39

C99 hỗ trợ macros with variadic arguments; tùy thuộc vào trình biên dịch của bạn, bạn có thể khai báo một macro mà những gì bạn muốn:

#define my_printf(format, ...) \ 
    do { \ 
     fprintf(stderr, "Calling printf with fmt %s\n", format); \ 
     some_other_variadac_function(format, ##__VA_ARGS__); \ 
    } while(0) 

Nói chung, tuy nhiên, giải pháp tốt nhất là sử dụng va_list hình thức của hàm bạn đang cố gắng bọc, nên một tồn tại.

1

Xin lỗi vì sự rant off-topic, nhưng:

meta vấn đề là giao diện varargs trong C đã bị phá vỡ cơ bản ngay từ đầu. Nó là một lời mời để đệm tràn bộ nhớ và truy cập bộ nhớ không hợp lệ vì kết thúc danh sách đối số không thể được tìm thấy mà không có một tín hiệu kết thúc rõ ràng (mà không ai thực sự sử dụng ra khỏi sự lười biếng). Và nó luôn dựa trên các macro cụ thể thực hiện bí truyền, với macro va_copy() quan trọng chỉ được hỗ trợ trên một số kiến ​​trúc.

+0

Nó khá là kludge, nhưng nó là công bằng đối với K & R, một phải lưu ý rằng nó có lẽ tốt hơn bất cứ thứ gì khác sẵn có vào thời điểm đó. Có bất kỳ ngôn ngữ nào khác cung cấp bất kỳ loại hỗ trợ chức năng hiệu quả nào của variadic không? Khiếu nại thực sự lớn nhất của tôi là thiếu tính sẵn có của va_copy và thiếu sự hỗ trợ tiếp xúc cho vxprintf chung (gửi từng ký tự đến một hàm được chỉ định). Tôi có một số kìm kẹp với ngôn ngữ C (ví dụ: quy tắc so sánh nguyên) nhưng không quá nhiều với va_args. – supercat

1

Có, bạn có thể làm điều đó, nhưng nó có phần xấu xí và bạn phải biết số lượng đối số tối đa. Hơn nữa nếu bạn đang ở trên một kiến ​​trúc mà các đối số không được truyền trên stack như x86 (ví dụ, PowerPC), bạn sẽ phải biết nếu các loại "đặc biệt" (đôi, nổi, altivec vv) được sử dụng và nếu vì vậy, đối phó với chúng cho phù hợp. Nó có thể gây đau đớn một cách nhanh chóng nhưng nếu bạn đang ở trên x86 hoặc nếu chức năng ban đầu có chu vi được xác định rõ ràng và giới hạn, nó có thể hoạt động. Nó vẫn sẽ là một hack, sử dụng nó cho mục đích gỡ lỗi. Đừng xây dựng phần mềm cho bạn. Dù sao, đây là một ví dụ làm việc trên x86:

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

int old_variadic_function(int n, ...) 
{ 
    va_list args; 
    int i = 0; 

    va_start(args, n); 

    if(i++<n) printf("arg %d is 0x%x\n", i, va_arg(args, int)); 
    if(i++<n) printf("arg %d is %g\n", i, va_arg(args, double)); 
    if(i++<n) printf("arg %d is %g\n", i, va_arg(args, double)); 

    va_end(args); 

    return n; 
} 

int old_variadic_function_wrapper(int n, ...) 
{ 
    va_list args; 
    int a1; 
    int a2; 
    int a3; 
    int a4; 
    int a5; 
    int a6; 
    int a7; 
    int a8; 

    /* Do some work, possibly with another va_list to access arguments */ 

    /* Work done */ 

    va_start(args, n); 

    a1 = va_arg(args, int); 
    a2 = va_arg(args, int); 
    a3 = va_arg(args, int); 
    a4 = va_arg(args, int); 
    a5 = va_arg(args, int); 
    a6 = va_arg(args, int); 
    a7 = va_arg(args, int); 

    va_end(args); 

    return old_variadic_function(n, a1, a2, a3, a4, a5, a6, a7, a8); 
} 

int main(void) 
{ 
    printf("Call 1: 1, 0x123\n"); 
    old_variadic_function(1, 0x123); 
    printf("Call 2: 2, 0x456, 1.234\n"); 
    old_variadic_function(2, 0x456, 1.234); 
    printf("Call 3: 3, 0x456, 4.456, 7.789\n"); 
    old_variadic_function(3, 0x456, 4.456, 7.789); 
    printf("Wrapped call 1: 1, 0x123\n"); 
    old_variadic_function_wrapper(1, 0x123); 
    printf("Wrapped call 2: 2, 0x456, 1.234\n"); 
    old_variadic_function_wrapper(2, 0x456, 1.234); 
    printf("Wrapped call 3: 3, 0x456, 4.456, 7.789\n"); 
    old_variadic_function_wrapper(3, 0x456, 4.456, 7.789); 

    return 0; 
} 

Đối với một số lý do, bạn không thể sử dụng phao nổi với va_arg, gcc nói rằng họ đang chuyển đổi sang gấp đôi nhưng chương trình bị treo. Điều đó một mình chứng minh rằng giải pháp này là một hack và rằng không có giải pháp chung. Trong ví dụ của tôi, tôi giả định rằng số lượng đối số tối đa là 8, nhưng bạn có thể tăng số đó. Hàm bọc cũng chỉ sử dụng các số nguyên nhưng nó hoạt động theo cùng một cách với các tham số 'bình thường' khác vì chúng luôn luôn truyền đến số nguyên. Hàm mục tiêu sẽ biết loại của chúng nhưng trình bao bọc trung gian của bạn không cần. Trình bao bọc cũng không cần biết số lượng đối số phù hợp vì hàm mục tiêu cũng sẽ biết nó. Để thực hiện công việc hữu ích (ngoại trừ việc chỉ ghi nhật ký cuộc gọi), bạn có thể sẽ phải biết cả hai.

9

Vì thực sự không thể chuyển tiếp cuộc gọi như vậy một cách tốt đẹp, chúng tôi đã làm việc này bằng cách thiết lập một khung ngăn xếp mới với bản sao của khung ngăn xếp gốc. Tuy nhiên, đây là số không thể chuyển đổi cao và làm cho tất cả các loại giả định, ví dụ: mã sử dụng con trỏ khung và các quy ước gọi điện 'chuẩn'.

Tệp tiêu đề này cho phép bọc các hàm variadic cho x86_64 và i386 (GCC). Nó không hoạt động đối với các đối số dấu chấm động, nhưng nên thẳng về phía trước để mở rộng để hỗ trợ các đối số đó.

#ifndef _VA_ARGS_WRAPPER_H 
#define _VA_ARGS_WRAPPER_H 
#include <limits.h> 
#include <stdint.h> 
#include <alloca.h> 
#include <inttypes.h> 
#include <string.h> 

/* This macros allow wrapping variadic functions. 
* Currently we don't care about floating point arguments and 
* we assume that the standard calling conventions are used. 
* 
* The wrapper function has to start with VA_WRAP_PROLOGUE() 
* and the original function can be called by 
* VA_WRAP_CALL(function, ret), whereas the return value will 
* be stored in ret. The caller has to provide ret 
* even if the original function was returning void. 
*/ 

#define __VA_WRAP_CALL_FUNC __attribute__ ((noinline)) 

#define VA_WRAP_CALL_COMMON()          \ 
    uintptr_t va_wrap_this_bp,va_wrap_old_bp;      \ 
    va_wrap_this_bp = va_wrap_get_bp();        \ 
    va_wrap_old_bp = *(uintptr_t *) va_wrap_this_bp;    \ 
    va_wrap_this_bp += 2 * sizeof(uintptr_t);      \ 
    size_t volatile va_wrap_size = va_wrap_old_bp - va_wrap_this_bp; \ 
    uintptr_t *va_wrap_stack = alloca(va_wrap_size);     \ 
    memcpy((void *) va_wrap_stack,         \ 
     (void *)(va_wrap_this_bp), va_wrap_size); 


#if (__WORDSIZE == 64) 

/* System V AMD64 AB calling convention */ 

static inline uintptr_t __attribute__((always_inline)) 
va_wrap_get_bp() 
{ 
    uintptr_t ret; 
    asm volatile ("mov %%rbp, %0":"=r"(ret)); 
    return ret; 
} 


#define VA_WRAP_PROLOGUE()   \ 
    uintptr_t va_wrap_ret;   \ 
    uintptr_t va_wrap_saved_args[7]; \ 
    asm volatile (     \ 
    "mov %%rsi,  (%%rax)\n\t"  \ 
    "mov %%rdi, 0x8(%%rax)\n\t"  \ 
    "mov %%rdx, 0x10(%%rax)\n\t"  \ 
    "mov %%rcx, 0x18(%%rax)\n\t"  \ 
    "mov %%r8, 0x20(%%rax)\n\t"  \ 
    "mov %%r9, 0x28(%%rax)\n\t"  \ 
    :        \ 
    :"a"(va_wrap_saved_args)   \ 
    ); 

#define VA_WRAP_CALL(func, ret)   \ 
    VA_WRAP_CALL_COMMON();     \ 
    va_wrap_saved_args[6] = (uintptr_t)va_wrap_stack; \ 
    asm volatile (      \ 
    "mov  (%%rax), %%rsi \n\t"   \ 
    "mov 0x8(%%rax), %%rdi \n\t"   \ 
    "mov 0x10(%%rax), %%rdx \n\t"   \ 
    "mov 0x18(%%rax), %%rcx \n\t"   \ 
    "mov 0x20(%%rax), %%r8 \n\t"   \ 
    "mov 0x28(%%rax), %%r9 \n\t"   \ 
    "mov   $0, %%rax \n\t"   \ 
    "call    *%%rbx \n\t"   \ 
    : "=a" (va_wrap_ret)     \ 
    : "b" (func), "a" (va_wrap_saved_args) \ 
    : "%rcx", "%rdx",      \ 
     "%rsi", "%rdi", "%r8", "%r9",  \ 
     "%r10", "%r11", "%r12", "%r14",  \ 
     "%r15"        \ 
    );          \ 
    ret = (typeof(ret)) va_wrap_ret; 

#else 

/* x86 stdcall */ 

static inline uintptr_t __attribute__((always_inline)) 
va_wrap_get_bp() 
{ 
    uintptr_t ret; 
    asm volatile ("mov %%ebp, %0":"=a"(ret)); 
    return ret; 
} 

#define VA_WRAP_PROLOGUE() \ 
    uintptr_t va_wrap_ret; 

#define VA_WRAP_CALL(func, ret)  \ 
    VA_WRAP_CALL_COMMON();    \ 
    asm volatile (     \ 
    "mov %2, %%esp \n\t"   \ 
    "call *%1  \n\t"   \ 
    : "=a"(va_wrap_ret)    \ 
    : "r" (func),      \ 
     "r"(va_wrap_stack)    \ 
    : "%ebx", "%ecx", "%edx" \ 
    );         \ 
    ret = (typeof(ret))va_wrap_ret; 
#endif 

#endif 

Cuối cùng bạn có thể bọc các cuộc gọi như thế này:

int __VA_WRAP_CALL_FUNC wrap_printf(char *str, ...) 
{ 
    VA_WRAP_PROLOGUE(); 
    int ret; 
    VA_WRAP_CALL(printf, ret); 
    printf("printf returned with %d \n", ret); 
    return ret; 
} 
0

Có chủ yếu là ba tùy chọn.

Một là không vượt qua nó, nhưng để sử dụng việc thực hiện variadic chức năng mục tiêu của bạn và không vượt qua trên elip. Một trong những khác là sử dụng một macro variadic. Tùy chọn thứ ba là tất cả những thứ tôi đang thiếu.

Tôi thường đi với tùy chọn một vì tôi cảm thấy như thế này là thực sự dễ dàng để xử lý. Tùy chọn hai có một nhược điểm vì có một số hạn chế để gọi các macro variadic.

Dưới đây là một số mã ví dụ:

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

#define Option_VariadicMacro(f, ...)\ 
    printf("printing using format: %s", f);\ 
    printf(f, __VA_ARGS__) 

int Option_ResolveVariadicAndPassOn(const char * f, ...) 
{ 
    int r; 
    va_list args; 

    printf("printing using format: %s", f); 
    va_start(args, f); 
    r = vprintf(f, args); 
    va_end(args); 
    return r; 
} 

void main() 
{ 
    const char * f = "%s %s %s\n"; 
    const char * a = "One"; 
    const char * b = "Two"; 
    const char * c = "Three"; 
    printf("---- Normal Print ----\n"); 
    printf(f, a, b, c); 
    printf("\n"); 
    printf("---- Option_VariadicMacro ----\n"); 
    Option_VariadicMacro(f, a, b, c); 
    printf("\n"); 
    printf("---- Option_ResolveVariadicAndPassOn ----\n"); 
    Option_ResolveVariadicAndPassOn(f, a, b, c); 
    printf("\n"); 
} 
Các vấn đề liên quan