GSI

'함수 포인터'에 해당되는 글 1건

  1. 2007.09.18 함수 포인터 타입 - (펌)

함수 포인터 타입 - (펌)

STL 2007. 9. 18. 14:48 |

주소 : http://www.winapi.co.kr/clec/cpp2/15-2-2.htm

함수 포인터 타입도 일종의 고유한 타입이다. 따라서 원형이 다른 함수 포인터끼리는 곧바로 대입할 수 없으며 함수의 인수로도 넘길 수 없다. 정수형 포인터 변수(int *)에 실수형 포인터 변수(double *)의 값을 대입할 수 없듯이 말이다. 다음 코드를 보자.

 

int (*pf1)(char *);

void (*pf2)(double);

pf1=pf2;                  // 타입이 다르므로 에러

 

pf1은 문자형 포인터를 인수로 취하고 정수형을 리턴하는 함수를 가리키는 함수 포인터이며 pf2는 실수를 인수로 취하고 리턴값이 없는 함수를 가리키는 함수 포인터이다. 두 변수가 가리킬 수 있는 함수의 원형이 다르기 때문에 pf2가 가리키는 번지를 pf1에 곧바로 대입할 수 없다. 만약 이것이 가능하다면 pf1로 함수를 호출할 때 컴파일러는 char *형의 인수를 찾지만 pf1이 가리키는 함수는 double형의 인수를 받아들이므로 불일치가 발생하며 함수가 제대로 호출되지 않을 것이다.

함수 포인터가 가리킬 수 있는 원형과 같지 않은 함수의 번지를 대입하는 것도 똑같은 이유로 에러로 처리된다. 다음 코드에서 pf3은 문자형 포인터와 실수를 인수로 취하고 리턴값이 없는 함수를 가리키도록 선언했는데 func 함수는 pf3의 원형과 다르므로 pf3에 func 함수의 번지를 대입할 수 없다.

 

int func(int a);

 

void (*pf3)(char *, double);

pf3=func;                         // 에러

 

그러나 타입이 다른 함수 포인터끼리라도 강제로 대입할 수는 있는데 이것이 일단은 가능해야 한다. void 포인터에 저장된 함수의 번지를 대입받는다거나 자료 구조 설계시에 미리 알 수 없는 함수에 대한 포인터를 다루고자 할 때이다. 이럴 때 사용하는 것이 바로 캐스트 연산자이며 여러 가지 이유로 강제 캐스팅의 필요성은 누구나 인정하고 있다. int *pi와 double *pd가 있을 때 pi=pd 대입은 금지되지만 pi=(int *)pd는 가능한 것처럼 함수 포인터도 타입에 맞게 캐스팅하면 강제로 대입할 수 있다.

데이터 포인터에서와 마찬가지로 함수 포인터에도 캐스트 연산자를 쓸 수 있는데 문제는 함수 포인터의 캐스트 연산자가 모양이 생소해서 조금 어렵다는 것이다. 함수 포인터는 타입 자체가 길기 때문에 캐스트 연산자의 모양도 상당히 복잡해 보인다. 다음 코드는 pf2를 pf1에 강제로 대입하기 위해 캐스트 연산자를 사용한 것이다.

 

int (*pf1)(char *);

void (*pf2)(double);

pf1=(int (*)(char *))pf2;

 

이 식에서 (int (*)(char *))가 캐스트 연산자이다. 함수 포인터형의 캐스트 연산자를 만드는 방법은 함수 포인터 선언식에서 변수명을 빼고 전체를 괄호로 한 번 더 싸주면 된다. 일종의 공식이므로 외워 두거나 아니면 필요할 때마다 이 공식을 찾아보기 바란다.

pf1=(int (*)(char *))pf2 대입문은 pf2가 가리키는 번지를 문자형 포인터를 인수로 취하고 정수를 리턴하는 함수 포인터 타입으로 잠시 바꾼 후 pf1에 대입한다. pf3에 func 함수의 번지를 강제로 대입할 때도 마찬가지로 캐스트 연산자를 사용할 수 있다. 물론 이렇게 강제로 대입했을 때의 부작용에 대해서는 스스로 책임져야 한다.

 

