C에서 함수 포인터의 typedef 이해


237

인수가있는 함수에 대한 포인터를 typedefs 가진 다른 사람들의 코드를 읽을 때 나는 항상 약간 혼란스러워했습니다. 나는 C로 작성된 수치 알고리즘을 이해하려고 시도하는 동안 그러한 정의를 극복하는 데 시간이 걸렸다는 것을 기억합니다. 따라서 함수에 대한 포인터 (유용한 것과하지 말아야 할 것)에 대한 유용한 typedef를 작성하는 방법에 대한 팁과 생각을 공유 할 수 있습니까? 감사!


1
몇 가지 예를 들어 줄 수 있습니까?
Artelius

2
함수 포인터의 매크로 대신 함수 포인터의 typedef를 의미하지 않습니까? 나는 전자를 보았지만 후자는 보지 않았다.
dave4420

답변:


297

signal()C 표준 의 기능을 고려하십시오 .

extern void (*signal(int, void(*)(int)))(int);

완벽하게 모호하게 명백합니다-정수와 인수로 정수를 가져 와서 아무것도 반환하지 않는 함수에 대한 포인터 인 두 개의 인수를 취하는 함수입니다. ( signal())는 정수를 인수로 사용하는 함수에 대한 포인터를 반환하고 반환합니다. 아무것도.

당신이 쓰는 경우 :

typedef void (*SignalHandler)(int signum);

대신 다음 signal()과 같이 선언 할 수 있습니다 .

extern  SignalHandler signal(int signum, SignalHandler handler);

이것은 같은 것을 의미하지만 일반적으로 읽기가 다소 쉬운 것으로 간주됩니다. 함수가 a int와 a를 가져와 a SignalHandler를 반환하는 것이 더 명확합니다 SignalHandler.

그러나 익숙해지기까지는 약간의 시간이 걸립니다. 당신이 할 수없는 한 가지는 SignalHandler typedef함수 정의에서를 사용하여 신호 처리기 함수를 작성하는 것 입니다.

나는 여전히 다음과 같이 함수 포인터를 호출하는 것을 선호하는 구식 학교입니다.

(*functionpointer)(arg1, arg2, ...);

현대 구문은 다음을 사용합니다.

functionpointer(arg1, arg2, ...);

그 이유가 무엇인지 알 수 있습니다 functionpointer. 라는 함수가 아니라 변수가 초기화 된 위치를 찾아야한다는 것을 선호합니다 .


샘은 다음과 같이 언급했다.

나는이 설명을 전에 보았다. 그리고 지금처럼, 내가 얻지 못한 것은 두 진술 사이의 연결이라고 생각합니다.

    extern void (*signal(int, void()(int)))(int);  /*and*/

    typedef void (*SignalHandler)(int signum);
    extern SignalHandler signal(int signum, SignalHandler handler);

또는 내가 묻고 싶은 것은 두 번째 버전을 생각해 내기 위해 사용할 수있는 기본 개념은 무엇입니까? "SignalHandler"와 첫 번째 typedef를 연결하는 기본은 무엇입니까? 여기에 설명해야 할 것은 typedef가 실제로 여기에서 수행하는 것입니다.

다시 해보자. 이 중 첫 번째는 C 표준에서 직접 해제되어 다시 입력하고 괄호가 올바른지 확인했습니다 (수정하기 전까지는 기억하기 힘든 쿠키입니다).

우선 typedef, 타입에 대한 별칭을 소개 한다는 것을 기억하십시오 . 따라서 별칭은 SignalHandler입니다. 유형은 다음과 같습니다.

정수를 인수로 사용하고 아무것도 리턴하지 않는 함수에 대한 포인터

'아무것도 돌려주지 않는다'부분은 철자이다 void; 정수인 인수는 (설명) 자명하다. 다음 표기법은 단순히 C가 지정된대로 인수를 가져와 주어진 유형을 반환하는 함수에 대한 포인터를 철자하는 방법입니다.

type (*function)(argtypes);

신호 처리기 유형을 만든 후이를 사용하여 변수 등을 선언 할 수 있습니다. 예를 들면 다음과 같습니다.

static void alarm_catcher(int signum)
{
    fprintf(stderr, "%s() called (%d)\n", __func__, signum);
}

