고급 포인터와 함수 포인터 — C언어의 백미
C언어의 최종 보스 챕터입니다. 단순한 변수의 주소를 넘어 '포인터의 주소를 담는 이중 포인터'와 '함수의 주소를 담는 함수 포인터(Callback)'를 배움으로써, 다른 언어와 구별되는 C언어만의 강력한 메모리 제어 아키텍처를 완성합니다.
학습 목표
ptr++)을 통해 배열과 문자열을 탐색하는 원리를 복습한다.->)의 역할을 명확히 이해한다.**)의 메모리 구조를 그릴 수 있다.포인터 연산과 구조체 포인터
배열의 이름은 첫 번째 요소의 주소를 의미합니다. 따라서 numbers[0]은 *(numbers + 0)과 완벽히 동일합니다. 포인터에 1을 더하면 단순 숫자 1이 증가하는 것이 아니라 다음 자료형의 크기(ex: int 4바이트)만큼 메모리 주소가 점프합니다.
또한 구조체를 포인터로 가리킬 때는 (*ptr).age 대신 직관적인 ptr->age 화살표 연산자를 사용한다는 것을 기억하세요.
이중 포인터 (**) 의 세계
포인터도 결국 메모리 공간을 차지하는 '변수'입니다. 변수라면 당연히 주소(&ptr)가 있겠죠. "포인터 변수의 주소를 저장하는 또 다른 포인터"를 이중 포인터라고 부릅니다.
STEP 15 연결 리스트에서 void addNode(struct Node **head) 라고 썼던 기억이 나시나요? main()에 있는 head 포인터 자체의 값을 함수 안에서 변경(새로운 노드 주소 덮어쓰기)하려면, head 포인터의 주소를 넘겨받아야 하기 때문에 이중 포인터를 쓰는 것입니다.
포인터 배열 vs 배열 포인터
괄호 하나 차이로 의미가 완전히 달라집니다. 특히 2차원 배열을 다룰 때 헷갈리기 쉬우므로 명확히 암기해야 합니다.
int *ptr[5];: 포인터 배열. (int 포인터를 5개 담을 수 있는 배열)int (*ptr)[5];: 배열 포인터. (int가 5칸짜리인 통짜 배열 1개를 가리키는 포인터)
함수 포인터와 콜백 (Callback)
C언어에서는 함수의 이름도 메모리 주소입니다. 따라서 함수의 주소를 포인터 변수에 저장해두었다가 원할 때 호출할 수 있습니다. 이를 통해 if-else 떡칠 없이 동적으로 함수를 갈아 끼우는 설계가 가능해집니다.
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 }
C언어 표준 라이브러리의 강력한 정렬 함수인 qsort()는 4번째 인자로 오름차순/내림차순 정렬 로직이 담긴 비교 함수(compare)의 포인터를 넘겨받아 내부에서 호출(콜백)합니다. 데이터를 던져주면서 "이 비교 기준대로 처리해줘!" 라고 외주를 맡기는 셈입니다.
STEP 17 최종 프로젝트
함수 포인터 배열 기반 스마트 계산기
지저분한 switch-case 떡칠을 버리고, 함수 포인터 배열을 사용하여 단 한 줄의 코드로 원하는 연산 함수를 호출하는 실무형 메뉴 시스템을 만들어 보세요.
- [1]
add, subtract, multiply, divide4개의 독립된 함수를 선언합니다. - [2]
typedef int (*Operation)(int, int);로 타입을 정의합니다. - [3]
Operation ops[] = {add, subtract, multiply, divide};배열을 만듭니다. - [4] 사용자가 1(add)을 입력하면
ops[입력값-1](a, b)를 호출하여 결과를 출력합니다.
STEP 17 완료 체크리스트
축하합니다! 이중 포인터와 함수 포인터(콜백)를 이해했다면, 사실상 C언어 문법의 최상위 계층을 마스터한 것입니다. 특히 함수 포인터는 객체지향 프로그래밍(OOP)의 기초가 되며, 리눅스 커널이나 디바이스 드라이버 같은 시스템 코어 프로그래밍에서 필수적으로 사용되는 궁극기입니다. 다음 단계에서는 하드웨어 제어의 첫걸음, 비트 연산(Bitwise)으로 넘어갑니다.