노무현 대통령 배너


2006. 7. 20. 14:16

[본문스크랩] 함수 포인터란?

1. 함수 포인터란?

- 프로그램에서 함수의 이름은 메모리에 로드된 그 함수의 실행코드 영역의 시작주소를

한다.

- 함수에 대한 포인터는 바로 함수의 시작주소값을 갖는 포인터이다.

함수 포인터라는 것 역시 32비트 체제하에서 4바이트의 메모리를 갖는 포인터 변수 입니다. 일반 포인터 변수와 다른 점은 일반 포인터 변수가 변수들의 주소값을 저장하는 반면에 함수 포인터는 함수의 주소값을 갖는다는 것입니다. 함수는 code 부분입니다. 즉 프로그래머가 짠 코드가 컴파일 되어서 기계 코드로 변화된 것, 그것이 바로 code 입니다. 프로그램이 실행되기 위해서는 이 code가 메모리에 올라가 있어야 하는 것입니다. 여기서 어떤 함수의 호출은 이 code중에서 그 함수 부분으로 jump(이동) 하는 것이지요. 바로 이 함수 부분이라는 것이 그 함수의 주소값이 되는 것이고 이 함수의 주소값을 저장하는 포인터가 함수 포인터입니다.

C 언어의 경우 함수자체를 변수로 만들수는 없습니다. 대신 함수를 포인터하는 것은 가능한데, 이것을 통해서 함수를 포인터 처럼 사용할수 있으며, 이 포인터가 가리키고 있는 곳의 함수를 실행시킬수도 있습니다.

예) main 함수와 printf 함수의 시작 주소값을 출력합니다.

#include

main()

{

printf("address of main : %u n", &main);

printf("address of printf : %u n", &printf);

}

2. 함수 포인터의 용도

-함수에 접근하기 위해 사용된다.

-함수에 함수 자체를 실인수로 전달하기 위해 사용된다.

-함수의 처리결과가 함수일 때 그 함수에 대한 포인터를 돌려주기 위해 사용된다.

함수 포인터에 대한 연산은 허용되지 않습니다.

3. 함수 포인터의 선언

-다른 포인터 변수와 마찬가지로 함수 포인터도 먼저 선언하고 사용해야 한다.

- 함수 포인터의 선언은 일반적으로다음의 형식을 사용한다.

자료형 (*함수포인터명)(인자목록);

이 형식은 명시된 자료형을 돌려주고, 인자목록에 포함된 인자를 받는 함수에 대한

포인터를 선언한다.

-함수 포인터 선언의 구체적인 예

ⓐ int (*f1)(int a);

ⓑ char (*f2)(char *p[]);

ⓒ void (*f3)();

ⓐ 하나의 int형 인자를 받아들이고, int형 자료를 돌려주는 함수에 대한 포인터 f1을

선언한다.

ⓑ char형에 대한 포인터 배열을 인자로 받아, char형의 값을 돌려주는 함수에 대한

포인터f2를 선언한다.

아무런 인자도 받지 않고 결과값도 돌려주지 않는(void) 함수에 대한 포인터 f3를

선언한다.

- 함수 포인터 선언과 포인터를 돌려주는 함수 선언의 차이

ⓐ int (*f1)(int a);

ⓑ int *f2(int a);

ⓐ 함수 포인터 : 한 개의 int형 인자를 받아, int형 값을 결과로 돌려주는 함수에 대한

포인터 f1을 선언한다.

ⓑ 포인터를 돌려주는 함수의 선언 : 한 개의 int형 인자를 받아, int형 포인터 값을

결과로 돌려주는 함수를 선언한다.

함수에 대한 포인터의 선언은 반드시 포인터의 이름과 간접연산자(*) 주위에 ( )를 사용해야 합니다.

4. 함수 포인터의 초기화

- 함수 포인터를 선언하고 나면, 이 포인터가 어떤 함수를 지시하도록 초기화해야 한다.

- 함수 포인터를 초기화할 때, 인자목록과 return 자료형이 일치해야 한다.

- 함수 이름은 이름 자체로 주소를 의미한다. 따라서 함수포인터에 함수의 주소값을

초기화하려면 다음과 같이 한다.

예)

int add(int a, int b); => 함수의 prototype

int (*f1)(int x, int y); => 함수 포인터 선언

int add(int a, int b){ return a + b; } => 실제 함수 정의 부분

f1 = add; => 적합

f1 = &add; => 적합

f1 = add(); => 오류(f1은 포인터, add()의 결과는 int)

f1 = &add(); => 오류(&부적당)

5. 함수 포인터의 활용

- generic한 함수(혹은 알고리즘)의 작성을 가능하게 한다

- 잘 사용하면 유지/보수를 수월하게 한다.

- 함수의 이름 자체는 배열의 이름처럼 한번 정해지면 바꿀 수 없는 포인터 상수이다.

그러나 함수 포인터는 변경이 가능하며 필요할 때마다 다른 함수를 지시하도록 설정할

수 있다.

예)

#include

int add(int a, int b); /* 함수의 prototype */

int sub(int a, int b);

int mul(int a, int b);

int div(int a, int b);

main()