pf3=(void (*)(char *,double))func;

 

캐스트 연산자가 길어 보여서 그렇지 원리를 알고 나면 별로 어렵지 않다. 다음은 조금 이론적인 얘기가 되겠지만 함수 포인터의 배열이나 포인터를 선언하는 형식에 대해 알아보자. T형에 대해 T형 배열과 T형 포인터를 항상 선언할 수 있으므로 함수 포인터에 대해서도 배열과 포인터를 선언할 수 있다. func 타입의 함수를 가리킬 수 있는 함수 포인터를 요소로 가지는 크기 5의 arpf 배열은 다음과 같이 선언한다.

 

int (*arpf[5])(int);

 

함수 포인터 배열을 선언할 때는 변수명 다음에 첨자 크기를 밝혀 주면 된다. 잘못 생각하면 int (*arpf)(int)[5];가 맞을 것 같기도 한데 첨자 크기는 반드시 변수명 다음에 바로 써야 한다. 이 선언에 의해 int (*)(int)형의 함수의 번지를 가리킬 수 있는 함수 포인터 arpf[0] ~ arpf[4]까지 4개의 변수가 생성되며 각 변수는 int func(int)와 같은 원형의 함수를 가리키는 번지를 가질 수 있다. 동일한 타입의 변수들을 배열에 모아 두면 루프를 돌면서 함수들을 순서대로 호출한다거나 하는 처리도 가능해진다.

다음은 함수 포인터의 포인터를 선언해 보자. func 타입의 함수를 가리키는 함수 포인터를 가리키는 포인터 ppf는 다음과 같이 선언하는데 * 구두점만 하나 더 적으면 된다.

 

int (**ppf)(int);

 

이렇게 선언된 ppf는 int (*)(int) 타입으로 선언된 함수 포인터 변수나 함수 포인터 배열을 가리킬 수 있는 이차 함수 포인터 변수이다. ppf=&pf 또는 ppf=arpf 식으로 함수 포인터 변수의 번지를 대입받을 수 있으며 ppf로부터 함수를 호출할 때는 (**ppf)(2) 형식을 사용한다. 함수 포인터 배열 arpf와 이중 함수 포인터 ppf가 메모리에 구현된 모양은 다음과 같다.

함수 포인터의 타입은 함수가 취하는 인수들의 타입과 리턴값까지 정확하게 밝혀야 하기 때문에 타입의 형식이 너무 길어서 쓰기에 번거롭다. 또한 함수 포인터로부터 파생된 타입을 만드는 것도 헷갈리고 생소한 면이 있다. 그래서 함수 포인터 타입을 자주 사용하거나 자신이 없다면 직접 타입을 기술하는 것보다 typedef로 함수 포인터 타입을 따로 정의한 후 사용하는 것이 편리하다. int func(int)형의 함수를 가리키는 타입은 다음과 같이 정의한다.

 

typedef int (*PFTYPE)(int);

PFTYPE pf;

 

함수 포인터를 선언하는 문장에서 변수명을 원하는 타입 이름으로 바꾸고 앞에 typedef만 붙이면 된다. 이후 컴파일러는 PFTYPE이라는 명칭을 int (*)(int) 타입으로 인식하므로 PFTYPE으로 함수 포인터 변수를 쉽게 선언할 수 있으며 캐스트 연산자로도 사용할 수 있다. 또한 함수 포인터로부터 배열이나 포인터같은 파생 변수를 선언하는 것도 훨씬 더 간편하다.

 

PFTYPE arpf[5];

PFTYPE *ppf;

 

마치 int형으로부터 배열이나 포인터를 선언하듯이 PFTYPE을 사용할 수 있으므로 직관적이고 읽기에도 좋다.

Posted by gsi
: