본문 바로가기

Lang/C

[C] 포인터 # 함수 포인터의 이해

728x90

# 함수 포인터의 정의

함수 포인터는 함수를 가리키는 포인터입니다.

포인터는 메모리상의 주소를 저장하는 변수인데, 함수도 메모리에 존재하며 그 시작 주소가 있으므로 포인터 변수로 가리킬 수 있습니다.

 

정수형 데이터 타입을 가리키는 정수형 포인터는 정수형 변수의 주소를 가집니다.

하지만 함수는 그 안에 여러 데이터 타입이 있을 수 있기 때문에 좀 더 복잡한 선언 형식이 필요합니다.

 

* 함수포인터의 선언 
리턴 타입 (*변수명)(인수의 목록)

 

int func(int x); 라는 함수를 가리키는 함수 포인터를 선언하려면

int (*mypf)(int x); 로 선언해야합니다.

 

 

- 함수 포인터 선언 방법

함수 선언식에서

1. 함수명을 필요한 변수명으로 바꾸고

2. * 를 붙이고,

3. ( ) 감싸면 됩니다.

 

여기에서 인수목록의 변수는 생략이 가능합니다.

int (*mypf)(int);    // x를 생략하였습니다.

 

그리고 이렇게 선언된 함수 포인터에 함수를 대입하면 됩니다.

mypf = func;

함수명에 괄호가 없으면 그 함수의 시작 주소를 가리키는 포인터 상수가 됩니다.

그래서 함수 포인터에 함수의 주소를 대입하는 것이 됩니다.

 

괄호없는 배열명이 그 배열의 시작번지를 가리키는 포인터 상수인 것과 같습니다.

 

그리고 포인터가 데이터타입이 같은 다른 변수를 가리킬 수 있듯이

함수 포인터도 데이터 타입(함수의 원형)이 같다면 다른 함수를 가리킬 수도 있습니다.

int func1(int a){
	return 1;
}

int func2(int b){
	return 2;
}

int func3(int value){
	return 3;
}

void main(){
	int (*mypf)(int);
    
	// 함수 포인터의 타입이 같아 모두 대입 가능합니다.
	mypf = func1;
	mypf = func2;
	mypf = func3;
}

 

 

- 함수 포인터로 함수 호출하기

mypf(2); 라고 호출하면

func(2); 라고 호출한 것과 같습니다.

 

원칙적으로 (*mypf)(2); 가 맞는 방법이지만

컴파일러에서 간편하게

mypf(2); 로 호출하여도 문제가 없도록 컴파일해주고 있습니다.

 

 

 

 

# 함수 포인터의 타입

기본적으로 타입이 다른 함수 포인터를 서로 대입할 수 없는데

강제적으로 가능한 방법이 있습니다.

 

왜냐하면 void  포인터에 저장된 함수의 주소를 대입받는다던가,

자료 구조 설계 시 미리 알 수 없는 함수에 대한 포인터를 다룬다던가 하는 이유에서 입니다.

 

이 때 캐스트연산자로 강제적 대입이 가능합니다.

int (*pf1)(char *);
void (*pf2)(double);
pf1 = pf2;	// 에러. 
pf1 = (int(*)(char*))pf2	// 강제 캐스트로 가능합니다.

- 캐스트 연산자 작성 법

1. int (*pf1)(char*)     // 함수 포인터 선언식에서

2. int (*pf1)(char*)     // 변수명을 지우고

3. (int (*)(char*))        // 전체를 괄호로 감싸면 캐스트연산자가 됩니다.

4. pf1 = (int(*)(char*))pf2;    // 완성.

 

물론 무분별하게 캐스트를 하게되면 오작동을 유발하게됩니다.

 

 

 

 

- 함수 포인터의 배열 선언

int (*arrpf[5])(int);	// 배열 첨자는 변수명뒤에 붙입니다.
int func(int);의 함수 데이터타입을 가진 함수 포인터 5개를 저장할 수 있는 배열입니다.

 

 

 

- 함수 포인터의 포인터 (이중 함수 포인터)

 

