가변 개수의 인수 전달


333

가변 수의 인수를 취하는 C 함수가 있다고 가정 해보십시오. 첫 번째 함수에있는 모든 인수를 전달하여 내부에서 가변 수의 인수를 기대하는 다른 함수를 어떻게 호출 할 수 있습니까?

예:

void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }

4
fmt를 format_string ()과 fprintf () 모두에 전달한다는 점에서 예제가 조금 이상하게 보입니다. format_string ()은 어떻게 든 새로운 문자열을 반환해야합니까?
Kristopher Johnson

2
예는 이해가되지 않습니다. 코드의 개요를 보여주기 위해서였습니다.
Vicent Marti

162
"구글해야합니다": 동의하지 않습니다. Google은 많은 소음을냅니다 (불분명하고 종종 혼란스러운 정보). stackoverflow에서 좋은 (투표, 허용되는 답변)을 얻는 것이 실제로 도움이됩니다!
Ansgar

71
무게를 겨우 : 구글 에서이 질문에 왔으며 스택 오버플로 였기 때문에 대답이 유용 할 것이라고 확신했습니다. 그러니 물어보세요!
tenpn February

32
@Ilya : Google 외부에 글을 쓴 사람이 없으면 Google에서 검색 할 정보가 없습니다.
Erik Kaplun

답변:


211

타원을 전달하려면 타원을 va_list로 변환하고 두 번째 함수에서 해당 va_list를 사용해야합니다. 구체적으로 특별히;

void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...) 
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout, "%s",formatted_string);
}

3
코드는 질문에서 가져 왔으며 실제로는 기능이 아닌 타원을 변환하는 방법을 보여줍니다. 당신이 보면 그것은 format_string그것을 확실히 중 하나를해서는 안 FMT,에 현장 수정을해야하는 것처럼 거의 유용하지 않습니다 중 하나를합니다. 옵션에는 format_string을 완전히 제거하고 vfprintf를 사용하는 것이 포함되지만 format_string이 실제로 수행하는 작업을 가정하거나 format_string이 다른 문자열을 리턴하도록합니다. 후자를 보여주기 위해 답을 편집하겠습니다.
SmacL

1
형식 문자열이 printf와 동일한 형식 문자열 명령을 사용하는 경우 gcc 및 clang과 같은 일부 컴파일러에서 형식 문자열이 전달 된 실제 인수와 호환되지 않는 경우 경고를 표시 할 수 있습니다. GCC 함수 속성 ' 자세한 내용은 gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html 을 참조하십시오 .
Doug Richardson

1
args를 두 번 연속으로 전달하면 작동하지 않는 것 같습니다.
fotanus 2018 년

2
@fotanus :와 함께 함수를 호출하고 호출 된 함수가 전혀 argptr사용 argptr하지 않는 경우, 가장 안전한 방법은 호출 va_end()한 다음 다시 시작 va_start(argptr, fmt);하여 다시 초기화하는 것입니다. 또는 va_copy()시스템에서 지원 하는 경우 사용할 수 있습니다 (C99 및 C11에서 요구하지만 C89 / 90에서는 지원하지 않음).
Jonathan Leffler 2016 년

1
@ ThomasPadron-McCarthy 님의 코멘트가 최신이 아니며 마지막 fprintf에 문제가 없습니다.
Frederick

59

당신이 장난스럽고 휴대 할 수없는 트릭에 들어가고 싶지 않다면, 얼마나 많은 인수를 전달했는지 알지 못하고 printf를 호출하는 방법은 없습니다.

일반적으로 사용되는 솔루션은 그래서 항상 가변 인자 함수의 대체 양식을 제공하는 printfvprintf소요되는 va_list의 장소에서 .... ...버전은 주변 단지 래퍼 va_list버전.


참고 vsyslog입니다 하지 POSIX의 준수.
patryk.beza

53

다양한 기능위험 할 수 있습니다 . 더 안전한 트릭이 있습니다.

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});

11
이 속임수가 더 낫습니다. #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III

5
@ RichardJ.RossIII 나는 당신이 당신의 코멘트를 확장하기를 바랍니다. 이처럼 읽을 수는 없습니다. 코드 뒤에 아이디어를 만들 수 없으며 실제로 매우 흥미롭고 유용하게 보입니다.
penelope

