본문 바로가기
함수 포인터와 실행주소 변경

함수의 이름은 Symbol이라는 말을 잊지 않으셨겠지요? Symbol이라 함은 Physical 주소를 점유한다는 뜻이고요, 실제 함수의 이름이 뜻하는 것은 실행코드 영역에서의 함수의 시작주소를 의미합니다. 자, 그렇다면 이 함수이름 자체를 어딘가의 포인터에 도킹~! 시켜서 포인터가 가리키는 곳을 실행 시키면 원하는 함수를 실행시킬 수 있다는 말이죠! 그러면, 언제든지 소프트웨어 실행 시 같은 지점에서 그때 그 때 사정에 따라 다른 함수를 실행 시킬 수도 있다는 말이죠! 오호라

1. 함수포인터의 선언

이런 포인터 함수의 선언은 다음과 같이 해요.

자료형 (* 함수포인터 이름) (인자목록)

뭐, 우리 일반 포인터 자료형을 선언할 때도 자료형 * 포인터 이름으로 선언하잖아요.예를 들어 character형을 가르키는 pointer를 선언할 때는
char * cdata;
뭐 이런식이죠.

똑같이 함수 포인터를 선언하려면 아래처럼 할 수 있어요. integer형을 return하고 integer형 argument를 하나 받는 포인터함수는..

int (*function)(int a);

이런 식으로 선언할 수 있는게죠.

그럼, 잠깐 퀴즈. 다음은 뭘 의미할까요?

int * function (int a);

요건 function이라는 함수가 integer형 포인터를 return받는다는 뜻이에요. ㅋ. 전혀 다르죠.

그럼 이런 함수의 초기화는 어떻게 하느냐! 역시나 포인터 함수 역시 포인터니까, 포인터에는 포인터를 갖다 붙여주시는 센스. 만약에 저 pointer함수에 int recipe(int a)라는 함수를 엮어 주시려면

function = recipe;
또는
function = &recipe;

라고 엮어주시면 되요. 주의할 사항은 함수의 이름이 주소를 의미하니까, function = recipe(); 라고 엮어 주시면 큰일 납니다. 뒤의 ()는 함수라는 뜻이고요. C는 () 이것마저 연산자 입니다. 함수라고 알려주는 거고요. 결국 그러다 보니,   실제 주소를 가리켜야 하는데 ()을 붙임으로써 함수라는 걸 알려주는 꼴 밖에 안됩니다. 주소는 모르겠고.. recipe가 함수구나.. function아 recipe는 함수야.. 하고 알려주면 머합니까? 이렇게 엮어준 다음부터는 function() 이라고 호출해 주면 recipe()가 호출되는 효과를 가져와요.

2. 함수포인터 Array

자, 그러면 이런 함수 포인터를 이용하면 함수를 array로도 엮을 수 있겠네요. 사칙연산 같은 함수로 엮어 볼까요? (plus(), minus(), multiply(), divide() 함수가 우리가 아는 그 사칙연산을  해주는 함수라고 가정했을 때. )

예를 들면

int (* functions[3][4]) (int, int) = { plus, minus, multiply, divide }
라고 선언해 놓으면
function[2](1, 3) 은 뭐가 될까

결국 multiply (1,3)을호출하게되어 return값은 3이되겠네요.

뭐이런식이라면 더더욱 아름답고 Flexible한 소프트웨어를 만들수 있겠어요. 

typedef enum
{
    PLUS = 0,
    MINUS,
    MULTIPLY,
    DIVIDE
    NUM_MAX;
} cal_type;

int Calculation (cal_type how, int a, int b)
{
if (how >= NUM_MAX)
return NULL;
else
return function[cal_type](a, b);
}

뭐 약간 억지스럽긴 하지만, 이렇게 하면 어떨까요? calculation (PLUS, 3, 4)의 결과는 plus (3, 4) 이고 결과 값은 7이 되겠네요. 아이 간편하네요. 와하하.

또는 4칙 연산을 자동으로 모두 시행 하고 싶으면,