static void signal_catcher(int signum)
{
    fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum);
    exit(1);
}

static struct Handlers
{
    int              signum;
    SignalHandler    handler;
} handler[] =
{
    { SIGALRM,   alarm_catcher  },
    { SIGINT,    signal_catcher },
    { SIGQUIT,   signal_catcher },
};

int main(void)
{
    size_t num_handlers = sizeof(handler) / sizeof(handler[0]);
    size_t i;

    for (i = 0; i < num_handlers; i++)
    {
        SignalHandler old_handler = signal(handler[i].signum, SIG_IGN);
        if (old_handler != SIG_IGN)
            old_handler = signal(handler[i].signum, handler[i].handler);
        assert(old_handler == SIG_IGN);
    }

    ...continue with ordinary processing...

    return(EXIT_SUCCESS);
}

신호 처리기에서 사용하지 않는 방법에 유의하십시오 .printf()

그렇다면 코드를 깔끔하게 컴파일하는 데 필요한 표준 헤더 4 개를 제외하고 여기서 무엇을 했습니까?

처음 두 함수는 단일 정수를 취하고 아무것도 반환하지 않는 함수입니다. 그들 중 하나는 실제로 덕분에 전혀 반환하지 않지만 exit(1);다른 하나는 메시지를 인쇄 한 후에 반환됩니다. C 표준을 사용하면 신호 처리기 내부에서 많은 작업을 수행 할 수 없습니다. POSIX 는 허용되는 것보다 조금 관대하지만 공식적으로 전화를 제재하지는 않습니다 fprintf(). 수신 된 신호 번호도 인쇄합니다. 에서 alarm_handler()함수 값은 항상있을 것입니다 SIGALRM그게에 대한 핸들러이지만,있는 유일한 신호로 signal_handler()얻을 수 있습니다 SIGINT또는 SIGQUIT동일한 기능이 모두 사용되기 때문에 신호 수있다.

