C언어 : 함수의 포인터 / 콜백함수

지환·2022년 2월 4일
1

C언어

목록 보기
35/37
post-thumbnail

기존에 알고 있는 포인터는 데이터 세그먼트 or 스택 세그먼트에 있는 변수의 주소나 메모리 주소를 저장해서 사용하는 데이터 포인터에 대한 얘기다.

이번 시간은 코드 세그먼트에 있는 명령문의 주소를 저장해서 포인터로 사용하는 방법에 대해 알아보자.

함수 포인터

  1. 특정 함수를 구성하는 시작 명령의 위치를 가리키는 포인터다.
int sum(int a, int b)
{
    int result = 0;
    result = a + b;
    return result;
}


int result = sum(2,3);
  • 보편적으로 이와 같이 호출한다.
  • 함수 포인터를 사용하기 위해선 함수원형을 사용해서 포인터를 선언한다.
int sum(int a, int b); == int(*p)(int, int); //*p로 sum함수를 호출한다.
                           p = ∑ //sum의 함수의 주소를 p에 저장한다.

함수의 포인터를 사용하는 이유는 무엇일까?

-> 같은 형식의 함수를 그룹으로 묶을 수 있기 때문이다.
같은 형식이란(매개변수, 자료형, 같은 형태의 반환값)이다.

다음 예제를 보자.

 <덧셈>
 int sum(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;
 
 }
 
 ==============================
 int result 1, result2, result3,result4;
 result1 = sum(8,2);
 result2 = sub(8,2);
 result3 = Mul(8,2);
 result4 = Div(8,2);
 
 이 4개의 함수를 포인터 하나로 묶을 수 있다.
 
 int (*p[4])(int , int) = {&sum, &sub, &Mul, &Div};
 
 여기서 *p([4]) 와 (*p)[4]의 차이를 모른다면 포인터 부분을 다시 공부하도록 하자.
 
 ==========================================================
 int (*p[4])(int int) = {&sum, &sub, &Mul, &Div};
 int result[4], i; // 함수를 호출 했을 때 반환되는 값 4개를 저장할 배열을 선언
 for(i = 0; i < 4; i++) result[i] = (*p[i])(8,2); 

<함수 포인터를 사용해서 반복문으로 호출해보자>

#include <stdio.h>

int sum(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;

}
void main()
{
    int (*p[4])(int ,int) = { &sum, &sub, &Mul, &Div };
    char table[4] = { '+', '-', '*', '/' };
    for (int i = 0; i < 4; i++)
    {
        printf("%d %c %d = %d\n", 8, table[i], 2, (*p[i])(8, 2));
    }
}

<결과>

콜백 함수

  1. 프로그래머들은 자신의 코드가 노출되면 안되기 때문에 해당 코드를 컴파일해서 라이브러리(library, *lib) 형식의 파일로 제공한다.
  2. 라이브러리 안에 있는 함수들이 어떤 형태로 선언된 함수인지 알아야 코드를 자세히 볼 수 없는 사용자들도 사용할 수 있기 때문에 함수의 원형들을 헤더(header. *h) 파일에 적어서 함께 사용한다.

예를 들어 두 개의 정수 값을 넘겨 받아서 합산하는 sum 함수를 라이브러리 형태로 제공한다고 생각해보자.

  1. 라이브러리 사용자에겐 -> sum.lib 과 sum.h 를 모두 제공해야한다.
-라이브러리 프로그래머가 만든 헤더 파일과 라이브러리 파일
<sum함수의 원형>
sum.h
1. int sum(int a, int b);
< 값을 합산하는 함수>
sum.lib
int sum(int a, int b)
{
  return a + b;
}


-라이브러리 사용자가 사용하는 형태

#include "sum.h"
#pragma commnet(lib, "sum.lib")

void main()
{
   int result = sum(2,3);

}

※pragma 전처리기를 사용하면 컴파일러의 여러 가지 설정 값을 수정할 수 있다.
위에서 사용한 형식은 sum.lib 파일을 이 프로그램에서 사용하겠다는 의미다.

만약에 프로그래머에게 두 숫자 값이 음수인 경우에 양수로 변환해서 합산하는 함수도 추가로 만들어 달라고 요청했다고 해보자. sumabs라는 새로운 함수를 추가해야된다.

-라이브러리 프로그래머가 만든 헤더 파일과 라이브러리 파일
<sum함수의 원형>
sum.h
1. int sum(int a, int b)
2. int sumabs(int a, int b)

sum.lib

int sum(int a, int b)
{
  return a + b;

}

int sumabs(int a, int b)
{
  if(a<0) a = a * (-1);
  if(b<0) b = b * (-1);
     
     
     return a + b;
}

-라이브러리 사용자가 사용하는 형태

#include "sum.h"
pragma commnet(lib, "sum.lib")