void Calculation_All (int a, int b)
{
int loop;

for (loop =0; loop<4; loop++)
{
    printf ( "%d \n", function[loop] (a, b);
}

return;
}

요렇게도 가능하겠네요. 으흐흠.

3. 함수 포인터의 응용 → Device Drivers

이런 식의 함수 포인터를 어디다가 사용하면 편할까요? 이런 식의 함수 포인터는 Device Driver들을 그때그때 다르게 쓰고 싶을 때 사용하면 아주 좋습니다. 예를 들어, 주변 Device들 중에 같은 기능을 하지만 여러 가지 Vendor의 Device들을 한꺼번에 지원하고 싶을 때는 이런 함수 포인터를 이용하여 구현하면 아주 편리하답니다. 예를 들어, 이번 Article은 예만 엄청 들다 끝나는 거 아닌가 모르겠네요. 어떤 device가 read, write의 기능을 한다고 가정했을 때 아래처럼 포인터 함수를 정의해 놓고서,

typedef struct {
    const char      *name;
    void (*read) (byte *buffer, int count);
    void(*write) (byte *buffer, int count);
} device_type;

요런 식의 device driver들을 선언한 후에

device_type device =
{
    "It's me",
    device_read,
    device_write
}

Device driver를 끼워 넣을 수 있어요.

device_type *device_target;
device_target = &device

이렇게 해주면 device driver를 사용하는 사용자 입장에서는

(*device_target->read)(buffer, count)

이런 식으로 호출해 주기만 하면 그때그때 Driver가 바뀌는 효과를 가져올 수 있는 거지요. 이때는 drivce_read (buffer, count)가 불리겠죠. device2라는 걸 선언해 놓고 device_target = &device2 라고 선언하면 device2에 엮인 driver들이 불리는 거죠 머.

4. 함수포인터와 typedef

자, 이런 식의 함수연결을 더 편하게 해볼려면 뭐가 있을까요?Array도 이용 해 봤겠다. 이번엔 typedef를 이용 해 보시지요.

typedef int (funtion)(void) 로 선언하면 어떨까요?

이 의미는 function이라는 것이 int return값을 받으며, void 인자를 받는 함수라는 뜻이에요.  유식하게는  int ()(void)라는 뜻이죠.

그럼 이렇게 쓰면 어떻게 될까요?

function *temp = hello;

이 의미는 helllo()가 int return값을 가지며, (void) 인자를 갖는 함수라는 뜻이에요. 결국 temp() 를 호출하면, hello()를 호출하는 것과 같은 의미를 가집니다. 복잡하면서 재밌죠?

5. 함수 포인터의 완전 응용 → 원하는 주소로 억지로 branch

자, 그러면 또 한가지 예를 들어볼까요? 우리 Embedded System을 만들다 보면, 특정 주소로 branch해야 할 일들이 있습니다. 예를 들어 뭔가 하다가 꼭 0x7777 주소로 branch해야 할 일이 생겼다고 칩시다. 이때는 0x7777주소를 일단 void형의 함수 pointer임을 알려주는 casting을 해야 하겠습니다. 그건 () 요게 함수라는 표현이라 했으니까 이용하면 되고, void형 포인터 함수라는 뜻의 void (*)를 이용하면 되겠습니다.

이런 경우에는 결국.

void (*example) (void);
example = (void (*)())0x7777;
(*example)();
이라고 만들어 주면 억지로 pc를 0x7777로 만들어주는 효과가 있습니다. 이걸 한줄로 유식하게 표현하면

(*(void(*)())0x7777)();

요렇게도 가능하겠지요. 냠냠. 재미있는 예를 하나 들면, 어떤 NOR flash의 경우에는 NOR flash를 probing하기 위해서 command set을 날리면 NOR 영역이 모두 status를 return해 주는 바람에, 너 누구냐 하고 묻는 순간에는 NOR flash에서 software를 실행하면 Undefined Exception이 나는 현상이 발생했어요. 이를 모면하기 위하여 어떤 트릭을 썼냐 하면,

static unsigned int32 find_id[] = {
// id_code PROC
// In values: R0 = base of code section
0x1c01,            // MOV     r1, r0
// unsigned int32  codes;
// *base = (word)0x90;
0x2090,            // MOV     r0, #0x90
......
// return codes;
0x4770              // BX       r14
// out values, mfg id in top 16 bits R0
//             part id in bottom 16 bits R0
// ENDP
};

이런 식으로 미리 nmemonic을 32bit array에 넣어두고, 이 array는 PSRAM에 올려 놓고서, 이 array를 실행하는 형식을 쓴 거죠. 다음과 같이..
id = ((unsigned int32(*)()) ((unsigned char *) find_id)) (base);
인자는 base, id_code를 char형으로 align한 후 unsigned int32형을 return해주는 함수 포인터형으로 casting하여 id_code의 시작 주소를 함수처럼 실행해 버린 겁니다. 와~ 핑핑.. 머리 좋은 사람 너무 많아요.

그러면 맨 처음에 했던 선언을 기준으로 다음의 선언을 해석해 보세요.

ⓐ int (*ifunc)(int a);
ⓑ char *(*cfunc)(char *p[]);
ⓒ void (*vfunc)(void);

ⓐ int 형의 return값을 갖고, int a의 인자를 갖는 ifunc 라는 함수 포인터
ⓑ char 포인터형의 return값을 갖고, char형의 포인터 배열을 인자로 갖는 cfunc 이라는 함수 포인터
ⓒ return값이 없고, 인자값도 없는 vfunc라는 함수 포인터

뭐 간단한가요? 아 어질어질. 참!!!!포인터 함수의 연산은 허용하지 않습니당. 그거 조심하셔야 해욧.

 

 
Linked at 친절한 임베디드 시스템 개발자.. at 2009/09/06 00:20

... ⓘ 함수 포인터와 실행주소 변경 ... more

친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





친절한 임베디드 개발자 되기 강좌 글 전체 리스트 (링크) -