Phase 3 · 자료구조 및 실무 프로그래밍

고급 포인터와 함수 포인터 — C언어의 백미

C언어의 최종 보스 챕터입니다. 단순한 변수의 주소를 넘어 '포인터의 주소를 담는 이중 포인터'와 '함수의 주소를 담는 함수 포인터(Callback)'를 배움으로써, 다른 언어와 구별되는 C언어만의 강력한 메모리 제어 아키텍처를 완성합니다.

예상 학습 시간 : 150분
난이도 : ★★★★★ 고급
사전 지식 : 포인터 기초, 배열, 함수
다음 단계 : 비트 연산 (Step 18)
01

학습 목표

1
포인터 연산(ptr++)을 통해 배열과 문자열을 탐색하는 원리를 복습한다.
2
구조체 포인터에서 화살표 연산자(->)의 역할을 명확히 이해한다.
3
포인터 변수 자체의 주소를 담는 이중 포인터(**)의 메모리 구조를 그릴 수 있다.
4
포인터 배열배열 포인터의 괄호 유무에 따른 차이점을 식별한다.
5
함수의 주소를 저장하는 함수 포인터를 선언하고 호출할 수 있다.
6
함수 포인터를 인자로 넘기는 콜백(Callback) 패턴을 사용하여 동적 메뉴를 구성한다.
02

포인터 연산과 구조체 포인터

배열의 이름은 첫 번째 요소의 주소를 의미합니다. 따라서 numbers[0]*(numbers + 0)과 완벽히 동일합니다. 포인터에 1을 더하면 단순 숫자 1이 증가하는 것이 아니라 다음 자료형의 크기(ex: int 4바이트)만큼 메모리 주소가 점프합니다.

또한 구조체를 포인터로 가리킬 때는 (*ptr).age 대신 직관적인 ptr->age 화살표 연산자를 사용한다는 것을 기억하세요.

03

이중 포인터 (**) 의 세계

포인터도 결국 메모리 공간을 차지하는 '변수'입니다. 변수라면 당연히 주소(&ptr)가 있겠죠. "포인터 변수의 주소를 저장하는 또 다른 포인터"를 이중 포인터라고 부릅니다.

int **pptr
ptr의 주소 저장
int *ptr
num의 주소 저장
int num
100
⚠️ 이중 포인터는 언제 쓰는가?

STEP 15 연결 리스트에서 void addNode(struct Node **head) 라고 썼던 기억이 나시나요? main()에 있는 head 포인터 자체의 값을 함수 안에서 변경(새로운 노드 주소 덮어쓰기)하려면, head 포인터의 주소를 넘겨받아야 하기 때문에 이중 포인터를 쓰는 것입니다.

04

포인터 배열 vs 배열 포인터

괄호 하나 차이로 의미가 완전히 달라집니다. 특히 2차원 배열을 다룰 때 헷갈리기 쉬우므로 명확히 암기해야 합니다.

  • int *ptr[5]; : 포인터 배열. (int 포인터를 5개 담을 수 있는 배열)
  • int (*ptr)[5]; : 배열 포인터. (int가 5칸짜리인 통짜 배열 1개를 가리키는 포인터)
05

함수 포인터와 콜백 (Callback)

C언어에서는 함수의 이름도 메모리 주소입니다. 따라서 함수의 주소를 포인터 변수에 저장해두었다가 원할 때 호출할 수 있습니다. 이를 통해 if-else 떡칠 없이 동적으로 함수를 갈아 끼우는 설계가 가능해집니다.

🔄
Callback Pattern
함수 포인터 기초
⚙️ C
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

// 복잡한 함수 포인터 선언을 간소화 (반환형 int, 매개변수 int, int)
typedef int (*Operation)(int, int);

int main() {
    Operation op; 
    
    op = add; // 더하기 함수 장착!
    printf("결과: %d\n", op(10, 20)); // 30

    op = sub; // 빼기 함수로 갈아끼우기!
    printf("결과: %d\n", op(10, 20)); // -10
}
💡 qsort 의 비밀

C언어 표준 라이브러리의 강력한 정렬 함수인 qsort()는 4번째 인자로 오름차순/내림차순 정렬 로직이 담긴 비교 함수(compare)의 포인터를 넘겨받아 내부에서 호출(콜백)합니다. 데이터를 던져주면서 "이 비교 기준대로 처리해줘!" 라고 외주를 맡기는 셈입니다.

06

STEP 17 최종 프로젝트

🚀 Mission

함수 포인터 배열 기반 스마트 계산기

지저분한 switch-case 떡칠을 버리고, 함수 포인터 배열을 사용하여 단 한 줄의 코드로 원하는 연산 함수를 호출하는 실무형 메뉴 시스템을 만들어 보세요.

  • [1] add, subtract, multiply, divide 4개의 독립된 함수를 선언합니다.
  • [2] typedef int (*Operation)(int, int); 로 타입을 정의합니다.
  • [3] Operation ops[] = {add, subtract, multiply, divide}; 배열을 만듭니다.
  • [4] 사용자가 1(add)을 입력하면 ops[입력값-1](a, b) 를 호출하여 결과를 출력합니다.
07

STEP 17 완료 체크리스트

구조체 포인터에서 값을 꺼낼 때 사용하는 화살표(->) 연산자를 이해한다.
이중 포인터(**)가 주로 원본 포인터의 주소 자체를 함수 내에서 변경해야 할 때 쓰인다는 것을 이해한다.
포인터 배열(*ptr[5])과 배열 포인터((*ptr)[5])의 차이점을 설명할 수 있다.
C언어에서 함수의 이름도 메모리 주소라는 것을 깨닫고 함수 포인터를 선언할 수 있다.
함수 포인터 배열을 활용하여 if나 switch 없이 메뉴(라우터) 시스템을 깔끔하게 구현할 수 있다.
C언어 문법의 정점 도달!

축하합니다! 이중 포인터와 함수 포인터(콜백)를 이해했다면, 사실상 C언어 문법의 최상위 계층을 마스터한 것입니다. 특히 함수 포인터는 객체지향 프로그래밍(OOP)의 기초가 되며, 리눅스 커널이나 디바이스 드라이버 같은 시스템 코어 프로그래밍에서 필수적으로 사용되는 궁극기입니다. 다음 단계에서는 하드웨어 제어의 첫걸음, 비트 연산(Bitwise)으로 넘어갑니다.

이전 : Step 16 Step 18 : 비트 연산