그런 다음 각 요소가 신호 번호와 해당 신호에 설치할 핸들러를 식별하는 구조 배열을 만듭니다. 나는 3 가지 신호에 대해 걱정하기로했다. 나는 종종 SIGHUP, SIGPIPE그리고 SIGTERM또한 그것들이 정의되어 있는지 ( #ifdef조건부 컴파일) 에 대해 걱정 하지만, 그것은 단지 상황을 복잡하게 만듭니다. 나는 아마도 POSIX sigaction()대신 POSIX 를 사용 signal()하지만 다른 문제입니다. 우리가 시작한 것을 고수합시다.

main()함수는 설치할 핸들러 목록을 반복합니다. 각 핸들러에 대해 먼저 signal()프로세스가 현재 신호를 무시하고 있는지 확인하기 위해 호출 하고, 그렇게하는 동안 SIG_IGN신호가 무시되도록 핸들러로 설치합니다 . 신호가 이전에 무시되지 않았 으면 signal()이번에는 선호하는 신호 처리기를 설치하기 위해 다시 호출 합니다. (다른 값은 아마 인 SIG_DFL신호의 기본 신호 처리기,.)에 대한 첫 번째 호출 '신호가 ()'에 핸들러를 설정하기 때문에 SIG_IGN하고 signal()이전 오류 처리기의 값 반환 old애프터 if문이 있어야합니다 SIG_IGN- 따라서 주장을. (음, 그것은 할 수 있었다SIG_ERR 무언가가 극도로 잘못되면-어썰트 발사에서 그것에 대해 배울 것입니다.)

그런 다음 프로그램이 작업을 수행하고 정상적으로 종료됩니다.

함수의 이름은 적절한 유형의 함수에 대한 포인터로 간주 될 수 있습니다. 이니셜 라이저와 같이 함수 호출 괄호를 적용하지 않으면 함수 이름이 함수 포인터가됩니다. 이것이 pointertofunction(arg1, arg2)표기법을 통해 함수를 호출하는 것이 합리적인 이유이기도합니다 . 당신이 볼 때 alarm_handler(1), 당신은 고려할 수있는 alarm_handler기능에 대한 포인터이며, 따라서 alarm_handler(1)함수 포인터를 통해 함수의 호출이다.

따라서 지금까지 SignalHandler변수에 할당 할 올바른 유형의 값 이있는 한 변수가 비교적 간단하다는 것을 알았습니다.이 두 신호 처리기 함수가 제공하는 것입니다.

이제 우리는 다시 두 가지 선언이 signal()서로 어떻게 관련 되는지에 대한 질문으로 돌아갑니다 .

두 번째 선언을 검토하겠습니다.

 extern SignalHandler signal(int signum, SignalHandler handler);

함수 이름과 유형을 다음과 같이 변경 한 경우 :

 extern double function(int num1, double num2);

당신은 아무 문제받는 함수로이 해석이없는 것 int하고를 double인수로 리턴 double?하지만 어쩌면 당신은 하드와 같은 질문에 대해 신중해야 - (값을 당신을 것이라고이 문제가 있다면 아마 당신은 더 나은 '자백하지 않는 게 좋을을 이것이 문제라면 이것으로).

이제 대신 인의 double상기 signal()함수는 소요 SignalHandler번째 인수로하고, 그 결과로 하나의 반환한다.

이를 처리 할 수있는 메커니즘은 다음과 같습니다.

extern void (*signal(int signum, void(*handler)(int signum)))(int signum);

설명하기 까다로워서 아마 망칠 것입니다. 이번에는 매개 변수 이름을 지정했지만 이름은 중요하지 않습니다.

일반적으로 C에서 선언 메커니즘은 다음과 같이 작성하면됩니다.

type var;

당신이 쓸 때 var그것은 주어진 값을 나타냅니다 type. 예를 들면 다음과 같습니다.

int     i;            // i is an int
int    *ip;           // *ip is an int, so ip is a pointer to an integer
int     abs(int val); // abs(-1) is an int, so abs is a (pointer to a)
                      // function returning an int and taking an int argument

표준에서는 typedef문법에서 스토리지 클래스로 취급되며 스토리지 클래스와 유사 static하며 extern스토리지 클래스입니다.

typedef void (*SignalHandler)(int signum);

즉, 유형의 변수 SignalHandler(예 : alarm_handler)가 다음과 같이 호출되면

(*alarm_handler)(-1);

결과는 type void- 결과 가 없습니다. 그리고 with 인수 (*alarm_handler)(-1);의 호출입니다 .alarm_handler()-1

우리가 선언하면 :

extern SignalHandler alt_signal(void);

그것은 다음을 의미합니다.

(*alt_signal)();

void 값을 나타냅니다. 따라서:

extern void (*alt_signal(void))(int signum);

동일합니다. 이제는 signal()a를 반환 할뿐만 아니라 SignalHandlerint와 a SignalHandler를 모두 인수로 받아들이 기 때문에 더 복잡합니다 .

extern void (*signal(int signum, SignalHandler handler))(int signum);

extern void (*signal(int signum, void (*handler)(int signum)))(int signum);

그래도 여전히 혼란 스러울 경우, 어떻게 도와야할지 모르겠습니다. 여전히 몇 가지 수준에 미스터리하지만, 그것이 어떻게 작동하는지에 익숙해 져서 25 년 동안 더 고집한다면 그래서, 그것은 당신에게 두 번째 본성이 될 것입니다 (그리고 영리하다면 조금 더 빠를 수도 있습니다).


3
나는이 설명을 전에 보았다. 그리고 지금처럼, 내가 얻지 못한 것은 두 문장 사이의 연결이라고 생각합니다 : extern void ( signal (int, void ( ) (int))) (int); / * and * / typedef 무효 (* SignalHandler) (int signum); extern SignalHandler signal (int signum, SignalHandler 핸들러); 또는 내가 묻고 싶은 것은 두 번째 버전을 생각해 내기 위해 사용할 수있는 기본 개념은 무엇입니까? "SignalHandler"와 첫 번째 typedef를 연결하는 기본은 무엇입니까? 여기에 설명해야 할 것은 typedef가 실제로 여기에서 수행하는 것입니다. Thx

6
큰 답은이 스레드로 돌아와서 기쁘다. 나는 모든 것을 이해한다고 생각하지 않지만 언젠가는 할 것입니다. 이것이 내가 그렇게 좋아하는 이유입니다. 감사합니다.
toto

2
니트를 선택하기 만하면됩니다 : 시그널 핸들러 안에서 printf ()와 friends를 호출하는 것은 안전하지 않습니다. printf ()는 재진입이 아닙니다 (기본적으로 재진입이 아닌 malloc ()를 호출 할 수 있기 때문에)
wildplasser

4
extern void (*signal(int, void(*)(int)))(int);수단은 signal(int, void(*)(int))함수에 대한 함수 포인터를 반환합니다 void f(int). 함수 포인터를 반환 값으로 지정 하려면 구문이 복잡해집니다. 반환 값 유형은 왼쪽 에, 인수 목록은 오른쪽 에 배치 해야하며 정의하는 중간에 있어야합니다. 그리고이 경우 signal()함수 자체는 함수 포인터를 매개 변수로 사용하므로 훨씬 복잡합니다. 좋은 소식은,이 글을 읽을 수 있다면 힘은 이미 당신과 함께 있다는 것입니다. :).
smwikipedia

1
&함수 이름 앞에서 사용 하는 것에 대한 구식은 무엇입니까 ? 완전히 불필요합니다. 무의미합니다. 그리고 확실히 "오래된 학교". 구식은 단순하고 간단한 기능 명을 사용합니다.
Jonathan Leffler

80

함수 포인터는 다른 포인터와 비슷하지만 데이터 주소 (힙 또는 스택) 대신 함수 주소를 가리 킵니다. 다른 포인터와 마찬가지로 올바르게 입력해야합니다. 함수는 반환 값과 허용되는 매개 변수 유형으로 정의됩니다. 따라서 함수를 완전히 설명하려면 리턴 값을 포함시켜야하며 각 매개 변수의 유형이 승인됩니다. 그러한 정의를 typedef 할 때, 그 정의를 사용하여 포인터를보다 쉽게 ​​만들고 참조 할 수있게하는 '친숙한 이름'을 부여합니다.

예를 들어 함수가 있다고 가정하십시오.

float doMultiplication (float num1, float num2 ) {
    return num1 * num2; }

다음 typedef :

typedef float(*pt2Func)(float, float);

doMulitplication기능 을 가리키는 데 사용할 수 있습니다 . float를 반환하고 각각 float 유형의 두 매개 변수를 사용하는 함수에 대한 포인터를 정의하는 것입니다. 이 정의는 이름이 친숙 pt2Func합니다. 참고 pt2Funcfloat를 반환하고이 수레에 걸리는 모든 기능을 가리킬 수 있습니다.

따라서 다음과 같이 doMultiplication 함수를 가리키는 포인터를 만들 수 있습니다.

pt2Func *myFnPtr = &doMultiplication;

다음과 같이이 포인터를 사용하여 함수를 호출 할 수 있습니다.

float result = (*myFnPtr)(2.0, 5.1);

이것은 좋은 독서를합니다 : http://www.newty.de/fpt/index.html


psychotik, 감사합니다! 도움이되었습니다. 함수 포인터 웹 페이지에 대한 링크가 정말 도움이됩니다. 지금 읽고 있습니다.

... 그러나, newty.de 링크가 모두 :(에서 형식 정의에 대해 이야기 나타나지 않는 링크는 매우 중요하지만, 그래서 비록 형식 정의에 대한이 글의 반응은 매우 중요하다!

11
이미 포인터 pt2Func myFnPtr = &doMultiplication;pt2Func *myFnPtr = &doMultiplication;것처럼 대신 할 수 있습니다 myFnPtr.
Tamilselvan

1
pt2Func 선언 * myFnPtr = & doMultiplication; pt2Func 대신 myFnPtr = & doMultiplication; 경고를 던졌습니다.
AlphaGoku

2
@Tamilselvan이 맞습니다. myFunPtr이미 함수 포인터이므로pt2Func myFnPtr = &doMultiplication;
Dustin Biser

35

함수 포인터의 typedef를 이해하는 매우 쉬운 방법 :

int add(int a, int b)
{
    return (a+b);
}

typedef int (*add_integer)(int, int); //declaration of function pointer

int main()
{
    add_integer addition = add; //typedef assigns a new variable i.e. "addition" to original function "add"
    int c = addition(11, 11);   //calling function via new variable
    printf("%d",c);
    return 0;
}

32

cdecl함수 포인터 선언과 같은 이상한 구문을 해독하는 데 유용한 도구입니다. 그것들을 사용하여 생성 할 수도 있습니다.

복잡한 선언을 향후 유지 관리를 위해 쉽게 해석 할 수있는 팁 (자신 또는 다른 사람에 의해)을 결정하는 한, typedef작은 청크를 만들고 그 작은 조각을 더 크고 복잡한 표현을위한 빌딩 블록으로 사용하는 것이 좋습니다 . 예를 들면 다음과 같습니다.

typedef int (*FUNC_TYPE_1)(void);
typedef double (*FUNC_TYPE_2)(void);
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);

오히려

typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void);

cdecl 이 물건으로 당신을 도울 수 있습니다 :

cdecl> explain int (*FUNC_TYPE_1)(void)
declare FUNC_TYPE_1 as pointer to function (void) returning int
cdecl> explain double (*FUNC_TYPE_2)(void)
declare FUNC_TYPE_2 as pointer to function (void) returning double
cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int
int (*(*FUNC_TYPE_3)(double (*)(void )))(void )

그리고 (실제로) 위의 미친 혼란을 어떻게 정확하게 생성했는지입니다.


2
안녕하세요 칼, 그것은 매우 통찰력있는 예와 설명이었습니다. 또한 cdecl을 사용해 주셔서 감사합니다. 매우 감사.

Windows 용 cdecl이 있습니까?
Jack

@Jack, 당신이 그것을 만들 수 있다고 확신합니다.
Carl Norum

2
온라인과 동일한 기능을 제공하는 cdecl.org 도 있습니다. Windows 개발자에게 유용합니다.
zaknotzach

12
int add(int a, int b)
{
  return (a+b);
}
int minus(int a, int b)
{
  return (a-b);
}

typedef int (*math_func)(int, int); //declaration of function pointer

int main()
{
  math_func addition = add;  //typedef assigns a new variable i.e. "addition" to original function "add"
  math_func substract = minus; //typedef assigns a new variable i.e. "substract" to original function "minus"

  int c = addition(11, 11);   //calling function via new variable
  printf("%d\n",c);
  c = substract(11, 5);   //calling function via new variable
  printf("%d",c);
  return 0;
}