5
@ArtOfWarfare 나는 나쁜 해킹에 동의한다는 것을 확신하지 못한다. Rose는 훌륭한 솔루션을 가지고 있지만 func ((type []) {val1, val2, 0}); #define func_short_cut (...) func ((type []) { VA_ARGS }); 그런 다음 func_short_cut (1, 2, 3, 4, 0)을 호출하면됩니다. 이것은 Rose의 깔끔한 트릭의 추가 이점이있는 일반적인 variadic 함수와 동일한 구문을 제공합니다 ... 여기에 문제가 있습니까?
chrispepper1989

9
인수로 0을 전달하려면 어떻게해야합니까?
Julian Gold

1
이를 위해서는 사용자가 종료 0으로 전화해야한다는 것을 기억해야합니다. 어떻게 더 안전합니까?
cp.engr

29

멋진 C ++ 0x에서는 가변 템플릿을 사용할 수 있습니다.

template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}

Variadic 템플릿을 Visual Studio에서 여전히 사용할 수 없다는 것을 잊지 마십시오. 물론 이것은 걱정할 필요가 없습니다!
Tom Swirly

1
Visual Studio를 사용하는 경우 2012 년 11 월 CTP를 사용하여 variadic 템플릿을 Visual Studio 2012에 추가 할 수 있습니다. Visual Studio 2013을 사용하는 경우 가변 템플릿이 있습니다.
user2023370

7

함수 호출에 인라인 어셈블리를 사용할 수 있습니다. (이 코드에서는 인수가 문자라고 가정합니다).

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }

4
이식성이 없으며 컴파일러에 따라 다르며 컴파일러 최적화를 방지합니다. 매우 나쁜 해결책.
Geoffroy

4
삭제하지 않은 새로운 기능.
user7116

8
적어도 이것은 실제로 질문을 재정의하지 않고 질문에 대답합니다.
lama12345 5

6

매크로도 시도해 볼 수 있습니다.

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)

int main()
{
    int x=10;
    DEBUG_PRINT(DBG, "i am x %d\n", x);
    return 0;
}

6

포맷터를 로컬 버퍼에 먼저 저장하여 전달할 수는 있지만 스택이 필요하고 때로는 문제가 될 수 있습니다. 나는 다음을 시도했지만 정상적으로 작동하는 것 같습니다.

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

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("\nMDL: %d, TYPE: %s", mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main() 
{
    int x = 3, y = 6;
    showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
    showLog(1, "ERR");
}

도움이 되었기를 바랍니다.


2

Ross의 솔루션은 약간 정리되었습니다. 모든 인수가 포인터 인 경우에만 작동합니다. 또한 언어 구현 __VA_ARGS__은 비어있는 경우 이전 쉼표 생략을 지원해야합니다 (Visual Studio C ++ 및 GCC 모두 수행).

// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}

0

작성한 일반적인 가변 기능이 있다고 가정 해 봅시다. variadic 인수 앞에 적어도 하나의 인수가 필요 ...하므로 항상 추가 인수를 작성해야합니다.

아니면 당신은?

variadic 함수를 매크로로 래핑하면 선행 인수가 필요하지 않습니다. 이 예제를 고려하십시오.

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

매번 초기 인수를 지정할 필요가 없기 때문에 훨씬 편리합니다.


-5

이것이 모든 컴파일러에서 작동하는지 확실하지 않지만 지금까지 작동했습니다.

void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

원하는 경우 inner_func ()에 ...를 추가 할 수 있지만 필요하지는 않습니다. va_start는 주어진 변수의 주소를 시작점으로 사용하기 때문에 작동합니다. 이 경우 func ()의 변수에 대한 참조를 제공합니다. 따라서 해당 주소를 사용하고 스택에서 그 이후의 변수를 읽습니다. inner_func () 함수는 func ()의 스택 주소에서 읽습니다. 따라서 두 함수가 동일한 스택 세그먼트를 사용하는 경우에만 작동합니다.

va_start 및 va_arg 매크로는 var를 시작점으로 지정하면 일반적으로 작동합니다. 따라서 원하는 경우 다른 함수에 포인터를 전달하고 사용할 수도 있습니다. 자신 만의 매크로를 쉽게 만들 수 있습니다. 모든 매크로는 타입 캐스트 메모리 주소입니다. 그러나 모든 컴파일러와 호출 규칙에서 작동하도록 만드는 것은 성가신 일입니다. 따라서 일반적으로 컴파일러와 함께 제공되는 것을 사용하는 것이 더 쉽습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.