Programming/C++

함수 포인터와 void 포인터

OKOK 2017. 7. 27. 19:19

앞으로 공부해야 할 보다 수준 있는 프로그래밍 분야(알고리즘, 네트워크 프로그래밍, 시스템 프로그래밍, win32 프로그래밍) 을 접하면서 모든 문법적 요소의 필요성을 본인 스스로 느끼게 될 것 입니다. 


총 3개의 함수로 구성이 되어 있는 test.c 를 통해서 test.exe 라는 실행 파일을 생성하였다고 가정해 봅니다. 이 파일을 더블클릭 하게 되면 어떠한 과정을 거쳐서 프로그램이 실행 되나요? test.c 는 main, fct1 그리고 fct2 이렇게 3개의 함수로 구성되어 있다고 가정하겠습니다. 


프로그램의 실행은 CPU가 담당합니다. CPU는 메인 메모리라는 곳에 올라와 있는 것을 가지고 일을 하게 됩니다. 메인 메모리는 보통 램(RAM)을 이야기 하는 것입니다. 다시 말하면 하드 디스크상에 존재하는 test.exe 를 실행하기 위해서는 test.exe 를 구성하고 있는 함수 main, fct1 그리고 fct2를 메인 메모리로 옮겨 놓아야 한다는 것입니다. 그래야 비로서 CPU에 의한 실행이 가능해집니다. 메인 메모리에 올라와 있는 함수를 가리킬 수 있는 포인터를 함수 포인터라고 합니다. 프로그램 실행에 대한 자세한 내용은 운영체제와 시스템 프로그래밍을 공부하면서 알게 되는 내용입니다. 


컴퓨터를 부팅하고 나면 OS는 항상 메모리에 올라와 있으면서 사용자가 컴퓨터를 사용하는 것에 있어서 모든것을 관장합니다. 인출단계에서 메모리에서 프로그램 명령어를 읽어옵니다. 실행단계에서 명령어에 따라 실행합니다. 인터럽트단계에서 실행단계 중 인터럽트가 들어왔나 확인을 합니다. 인터럽트가 있었으면 인터럽트 단계를 실행하고 없었으면 인출단계부터 다시 반복합니다. CPU가 하는 일은 메모리에서 명령어를 읽어와서 실행하는 것입니다. 어떤 일을 할지는 interrupt, 프로그램의 명령어에 따릅니다. 


함수포인터

프로그래머가 정의한 모든 함수는 프로그램 실행 시 메인 메모리에 올라가게 됩니다. 메모리 상에 올라간 다음에야 실행이 가능하기 때문입니다. 이 때 함수의 이름은 메모리상에 존재하는 함수의 위치를 가리키는 주소 값을 의미합니다. 함수의 이름은 메모리상에 존재하는 함수의 위치를 가리키는포인터입니다.


#include <stdio.h>


void Add(int a, int b);

void SPrint(char * str);


int main(void)

{

char *string = "Function Pointer";

int a = 10, b = 20;


void(*fPtr1)(int, int) = Add;

void(*fPtr2)(char*) = SPrint;


fPtr1(a, b);

fPtr2(string);


return 0;

}


void Add(int a, int b)

{

printf("plus result : %d\n", a + b);

}


void SPrint(char * str)

{

printf("Input array : %s\n", str);

 

함수 포인터를 선언과 동시에 초기화하고 있습니다. 이제 fPtr1은 함수 이름 Add가 지니는 값과 같은 값을 지니게 되었습니다. 따라서 fPtr1 은 변수이고, Add는 상수라는 점을 제외하면 이 둘은 완전히 같습니다. fPtr2를 SPrint로 초기화하고 있다. 역시 fPtr2는 SPrint 와 같은 값을 지니게 됩니다. 함수 포인터를 이용해서 함수를 호출하고 있습니다. 함수의 이름이 지니는 값과 같은 값을 지니기 때문에 호출이 가능합니다. 


#include <stdio.h>


void SelFunction(int s);

void Add(void);

void Min(void);


int main(void)

{

int sel;


while (1)

{

printf("choice: plus(1), minus(2), end(3) ");

scanf_s("%d", &sel);

if (sel == 3)

break;

SelFunction(sel);

}

printf("Program ended. \n");


return 0;

}


void SelFunction(int s)

{

void(*fPtr)(void);

if (s == 1)

fPtr = Add;

else

fPtr = Min;

fPtr();

}


void Add(void)

{

int a, b;

printf("two number: ");

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

printf("result : %d\n\n", a + b);

}


void Min(void)

{

int a, b;

printf("minus two number: ");

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

printf("result: %d \n\n", a - b);


코드 길이는 다소 길어 보이지만 SelFunction 함수 하나만 유념합니다. 이 함수는 전달되는 인자를 통해서 실행할 함수를 결정 짓습니다. 함수 포인터는 우리가 정의한 함수를 운영체제(윈도우즈, 리눅스)에게 알려 줄 때 유용하게 사용되는 개념입니다. 본인이 정의한 함수의 이름을 운영체제에 전달하면 운영체제는 전달된 함수 이름을 이용해서 본인을 대신해 함수를 호출해 줍니다. 


void형 포인터

void 포인터 변수를 선언하게 되면 변수이건, 함수이건, 포인터의 주소 값까지도 저장이 가능합니다. 변수 c의 주소 값을, 변수 n의 값을 저장합니다. 무엇이든 담을 수 있는 포인터이므로 변수 n과 c의 타입은 중요하지 않습니다. void 포인터는 주소 값을 저장하는 것 이외에 아무 것도 할 수 없기 때문입니다. 


main 함수를 통한 인자의 전달

#include <stdio.h>


int main(int argc, char **argv)

{

int i = 0;

printf("convey num : %d\n", argc);


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

printf("%d th charter string : %s\n", i + 1, argv[i]);

return 0;


실행하는 방법 cmd 를 열고 프로젝트 실행 파일이 있는 곳으로 디렉토리를 옮깁니다. 그 다음에 프로젝트 이름과 전달하고자 하는 인자 2개를 입력합니다. 프롬프트상에서 main abcd 1234 라는 명령문을 전달하고 있습니다. 여기서 main은 실행시킬 .exe 파일을 지정해 주는 것이고, abcd 와 1234는 main 함수로 전달되는 인자를 지정해 주는 것입니다. 이러한 형태로 .exe 파일을 실행시키면 명령 프롬프트 상에서 내려진 명령문은 문자열 배열로 구성되어 main 함수의 인자로 전달되게 됩니다. 일단 명령 프롬프트상에서 내려진 명령문을 참조하여 문자열을 형성합니다. 이 때 문자열은 공백을 기준으로 나뉘게 되므로, 명령문을 통해 총 3개의 문자열을 형성합니다. 그리고 문자열을 담을 char형 포인터 배열을 형성합니다. 문자열의 개수가 3이므로 배열의 길이는 3이 됩니다. 각각의 배열 요소는 형성된 문자열의 주소 값을 지니게 되고, 최종적으로 main 함수의 두 번째 인자로 전달 됩니다. 그렇다면 main 함수의 첫 번째 인자로 전달되는 것은 문자열의 개수입니다. 


이번 포스팅에서 함수 포인터가 무엇을 하는 포인터인지 공부하였습니다. 함수 이름도 사실은 포인터라는 것을 기억하세요. 또한 void 포인터가 어떠한 특징을 지니는지 살펴보았습니다. 프로그램 실행 시 main 함수를 통해서 문자열을 전달하는 방법에 대해서 살펴보았습니다.