int (**ppf)(int);	// * 하나 추가
(**ppf)(2);		// 호출문
int func(int);를 가리키는 함수포인터를 가리키는 포인터입니다.

 

 

 

- 복잡한 함수 포인터 선언식을 typedef로 치환하기

#include <stdio.h>
typedef int(*myPfTypeName)(int); // (1) 복잡한 함수 포인터 선언식을 간단한 명칭 하나로 치환 후

int func(int x){
	return x*2;
}

int main(){
	myPfTypeName pf;	// (2) 일반 데이터타입 변수 선언처럼 사용가능합니다.
	pf=func;
	int i = pf(2);
	
	printf("%d", i);
	return 0;
}

 

 

 

 

# 포인터로 함수 호출하기

함수 포인터를 사용하는 이유

#include <stdio.h>
#include <conio.h>	//getch()의 헤더
int m2(int x){
	return x*2;
}

int m3(int x){
	return x*3;
}

int main(){
	int (*pf)(int);
	
	char ch = getch();
	
    // 필요에 따라 다른 함수를 가리킨 후
    if(ch=='2')
		pf=m2;
	else
		pf=m3;
	
    // 호출하여 사용할 수 있습니다.
	printf("%d", pf(5));
	return 0;
}
출력결과
10    // 실행 후 2를 입력했을 경우
15    // 실행 후 다른 것을 입력했을 경우

 

 

 

- 위 코드 예제로 보았을 때 너무나 간단해서 함수 포인터를 꼭 사용해야하나 싶겠지만

1. 함수가 수십개라면 함수 포인터 배열을 선언하고 첨자를 선택한다던가
2. 함수를 선택하는 시점과 함수를 호출하는 시점이 다르다던가
3. 호출할 함수가 외부에 있어 동적으로 연결할 계획이라서 컴파일할 때는 그 함수를 알 수 없을 때

등의 상황에서 함수 포인터를 꼭 필요한 존재입니다.

 

 

 

 

 

# 함수 포인터 인수

함수 포인터를 다른 함수에게 인수로 보내는 경우

대표적으로 <stdlib.h>의 qsort()함수가 있습니다.

qsort()함수의 원형은 다음과 같이 생겼습니다.

void qsort(void *base, size_t num, size_t widthint ( *compare )(const void *, const void *));

qsort()함수는 base번지에서부터, 하나의 사이즈가 width이고, num개가 있는 값을

일정한 기준에 따라 정렬하는데,

여기에서 일정한 기준을 정의하는 비교 함수인 compare 가 인수로 전달되어야 합니다.

 

 

 

- qsort()함수를 통해 오름차순으로 정렬하는 코드

#include <stdio.h>
#include <stdlib.h>
int compare(const void *a, const void *b)
{
     if (*(int *)a == *(int *)b) return 0;
     if (*(int *)a > *(int *)b) return 1;
     return -1;
}

int main()
{
     int i;
     int ar[]={83, 4, 86, 8, 27, 12};

     // compare 함수를 인수로 qsort()를 호출하고 있습니다.
     qsort(ar,sizeof(ar)/sizeof(ar[0]),sizeof(int),compare);
     for (i=0;i<sizeof(ar)/sizeof(ar[0]);i++) {
          printf("ar[%d]= %d\n",i,ar[i]);
     }
     return 0;
}
출력결과
ar[0]= 4
ar[1]= 8
ar[2]= 12
ar[3]= 27
ar[4]= 83
ar[5]= 86
* 사용자가 비교 함수를 제공해야하는 이유
간단한 정수 비교만 한다면 명확하게 비교가 가능하겠지만 문자 비교의 경우 대소문자를 구분할 것인지 여부, 밑 줄이나 쉼 표 등까지 비교할 것인지 여부, 한글과 영문의 우선순위 등에 따라 복잡해지며, 이차 정렬까지 고려하면 일반화가 불가능하여 사용자가 비교 함수를 제공하도록 qsort()함수를 만들었습니다.

 

 

 

- 함수포인터를 다른 함수에게 인수로 보내는 경우 2