이것의 출력은 다음과 같습니다.

22

6

함수를 선언하는 데 동일한 math_func 정의자가 사용되었습니다.

extern struct에 typedef와 동일한 접근 방식을 사용할 수 있습니다 (다른 파일에서 sturuct 사용).


5

더 복잡한 유형, 즉 함수 포인터를 정의하려면 typedef를 사용하십시오.

C에서 상태 머신을 정의하는 예를 들어 보겠습니다.

    typedef  int (*action_handler_t)(void *ctx, void *data);

이제 우리는 두 개의 포인터를 가져 와서 int를 반환하는 action_handler라는 유형을 정의했습니다.

상태 머신을 정의하십시오

    typedef struct
    {
      state_t curr_state;   /* Enum for the Current state */
      event_t event;  /* Enum for the event */
      state_t next_state;   /* Enum for the next state */
      action_handler_t event_handler; /* Function-pointer to the action */

     }state_element;

액션에 대한 함수 포인터는 단순한 타입처럼 보이고 typedef는 주로이 목적을 수행합니다.

이제 모든 이벤트 핸들러가 action_handler에 의해 정의 된 유형을 준수해야합니다.

    int handle_event_a(void *fsm_ctx, void *in_msg );

    int handle_event_b(void *fsm_ctx, void *in_msg );

참고 문헌 :

Linden의 전문가 C 프로그래밍


4

이것은 내가 연습으로 쓴 함수 포인터와 함수 포인터 배열의 가장 간단한 예입니다.

    typedef double (*pf)(double x);  /*this defines a type pf */

    double f1(double x) { return(x+x);}
    double f2(double x) { return(x*x);}

    pf pa[] = {f1, f2};


    main()
    {
        pf p;

        p = pa[0];
        printf("%f\n", p(3.0));
        p = pa[1];
        printf("%f\n", p(3.0));
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.