함수의 주소
변수를 선언하면 메모리 공간이 할당되고 그 공간의 위치가 주소로 존재하듯이 함수를 선언해도 변수와 마찬가지로 메모리에 공간이 할당되며 그 위치를 표현하는 주소가 생겨난다. C언어 코드는 컴파일이 되면 기계어로 변경되고 프로그램이 실행되면 코드 세그먼트라는 메모리 영역에 위치하게 된다. 즉, 함수의 형태는 변경되겠지만 결국 메모리에 저장되기 때문에 주소를 가지게 된다는 의미다.
#include <stdio.h>
void print_hello()
{
printf("Hello, world!\n");
}
int main()
{
// 메모리 상에 저장된 함수의 주소값
printf("함수의 주소값 : %p\n", print_hello); // & 생략 가능
return 0;
}
포인터 변수란?
위에서 설명했듯 포인터는 변수의 주소만 저장했다가 사용할 수 있는 것은 아니다. 따라서 함수 포인터는 함수의 주소를 저장했다가 해당 주소의 함수를 호출하는 데 사용하는 포인터를 말한다.
반환형식 (* 식별자) (파라미터형 목록)
함수 포인터 (Function Pointer) 사용법
반환값과 매개변수가 없는 경우
#include <stdio.h>
void print_hello()
{
printf("Hello, world!\n");
}
int main()
{
void (*fp)(); //반환값과 매개변수가 없는 함수 포인터 fp 선언ㅁ
fp = print_hello; //print_hello 함수의 메모리 주소를 함수 포인터 fp에 저장
fp(); //함수 포인터로 print_hello 함수 호출
// (*fp)(); 역참조 해서 가능
return 0;
}
인자값을 받지 않고 값을 반환하지 않는 함수를 가리키는 print_hello 함수를 만들고 함수 포인터 fp를 선언한 뒤 이 포인터를 print_hello() 함수의 주소로 초기화한다. 이 포인터는 print_hello() 함수의 주소값을 저장하고 있기 때문에, fp 포인터를 역참조하여 함수를 호출할 수 있다.
함수 포인터를 선언할 때는 함수 포인터와 저장될 함수의 반환값의 자료형 그리고 매개변수 자료형과 개수가 일치해야 한다. 여기서는 반환값과 매개변수가 없는 함수를 저장할 것이므로 void로 지정한다. 또한, 매개변수가 없으므로 ()만 붙이면 된다. 그리고 (*fp)와 같이 함수 포인터 이름 fp 앞에 *를 붙이고 괄호로 묶어준다.
반환 값과 매개변수가 있는 경우
#include <stdio.h>
int add(int a, int b) // 덧셈함수
{
return a + b;
}
int sub(int a, int b) // 뺄셈함수
{
return a - b;
}
int main()
{
int (*fp)(int, int); //함수 포인터 선언
fp = add; //add 함수의 메모리 주소를 함수 포인터 fp에 저장
printf("결과 값 : %d\n", fp(10, 20)); //add 함수를 호출
fp = sub; //sub 함수의 메모리 주소를 함수 포인터 fp에 저장
printf("결과 값 : %d\n", fp(10, 20)); //sub 함수 호출
return 0;
}
예제에서는 add와 sub함수 둘 다 int형 인자 값 2개가 필요하고 int형으로 반환을 해야한다. 그렇기에 함수 포인터를 선언할 때 반환값은 int형으로 지정하고 괄호 안 인자값에 int형을 두 개 넣어준다.
함수 포인터 배열 사용하기
#include <stdio.h>
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;} //나눗셈함수
int main()
{
int (*fp[4])(int, int); //함수 포인터 배열 선언
fp[0] = add; // 배열[1]에 덧셈 함수의 메모리 주소 저장
fp[1] = sub; // 배열[2]에 뺄셈 함수의 메모리 주소 저장
fp[2] = mul; // 배열[3]에 곱셈 함수의 메모리 주소 저장
fp[3] = div; // 배열[4]에 나눗셈 함수의 메모리 주소 저장
for (int i = 0; i < 4; i++) {
printf("배열[%d] 함수의 실행 값 : %d\n",i, fp[i](20, 10));
}
return 0;
}
함수 포인터를 배열로 만들어 사용할 수 있다. 일반 배열과 사용법은 동일하며 배열 인덱스에 각각의 함수를 지정하고 사용하면 된다.
typedef로 함수 포인터 간소화 하기
typedef int (*PtrFunc)(int, int)
PtrFunc fp = NULL;
fp = add;
포인터 함수를 사용하는 이유
C언어에서 문장의 주소는 문장의 위치를 나타내는 것에 라벨을 사용을 사용하며 문장 단위의 이동을 하려면 이것으로 충분하기 때문에 존재하지 않지만 함수의 경우는 함수명에 의한 호출은 충분하지 않을 수 있다. 함수명을 사용하여 함수를 호출하는 방법은 호출할 함수가 컴파일 시에 결정되어야 한다는 것인데 이것은 동시에 호출할 함수를 실행할 때에 동적으로 변경할 수 없는 것을 의미한다. 이렇게 된다면 프로그램의 가능성을 크게 제한해 버리는 것이지만 함수 포인터를 이용해서 함수 이름을 운영체제에 전달하면 운영체제는 우리를 대신하여 함수를 호출해준다. 이러한 특성을 가지고 있는 포인터 함수를 사용하여 실행 시에 호출할 함수를 동적으로 변경시킬 수 있다면, 기능을 바꾸거나 부분적인 갱신이 가능한 유연성 있는 시스템을 구축할 수 있다. 그래서 포인터 함수는 개발자가 자유롭게 기능을 확장할 수 있는 시스템을 구축할 때에도 사용된다. 함수의 포인터 배열을 제공하고, 필요에 따라서 이를 개발자가 함수의 포인터를 등록한다. 그런 뒤 시스템은 처리를 할 때에 배열로부터 순서대로 함수를 호출한다. 이 함수의 연계에 의해 함수의 호출을 자유롭게 연쇄시킬 수 있기 때문에, 시스템은 확장성이 높고 유연하게 된다. 또한 자주 사용하는 함수의 시작 주소를 배열에 저장해서 사용하면 일반적인 함수 호출보다 빠른 처리 속도를 기대할 수도 있다.
'프로그래밍 언어 > C++' 카테고리의 다른 글
[C/C++] 비트연산자 (&, |, ^, ~, <<, >>, and, or, xor, 비트 반전, 비트 이동) (0) | 2023.09.25 |
---|---|
[C++] 함수를 객체로 사용하기 (std::function, std::mem_fn, std::bind) (0) | 2023.09.23 |
[C++] 매크로 개념과 주의사항 (0) | 2023.09.20 |
[실4] 28279 - 덱2 (0) | 2023.09.19 |
[C++] emplace_back 과 push_back 의 차이 (0) | 2023.09.19 |