void main()
{
  int result1, result2;
  result1 = sum(2,-3);
  result2 = sumabs(2, -3);

}
  • 라이브러리에 포함된 함수는 본래의 기능을 유지하고 사용자가 원하는 경우에 스스로 함수의 기능을 일부 수정할 수 있도록 제공 하는 것이 좋다.

  • 그렇다고 프로그래머가 라이브러리 소스 코드 전체를 줄 수는 없다. 그러면 라이브러리 사용자는 어떻게 소스 코드 없이 라이브러리를 수정해서 사용할 수 있을까?

  • 함수를 매개변수로 함수 포인터를 사용하면 된다.

void myabsolute(int *p)
{
  
    if(*p<0) *p = (*p) * (-1);

}

int sumabs(int a, int b)
{
    myabsolute(&a); // if (a<0) a = a * (-1)
    myabsolute(&b); // if (b<0) b = b * (-1)
    
    return a + b;

}
  • 사용자가 myabsolute 같은 기능의 함수를 만들어서 기존 라이브러리에 있는 sumabs 함수를 호출할 때 함께 사용하고 싶다면 어떻게 해야 될까?
  • 기존 라이브러리에는 myabsolute라는 이름의 함수는 없다. 이번에 배운 함수 포인터를 활용하면 함수 이름 없이도 함수를 호출 할 수 있다.
  • myabsolute 함수의 원형이 void myabsolute(int *); 이기 때문에
void (*p)(int *); 함수의 주소를 받아서 사용할 수 있다.

================================================

int sumabs(int a, int b, void (*fp_abs)(int*))
{
      (*fp_abs)(&a);
      (*fp_abs)(&b);
      
      return a + b;

}

=================================================

sumabs(5,-1, &absolute); //sumabs함수에서 myabsolute 함수를 호출한 것과 같음

sum(5, -1, NULL); //sumabs 함수에서 myabsolute 함수를 사용하지 않는 경우

==================================================

<NULL에 대한 예외처리>


int sumabs(int a, int b, void (*fp_abs)(int *))
{
     if(fp_abs != NULL) (*fp_abs)(&a);
     if(fp_abs != NULL) (*fp_abs)(&b);
     
     return a + b;
}

함수 포인터를 이용해서 a,b 변수마다 다르게 사용할 수 있도록 설정하겠다.

-라이브러리 프로그래머 시점

<함수 원형>
-sum.h-
1. int sum(int a, int b. void(*pa)(int *), void(*pb)(int *))

-sum.lib-

int sum(int a, int b, void (*pa)(int *), void (*pb)(int *))
{
    if(NULL != pa) (*pa)(&a);
    if(NULL != pb) (*pb)(&b);
    
    return a + b;


}

=============================================================

#include "sum.h"
#pragma comment(lib, "sum.lib")

void main()
{
  int result = sum(-3, -2, NULL, NULL);

}
  • 이렇게 사용하면 Sum 함수의 세 번째, 네 번째 매개변수의 함수 포인터에 NULL 값이 대입되어 Sum 함수의 if문은 둘 다 처리되지 않는다. -5 반환
#include "sum.h"
pragma commnet(lib, "sum.lib")


void myabsolute(int* p)
{
	if (*p < 0) *p = (*p) * (-1);

}


void main()
{

	int result = sum(-3, -2, myabsolute, NULL);

}
==============================================================
-라이브러리 프로그래머 시점

<함수 원형>
-sum.h-
1. int sum(int a, int b. void(*pa)(int *), void(*pb)(int *))
===============================================================
-sum.lib-
int sum(int a, int b, void (*pa)(int *), void (*pb)(int *))
{
    if(NULL != pa) (*pa)(&a);
    if(NULL != pb) (*pb)(&b);
    return a + b;
}
==============================================================
※호출관계를 파악해보자.

sum(-3,-2,myabsolute, NULL) <==> 
int sum(int a, int b, void(*pa)(int *), void (*pb)(int *))


1. sum 함수의 pa변수가 NULL이 아니기 때문에 if(NULL != pa)(*pa)(&a); 문장이 수행된다.
2. sum의 pa 변수 == myabsolute // (*pa)(&a) == myabsolute(&a) 
3. sum 함수의 매개변수 a의 주소 값이 myabsolute 함수의 포인터 변수 p에 저장된다.
4. myabsolute 포인터 변수인 p는 변수 a의 주소 값을 저장하고 있다. 
5. 따라서 p가 가리키는 값은 음수인 -3이라서 if(*p<0) *p = (*p) * (-1); 조건문이 실행된다.
6. 함수의 포인터 pb는 NULL 값이 저장되어 있기 때문에 if(NULL!=pb) (*pb)(&b); 조건문은 실행 x
7. sum함수의 변수 a값이 -3에서 3으로 변경되었기 때문에 3 + (-2) 가 수행 -> 1이 반환
profile
아는만큼보인다.

0개의 댓글