int main(void)
{
int instructions[1024] = { 0 };
int pc = 0;
while (instructions[pc])
{
switch (instructions[pc])
{
case 1: //add
break;
case 2: //sub
break;
case 3: //mul
break;
case 4: //div
break;
default:
break;
}
pc++;
}
return 0;
}
위 코드는 instructions 배열 안에 있는 값에 따라 switch - case로 분기를 해 함수를 실행하는 구조문이다.
instructions 배열 안에 값이 1일 경우, add를 호출해 빠르게 처리할 수 있지만
만약 항목이 더 많아져서 배열안의 값이 1000일 경우 ( 물론 지금은 default로 처리 되지만 )
1000까지 비교해가면서 함수를 실행해야하기 때문에 속도가 느려진다.
이때 함수포인터 배열을 활용해 구조를 바꾸면 보다 빠르게 함수를 실행할 수 있어진다.
#include<stdio.h>
typedef struct MyParams {
int param1;
int param2;
int param3;
} MYPARAMS;
int (*g_op_list[5])(MYPARAMS*); // 함수 포인터 배열
int add(MYPARAMS* pParam)
{
return pParam->param1 + pParam->param2 + pParam->param3;
}
int sub(MYPARAMS* pParam)
{
return pParam->param1 - pParam->param2 - pParam->param3;
}
int mul(MYPARAMS* pParam)
{
return pParam->param1 * pParam->param2 * pParam->param3;
}
int div(MYPARAMS* pParam)
{
return pParam->param1 / pParam->param2 / pParam->param3;
}
void init_op_list(void)
{
g_op_list[1] = add;
g_op_list[2] = sub;
g_op_list[3] = mul;
g_op_list[4] = div;
}
int main(void)
{
init_op_list();
int instructions[1024] = { 0 };
int pc = 0;
MYPARAMS param = { 0 };
while (instructions[pc])
{
g_op_list[instructions[pc]](¶m);
pc++;
}
return 0;
}
위 처럼 switch - case 구문을 삭제하고 g_op_list 함수 포인터 배열을 활용해 직접적으로 해당 함수를 빠르게 호출해 줄 수 있다.
일반적인 함수를 저장하고, 다시 호출하는 방법은 위와 같다.
클래스에 있는 멤버 함수를 저장하고 호출할때는 어떻게 사용할까?
📌 클래스 함수 포인터 활용
정적
class Test
{
public:
Test();
~Test();
private:
void (Test::*Handler[5])(int a, int b);
public:
void Add(int a, int b);
}
클래스 안의 멤버 함수는 this 포인터를 받아야 하기 때문에 호출 방식이 일반 함수와는 다르다.
위의 Handler는 일반 함수가 아니라 Test 객체의 멤버 함수를 저장할 수 있다고 선언한다는 의미다.
Handler에 Test 클래스의 멤버함수를 저장하려면 다음과 같이 하면 된다.
packetProc[1] = &Test::int;
위와 같이 멤버 함수를 저장하고, 호출할때는
(this->*Handler[1])(a, b);
이처럼 호출하면 된다.
위 예시는 5개로 크기가 지정되어있는데, 동적으로 만드려면 어떻게 해야할까?
동적
class Test
{
public:
Test();
~Test();
private:
void (Test::**Handler)(int a, int b);
public:
void Add(int a, int b);
}
함수 포인터는 함수를 가리키는 포인터로, 특정 함수의 주소를 저장하고 이를 통해 해당 함수를 호출할 수 있다.
함수의 이름은 그 자체가 주소다.
함수호출 연산자의 피연산자는 함수 포인터와 같은 형식이여야 한다.
실행 코드가 저장된 메모리를 가리키는 포인터다.
이름이 주소인 경우는 배열 또한 마찬가지인데,
배열은 프로세스에서 데이터 영역에 저장된다. 데이터 영역은 읽고 쓰기가 가능하며, 실행은 불가능하다.
반면 실행 코드가 담겨 있는 실행 코드 영역에서는 읽기와 실행이 가능하며 쓰기는 불가능하다.
만약 실행 코드 영역이 쓰기가 가능하면 해당 실행 코드가 위조 혹은 변조가 되기 때문
운영체제는 실행코드의 영역과 데이터의 영역을 강력하게 구별한다.
📌 함수 포인터 형식
반환형 (*이름)(매개변수, ... )
int add(int a, int b) {
return a + b;
}
위와 같은 함수가 있을때, add 함수를 함수포인터에 담으려면 다음과 같이 선언하고 add 함수를 담으면 된다.
int (*pfAdd)(int, int) = add;
#include<stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int result = 0;
result = add(1, 2);
printf("Result: %d\n", result);
int (*pfAdd)(int, int) = add;
result = pfAdd(3, 4);
printf("Result: %d\n", result);
return 0;
}
위와 같이 코드를 구성하고 실행한 후 어셈블리어를 확인해보자.
Release에서 살펴보면, 함수 포인터 역시 함수를 호출하는 변수이기 때문에 최적화가 될 경우 함수 포인터를 콜해도 어셈블리에서는 call 하지 않는 것을 확인 할 수 있다.
반면 Debug에서는 pfAdd에 add 함수의 주소를 담고, pfAdd를 call하는 것을 확인 할 수 있다.