9

가변 매개 변수 목록을 사용하는 함수에 대한 사례의 하위 집합을 처리 할 함수에 대한 함수 포인터를 만들려고합니다. 유스 케이스에서는 ... 함수를 특정 매개 변수 목록을 사용하는 함수로 캐스팅하므로 va_list 및 친구들을 처리하지 않고도 다양한 매개 변수를 처리 할 수 ​​있습니다.C에서 가변 인수를 사용하는 함수 포인터에 가변 인수 포인터를 캐스팅하는 것이 안전합니까?

다음 예제 코드에서는 변수 매개 변수가있는 함수를 하드 코드 된 매개 변수 목록이있는 함수로 캐스팅합니다 (반대의 경우도 마찬가지 임). 이 작품 (또는 일하는) 작동하지만, 사용하는 호출 규칙 때문에 우연의 일치인지 모르겠다. (두 개의 다른 x86_64 기반 플랫폼에서 시도해 보았습니다.)

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

void function1(char* s, ...) 
{ 
    va_list ap; 
    int tmp; 

    va_start(ap, s); 
    tmp = va_arg(ap, int); 
    printf(s, tmp); 
    va_end(ap); 
} 

void function2(char* s, int d) 
{ 
    printf(s, d); 
} 

typedef void (*functionX_t)(char*, int); 
typedef void (*functionY_t)(char*, ...); 

int main(int argc, char* argv[]) 
{ 
    int x = 42; 

    /* swap! */ 
    functionX_t functionX = (functionX_t) function1; 
    functionY_t functionY = (functionY_t) function2; 

    function1("%d\n", x); 
    function2("%d\n", x); 
    functionX("%d\n", x); 
    functionY("%d\n", x); 

    return 0; 
} 

이 정의되지 않은 동작입니까? 그렇다면 누군가가이 방법이 작동하지 않는 플랫폼의 예제를 제공하거나,보다 복잡한 유스 케이스가 주어지면 실패 할 것이라고 예를 들어 조정할 수 있습니까?


편집 : 그것은 여전히 ​​작동

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

struct crazy 
{ 
    float f; 
    double lf; 
    int d; 
    unsigned int ua[2]; 
    char* s; 
}; 

void function1(char* s, ...) 
{ 
    va_list ap; 
    struct crazy c; 

    va_start(ap, s); 
    c = va_arg(ap, struct crazy); 
    printf(s, c.s, c.f, c.lf, c.d, c.ua[0], c.ua[1]); 
    va_end(ap); 
} 

void function2(char* s, struct crazy c) 
{ 
    printf(s, c.s, c.f, c.lf, c.d, c.ua[0], c.ua[1]); 
} 

typedef void (*functionX_t)(char*, struct crazy); 
typedef void (*functionY_t)(char*, ...); 

int main(int argc, char* argv[]) 
{ 
    struct crazy c = 
    { 
     .f = 3.14, 
     .lf = 3.1415, 
     .d = -42, 
     .ua = { 0, 42 }, 
     .s = "this is crazy" 
    }; 


    /* swap! */ 
    functionX_t functionX = (functionX_t) function1; 
    functionY_t functionY = (functionY_t) function2; 

    function1("%s %f %lf %d %u %u\n", c); 
    function2("%s %f %lf %d %u %u\n", c); 
    functionX("%s %f %lf %d %u %u\n", c); 
    functionY("%s %f %lf %d %u %u\n", c); 

    return 0; 
} 

:이 코드는 더 복잡 인수를 중단 할 것이라는 암시를 해결하기 위해, 내 예를 연장했다. 누군가가 실패 할 때의 구체적인 예를 지적 할 수 있습니까?

$ gcc -Wall -g -o varargs -O9 varargs.c 
$ ./varargs 
this is crazy 3.140000 3.141500 -42 0 42 
this is crazy 3.140000 3.141500 -42 0 42 
this is crazy 3.140000 3.141500 -42 0 42 
this is crazy 3.140000 3.141500 -42 0 42 
+2

는 "이 정의되지 않은 동작이 있습니까?"(GCC) 대신 0.5의 제로 예. 호환되지 않는 함수 포인터를 통해 함수를 호출하면 정의되지 않은 동작이 항상 발생합니다. –

+0

가변 매개 변수 호출이 사용되는지 여부에 관계없이 매개 변수를 스택에 푸시하는 것이 일관된 방식으로 수행됩니다 (적어도 예제를 통해). 따라서 실제로 사양에 대해 "정의되지 않았"습니까 (또는 언급되지 않았습니까?), 동작은 실제로 일관성이 있지 않습니까? – mpontillo

+3

@Mike : 가변 인수가 아닌 매개 변수는 가능할 때마다 CPU 레지스터를 통해 전달됩니다 (x86 이상). 그들은 스택에 전혀 밀어 넣지 않습니다. 이것이 효과가없는 이유 중 하나입니다. – AnT

답변

8

캐스팅 다른 기능 포인터 유형에 대한 포인터는 완벽하게 안전합니다. 그러나 언어가 보장하는 유일한 점은 나중에 다시 원래 유형으로 캐스팅하고 원래 포인터 값을 얻을 수 있다는 것입니다.

을 호출하면 호환되지 않는 함수 포인터 유형으로 강제 변환 된 포인터를 통해 함수가 정의되지 않은 동작을하게됩니다. Variadic인지 아닌지에 관계없이 모든 호환되지 않는 함수 포인터 유형에 적용됩니다.

게시 한 코드가 캐스트의 시점이 아니라 통화 시점에서 정의되지 않은 동작을 생성합니다. 매개 변수 전달 규칙 (낮은 수준의 모두 언어 수준)이 크게 다르기 때문에, "이 실패 할 경우"예를 추격하려고


무의미한 노력이지만, 어쨌든 쉽게해야합니다.예를 들어, 실제로는 일반적으로하지 않습니다 "작업"아래의 코드는

void foo(const char *s, float f) { printf(s, f); } 

int main() { 
    typedef void (*T)(const char *s, ...); 
    T p = (T) foo; 
    float f = 0.5; 
    p("%f\n", f); 
} 

인쇄는

+0

질문이이 함수를 명확하게 호출하고 있습니다. 이 컨텍스트에서 포인터를 캐스팅하면 두렵습니다. – Mastermnd

+0

좋은 예, 감사합니다! 귀하의 예제에서는 OS X에서는 "0.500000", Linux (x64)에서는 "0.000000"을 출력합니다. – mpontillo

4

예, 이것은 정의되지 않은 동작입니다.

포인터가 현재 컴파일러, 플랫폼 및 매개 변수 유형에 맞춰져 있기 때문에 작동하는 것입니다. 복식과 다른 유형으로 이것을 시도하면 이상한 행동을 재현 할 수있을 것입니다.

그렇지 않더라도 매우 위험한 코드입니다.

나는 varargs에 짜증이 난다고 가정합니다. 공용체 또는 구조체에 공통 매개 변수 집합을 정의한 다음이를 전달하는 것이 좋습니다.

+1

사실 짜증이 나지 않습니다. 그것은 같은 기능의'v' 버전이없는 API를 가지고있는 경우입니다. 예를 들어,'printf'와 같은 것이라면 항상'vprintf'가 있지만 이것들은'va_list'를 취하는 것과 동등한 함수를 가지고 있지 않습니다. 그 API를 소유하고 있지 않기 때문에 구조체를 정의하고 전달할 수는 없습니다. – mpontillo

+2

불행히도이 경우 안전한 경로는 각 호출에 대한 래퍼를 만들고 varargs를 구문 분석하는 것입니다. – Mastermnd

관련 문제