C언어 함수 포인터 (Function Pointer)
선수학습(3개)
요약
C언어 함수 포인터의 선언 문법과 호출 방법을 알아봅니다. int* (*fn)(int*) 같은 복잡한 선언 해석, 구조체 멤버로 함수 포인터 사용, 호출 시 역참조 연산자 순서까지 정보처리기사 실기 26년1회 기출 대비 핵심 개념을 정리합니다.
함수 포인터 핵심 정리
아래 표는 이 페이지에서 다룰 개념을 미리 정리한 것입니다. 지금 바로 이해가 안 돼도 괜찮습니다. 아래 본문에서 하나씩 풀어서 설명합니다.
| 개념 | 설명 | 예시 |
|---|---|---|
| 함수 포인터 선언 | 함수의 주소를 저장하는 포인터 | int (*fp)(int); |
| 함수 이름 | 그 자체가 함수의 시작 주소 | fp = dummy; (&dummy와 같음) |
| 함수 포인터 호출 | 저장된 함수를 실행 | fp(3) 또는 (*fp)(3) |
| 구조체 멤버 함수 포인터 | 구조체 안에 함수 포인터를 멤버로 두기 | struct { int (*fn)(int); } s; |
함수 포인터란? 기초
프로그램을 실행하면 함수 코드도 메모리 어딘가에 올라갑니다. 그 시작 위치에는 주소가 있고, C언어는 이 주소를 변수에 담을 수 있습니다. 이것이 함수 포인터(Function Pointer) 입니다.
포인터는 "주소를 저장하는 변수"입니다. 변수 포인터가 int 같은 값이 들어 있는 주소를 담듯, 함수 포인터는 함수 코드가 시작되는 주소를 담습니다.
처음 보면 int (*fp)(int) = add; 한 줄에 모르는 문법이 여러 개 겹쳐 있습니다. 아래에서 하나씩 분리해서 이해하고, 마지막에 합쳐서 읽겠습니다.
1단계: 함수 포인터 선언 — int (*fp)(int);
int를 받아 int를 돌려주는 함수의 주소를 담을 수 있는 포인터 fp를 선언합니다. 선언 문법의 세부 내용은 아래 섹션에서 자세히 다룹니다.
2단계: 함수 이름은 주소다 — add
add라는 함수 이름은 그 자체로 함수가 올라간 메모리 주소를 의미합니다. &add라고 써도 같은 주소이지만, 관습적으로 &를 생략합니다.
3단계: 포인터에 주소 저장 — fp = add;
변수 포인터에 변수 주소를 넣듯(int *p = &x;), 함수 포인터에 함수 주소를 넣습니다.
4단계: 저장된 주소로 호출 — fp(10)
fp에는 add의 주소가 들어 있습니다. fp(10)을 실행하면 C언어가 그 주소로 이동해 add(10)을 실행합니다. 결과는 add(10) → 10 + 1 → 11입니다.
함수 포인터에서 *는 두 가지 전혀 다른 역할로 등장합니다.
| 위치 | 코드 예 | *의 역할 |
|---|---|---|
| 선언부 | int (*fp)(int); | "fp는 포인터다"라는 표시 (타입 선언) |
| 호출부 | (*fp)(10) | fp가 가리키는 함수로 이동 (역참조1) |
(*fp)(10) 호출 방식은 "포인터가 가리키는 함수를 역참조해서 호출"하는 원형 표기입니다. 현대 C언어는 fp(10) 축약 표기도 허용하므로 두 방식이 동일합니다.
함수 포인터 선언 문법 기초
함수 포인터 선언은 처음 볼 때 복잡해 보이지만, 세 부분으로 나눠서 읽으면 쉽습니다.
| 위치 | 의미 |
|---|---|
int (맨 앞) | 이 함수가 반환하는 자료형 |
(*fp) | 이름은 fp, 그 앞의 *는 "포인터"라는 표시 |
(int) (맨 뒤) | 이 함수가 받는 매개변수 자료형 |
즉 int (*fp)(int);는 "int 하나를 받아서 int를 돌려주는 함수의 주소를 저장하는 포인터 fp" 를 뜻합니다.
괄호가 꼭 필요한 이유
(*fp) 의 괄호를 빼고 int *fp(int); 이라고 쓰면 완전히 다른 의미가 됩니다.
| 선언 | 해석 |
|---|---|
int (*fp)(int); | int를 받아 int를 돌려주는 함수의 포인터 |
int *fp(int); | int를 받아 int 포인터를 돌려주는 함수의 프로토타입 |
*를 먼저 fp에 묶어야 "fp는 포인터"가 되기 때문에 괄호 ( ) 가 필요합니다.
반환형이 포인터인 경우
반환형 자리에 int*처럼 포인터 타입을 쓸 수도 있습니다.
이 선언은 "int*를 받아 int*를 돌려주는 함수의 포인터 fp"입니다. 26년1회 기출 문제의 int* (*fn)(int*) 가 바로 이 형태입니다.
함수 포인터에 함수 할당 기초
선언된 함수 포인터에 실제 함수를 연결하려면 함수 이름을 대입합니다.
함수 이름은 변수와 달리 이름 자체가 주소입니다. 변수 포인터와 나란히 놓고 보면 차이가 명확합니다.
fp = add; 는 "fp에 add 함수의 시작 주소를 저장"하는 것이므로, fp = &add; 와 같은 의미입니다.
대입 시 두 함수의 매개변수 개수·위치별 타입·반환 타입이 세 가지 모두 일치해야 합니다. 하나라도 다르면 컴파일 경고(GCC) 또는 오류(MSVC)가 납니다.
함수 포인터로 함수 호출 기초
함수 포인터에 함수를 연결한 뒤에는 일반 함수처럼 호출합니다.
두 가지 호출 방식이 모두 허용됩니다.
| 표기 | 해석 |
|---|---|
fp(10) | "fp가 가리키는 함수에 10을 넘겨 호출" (권장) |
(*fp)(10) | "fp를 역참조한 함수에 10을 넘겨 호출" (원형) |
C 언어는 두 표기를 똑같이 처리하므로, 시험에서는 어느 쪽이 나와도 같은 의미로 읽으면 됩니다.
시험 출제 경향: 정보처리기사 실기에서는
(*fp)(인자)역참조 형태가 주로 출제됩니다. 두 방식이 동일하다는 것만 기억하면 어느 표기가 나와도 당황하지 않습니다.
구조체 멤버로 함수 포인터 두기 심화
구조체 안에도 함수 포인터를 멤버로 넣을 수 있습니다. 예를 들어 "학생 구조체에 점수를 계산하는 함수를 함께 담는다"처럼, 데이터(멤버 변수)와 행동(함수 포인터)을 하나의 구조체로 묶는 패턴입니다.
코드 해석 순서
struct fns { int* (*fn)(int*); } mine;}닫는 중괄호 바로 뒤에 변수명mine을 붙이면 구조체 타입 정의와 변수 선언을 한 줄에 할 수 있습니다.struct fns mine;을 따로 쓰는 것과 결과가 같습니다. 이 문법은 구조체 페이지의 즉시 변수 선언 섹션에서 자세히 다룹니다.mine.fn은 "int*를 받아int*를 돌려주는 함수"의 주소를 담을 수 있습니다.
mine.fn = dummy;dummy는int*를 받아int*를 돌려주는 함수이므로 시그니처가 일치합니다.- 이제
mine.fn을 호출하면dummy가 실행됩니다.
*mine.fn(n)- 호출 표기를 분해하면
*( mine.fn(n) )입니다. 뒤에서 자세히 다룹니다.
- 호출 표기를 분해하면
*mine.fn(n) 의 연산자 우선순위
함수 호출 () 는 역참조 * 보다 우선순위가 높습니다. 따라서 *mine.fn(n)은 다음 순서로 해석됩니다.
| 단계 | 수행 내용 | 결과 |
|---|---|---|
| 1 | mine.fn — 구조체 멤버로 저장된 함수 포인터를 꺼냄 | dummy 함수의 주소 |
| 2 | mine.fn(n) — 꺼낸 함수에 n을 넘겨 호출 (= dummy(n)) | n + 1 (배열 두 번째 요소의 주소) |
| 3 | *mine.fn(n) — 2단계의 반환값(주소)에 역참조 | n[1] = 32 |
2단계에서 n + 1이 두 번째 요소 주소인 이유: 배열 이름 n은 첫 번째 요소 n[0]의 주소입니다. int 포인터에 1을 더하면 다음 int 크기(4바이트)만큼 이동하므로 n + 1은 n[1]의 주소가 됩니다.
n[] = {16, 32} 이고 dummy(n) 이 n + 1을 돌려주므로, 역참조 결과는 두 번째 요소 32입니다.
printf("%x", 32) 는 32를 16진수로 출력합니다. 32를 16진수로 변환하면 32 ÷ 16 = 2 나머지 0이므로 20이 나옵니다. %x는 16진수를 소문자로 출력하는 서식 지정자입니다.
![구조체 멤버 함수 포인터 *mine.fn(n) 실행 흐름: mine.fn으로 dummy 함수 주소를 꺼내고, dummy(n)을 호출해 n+1(주소 104)을 반환받은 뒤, 역참조하여 n[1]=32를 얻어 %x로 20을 출력하는 3단계](/_next/image?url=%2Fcoding%2Fc-language%2Ffunction-pointer%2Fstruct-fn-execution-flow.webp&w=3840&q=75)
정보처리기사 실기 대비 문제 기초
Footnotes
-
역참조:
*를 사용해 포인터가 가리키는 주소로 이동해 그 내용에 접근하는 연산입니다. 변수 포인터에서는*p로 값을 읽고, 함수 포인터에서는(*fp)(인자)로 함수를 실행합니다. ↩