{

int (*fun)(int x, int y); /* 함수 포인터 선언 */

int a, b;

char c;

printf("Input (num op num) : ");

scanf("%d %c %d", &a, &c, &b);

switch (c)

{

case '+' :

fun = add;

break;

case '-' :

fun = sub;

break;

case '*' :

fun = mul;

break;

case '/' :

fun = div;

break;

}

printf("%d %c %d = %dn", a, c, b, fun(a,b));

}

int add(int a, int b)

{

return a+b;

}

int sub(int a, int b)

{

return a-b;

}

int mul(int a, int b)

{

return a*b;

}

int div(int a, int b)

{

return a/b;

}

입력에 따라 함수포인터 fun에 지정되는 함수가 결정된다.


- 함수 포인터의 배열: 여러개의 함수포인터를 배열에 저장하여 사용할 수 있다.

int (*fun[3])(int, int);

int 형 자료 두 개를 입력으로 받아, int형 결과를 돌려주는 함수포인터 3개를 저장할 수

있는 배열이다.

예) 두 정수 a,b를 읽어 합,차,곱을 구하는 예제로 함수포인터의 배열을 사용한다.

#include


int add(int a, int b); /* 함수의 prototype */

int sub(int a, int b);

int mul(int a, int b);

main()

{

char op[] = {'+', '-', '*'};

int (*fun[])(int x, int y) = {add, sub, mul};

int a, b;

printf("Input number(2) : ");

scanf("%d %d", &a, &b);

for (i = 0;i < 3; i++)

printf("%d %c %d = %dn", a, op[i], b, fun[i](a,b));

}

int add(int a, int b)

{

return a+b;

}

int sub(int a, int b)

{

return a-b;

}

int mul(int a, int b)

{

return a*b;

}

- 함수 포인터를 인자로 전달하기:함수명을 인자로 전달하거나 함수 포인터 자체를

함수의 인자로 보내고 받을 수 있다.함수명을 실인수로 사용할 경우, 호출당한

함수의 가인수는 함수 포인터가 된다.

예)
#include


int print_add(int a, int b)

{

printf("%d + %d = %dn", (a, b, a+b);

}

int add(void (*fp)(int, int), int x, int y)

{

fp(x, y);

}

main()

{

add(print_add, 10, 5);

}

- 함수의 주소 값 전달

add(think); => think(); 함수의 시작 번지 값이 add() 함수의 매개변수이다.

add(think()); => think(); 함수의 리턴 값이 add() 함수의 매개변수이다.

- 함수 포인터를 이용해 특정 번지로 점프하기

예1)

#include

void main(void)

{

unsigned int goaddr = 0x8120; /* 8120H 번지임을 나타낸다. */

void (*gofunc)(void); /* 함수 포인터 선언 */

gofunc = (void(*)()) goaddr; /* 초기화 */

(*gofunc)();/* 함수포인터 실행 */

}

위 예에서는 void (*gofunc)(void); 로 선언된 함수 포인터가 실제적으로 가리켜야 할

목적지 함수가 따로 없는 것처럼 보인다. 그러나 잘 보면 목적 함수는 다음과 같음을

알 수 있다.

void (*goaddr)(); : 목적함수

그리고 위 목적함수는 하나의 형(type)으로써 뒤의 goaddr을 cast한다.

이제 (*gofunc)(); 로 실행되면 컴퓨터의 PC(Program Counter) 또는

IP(Instruction Pointer)는 (*gofunc)(); 함수로부터 void (*goaddr)(); 함수로 넘어간다.

그리고 void (*goaddr)(); 함수의 시작번지는 0x8120 번지가 되는 것이다. 따라서

컴퓨터의 PC는 0x8120번지로 점프하게 되는 결과를 낳는다.

예2)
#include


void main(void)

{

void (*gofunc)(void); /* 함수 포인터 선언 */

gofunc = (void (*)()) 0x8120; /* 시작주소 초기화 */

(*gofunc)(); /* 함수포인터 실행 */

}


위 프로그램은 번거롭게 goaddr이라는 변수를 생략하고 직접 (*gofunc)();의 시작주소를

지정하였다. 이로서 확실히 알 수 있는 것은 포인터 함수의 시작주소의 캐스팅 형이

(void (*)()) 이 된다는 것이다.

예3) 단 한줄로 나타내보자.

#include


void main(void)

{

(*((void (*)()) 0x8120))();

}


이 한줄은 바로 0x8120번지로 점프하라는 명령어와 같다!

그러나 TurboC++ 3.0 에서는 위 명령이 유효하나 IC96에서는 무효하다.

(다음과 같은 에러 메시지가 뜬다.)

iC-96 FATAL ERROR --

internal error: invalid directionary access, case 3

COMPILATION TERMINATED


그러나 방법은 있다. 아래와 같이 하면 IC96에서 에러 없이 멋지게 만들어낼 수 있다.

#include <80c196.h> /* 표준 인클루드 파일 */

void main()

{

(*(void (*)(void))(*(void (**)(void))0x8120))();

}

실제로 위와 같은 선언은 특히 Embedded System Programming에서 많이 사용되는

방법이다. 특히 80C196에서 이중 인터럽트 벡터 지정 시에 유용하게 사용될 수 있다.

출처 : 네이버 카페 임베디드시스템(Device Control)