ftp서버에서 파일을 다운받아 로컬 HDD에 저장하는 함수가 있다고 가정합니다.

예를 들어 이렇게 생겼습니다.

// FtpDown("fpt서버의 주소", "다운받을 HDD의 주소"); 
void FtpDown(const char *src, const char *dest);

이 함수를 실행했는데 다운받는 파일의 용량이 매우 커서 시간이 오래 걸린다면 그 동안 프로그램은 멈춰있는 것처럼 보일 것입니다.

 

그래서 FtpDown함수 내에 진행사항을 문자열로 알려주는 코드를 추가 해 넣었습니다.

그 후 발생할 수 있는 문제점을 알아보면

1. 중간에 다운로드를 취소할 수 없습니다.

2. 진행사항을 문자열로만 알려줄 수 있습니다. 문자열이 아닌 그래프나, 애니메이션의 형태도 추가하고 싶을 수 있습니다.

 

그래서 FtpDown 함수 내에 안내 코드를 작성하지 않고,

사용자 정의 함수를 호출하여 진행사항을 알려주도록 하겠습니다.

// FtpDown("fpt서버의 주소", "다운받을 HDD의 주소", prog라는 함수 포인터를 추가);
void FtpDown(const char* src, const char* dest, BOOL (*prog)(int, int));
함수 포인터 prog는
(int (총 용량), int (현재까지 다운받은 용량))을 인수로 받고,
BOOL (다운로드 취소 여부)를 리턴받는 함수를 가리킬 수 있습니다.

 

#include <stdio.h>
#include <conio.h>
#include <windows.h>
void FtpDown(const char *src, const char *dest, bool (*prog)(int, int))
{
     int total, now;
     bool UserBreak;
     
     // 실제로는 다운받을 파일의 용량을 대입해야 합니다.
     total=100;
     now=0;
     for (now=0;now<total;now++) {
          
          // 실제로 다운 받는 코드는 생략합니다.
          // DownFile(src, dest);
          
          // for문과 함께 1MB다운로드 받는데 1/10초 걸리는 것을 표현합니다.
          Sleep(100);
          
          // 인수로 받은 진행과정 및 취소 함수를 호출합니다.
          UserBreak=(*prog)(total,now);
          if (UserBreak==TRUE) {
              puts("다운로드를 취소했습니다");
              break;
          }
     }
}
bool Progress(int total, int now)
{
     // 다운로드 과정을 보여 줍니다.
     printf("총 %dMB중 %dMB만큼 받았습니다.\n",total,now);
     
     // 만약 esc키를 누르면 true를 리턴하여 다운로드가 멈추게 됩니다.
     if (kbhit() && getch()==27) {
          return TRUE;
     } else {
          return FALSE;
     }
}
int main()
{
     FtpDown("원본의 주소","대상의 주소",Progress);
     return 0;
}

 

 

 

 

# 함수 포인터 리턴

함수 포인터를 리턴하는 경우는 거의 없기 때문에 간단하게 알아보겠습니다.

 

 

- 함수 포인터를 리턴하는 함수의 원형

fp의 리턴타입 ((*함수명)(인수목록))(fp의 인수 목록)
#include <stdio.h>
int func1(int a, double b)
{
     return 1;
}

int func2(int a, double b)
{
     return 2;
}

// 인수목록이 (int, double)이고,리턴 타입이 int인 함수 포인터를 리턴하는 함수
// 즉 func1를 가리키는 함수포인터 혹은
// func2를 가리키는 함수포인터를 리턴하게 됩니다.
int (*SelectFunc(char ch))(int,double)
{
     return (ch=='a'? func1 : func2);
}

int main()
{
    int (*funcPointer)(int,double);
    
    // b를 넣으면 func2 함수를 가리키는 함수 포인터가 리턴되고
    funcPointer=SelectFunc('b');
    
    // func2(1, 2.3); 가 호출되어 int형 2를 리턴합니다.
	printf("%d",funcPointer(1,2.3));
	return 0;
}
출력결과
2
728x90