[c] C에서 가변 함수의 호출을 전달하십시오.

C에서는 가변 함수의 호출을 전달할 수 있습니까? 에서처럼

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

위와 같은 방법으로 호출을 전달하는 것이 반드시 필요한 것은 아닙니다 (다른 방법으로 호출을 기록하거나 vfprintf를 사용할 수 있기 때문에). 작업중 인 코드베이스는 래퍼가 실제 작업을 수행해야하며 vfprintf와 유사한 도우미 함수가 없습니다 (추가 할 수 없습니다).

[업데이트 : 지금까지 제공된 답변에 따라 약간의 혼동이있는 것 같습니다. 다른 방법으로 질문을 표현하려면 : 일반적으로 함수의 정의를 수정하지 않고 임의의 가변 함수를 래핑 할 수 있습니다 .]



답변

에 당신이 함수와 유사한이없는 경우 vfprintf그이 걸리는 va_list대신 가변 인자의, 당신은 그것을 할 수 없습니다 . 보다http://c-faq.com/varargs/handoff.html .

예:

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


답변

그러나 직접적이지는 않지만 가변성 함수가 varargs스타일 대체 함수 와 쌍을 이루는 것이 일반적입니다 (그리고 표준 라이브러리에서 거의 보편적으로 찾을 수 있습니다) . 예 printf/vprintf

v … 함수는 va_list 매개 변수를 사용하며, 구현은 종종 컴파일러 특정 ‘매크로 매직’으로 수행되지만 다음과 같이 variadic 함수에서 v … 스타일 함수를 호출하면 작동합니다.

#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;
}

이것은 당신이 찾고있는 효과를 줄 것입니다.

variadic 라이브러리 함수 작성을 고려중인 경우 va_list 스타일 컴패니언을 라이브러리의 일부로 사용 가능하게하는 것도 고려해야합니다. 질문에서 알 수 있듯이 사용자에게 유용 할 수 있습니다.


답변

C99는 가변 인수를 갖는 매크로를 지원 합니다 . 컴파일러에 따라 원하는 것을 수행하는 매크로를 선언 할 수 있습니다.

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

그러나 일반적으로 가장 좋은 해결책은 랩하려는 함수 의 va_list 형식 을 사용하는 것입니다.


답변

거의 다음에서 사용할 수있는 기능을 사용합니다 <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;
}

vprintfplain 대신 버전 을 사용해야합니다 printf. 이 상황에서는을 사용하지 않고 가변 함수를 직접 호출 할 수있는 방법이 없습니다 va_list.


답변

이러한 호출을 좋은 방법으로 전달하는 것은 실제로 불가능하므로 원래 스택 프레임의 복사본으로 새 스택 프레임을 설정하여이 문제를 해결했습니다. 그러나 이것은 매우 이식성이 없으며 모든 종류의 가정을 만듭니다. 코드가 프레임 포인터와 ‘표준’호출 규칙을 사용 을합니다.

이 헤더 파일은 x86_64 및 i386 (GCC)에 대한 가변 기능을 래핑 할 수 있습니다. 부동 소수점 인수에는 작동하지 않지만이를 지원하기 위해 확장하려면 간단해야합니다.

#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

결국 다음과 같이 통화를 감쌀 수 있습니다.

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;
}


답변

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;
}


답변

원시 스택 요소를 검색 할 수있는 유일한 위치는에 있기 때문에 이러한 함수 호출을 전달할 방법이 없습니다 my_print(). 이와 같이 호출을 래핑하는 일반적인 방법은 인수를 다양한 varargs구조체 로 변환하는 함수 와 실제로 해당 구조체에서 작동 하는 함수를 갖는 것 입니다. 이러한 이중 기능 모델을 사용하면 (예를 들어) 포장 할 수 printf()있는 구조체를 초기화하여 my_printf()va_start()다음에 전달할 vfprintf().