“함수를 가리키는 포인터”. 실행 중(런타임)에 어떤 함수를 호출할지 선택할 수 있어 콜백, 전략 교체, 상태 머신, 플러그인 등에 유용합니다.
정적 바인딩: 어떤 함수가 호출될지 컴파일 타임에 고정.
동적 바인딩: 어떤 함수를 호출할지 런타임에 결정(함수 포인터가 핵심 수단).
흔한 오해 바로잡기
“주소값을 가져와 쓰기 때문에 실제 값이 변한다/원본을 참조한다”는 설명은 ‘데이터 포인터’에 해당합니다. 함수 포인터는 “함수의 주소”를 보유할 뿐 데이터를 직접 바꾸지 않습니다. 다만 호출 대상 함수를 바꿔 “동작 결과”를 바꿀 수는 있습니다.
“메모리를 줄이려고 쓴다”는 것도 주목적은 아닙니다. 주 목적은 유연성과 다형성(런타임 선택)이며, 간혹 테이블 주도 설계에서 분기 비용/테이블 크기와 관련된 트레이드오프가 있을 뿐입니다.
#include <stdio.h>
#include <stdlib.h>
/* 전략(Strategy) 함수들 */
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
/* 함수 포인터로 동적 바인딩 */
int apply(int (*op)(int,int), int x, int y) {
return op(x, y);
}
/* 표준 라이브러리 콜백(qsort) 예시 */
int cmp_int_asc(const void *a, const void *b) {
int x = *(const int*)a, y = *(const int*)b;
return (x > y) - (x < y);
}
int main() {
printf("%d\n", apply(add, 3, 5)); // 8
printf("%d\n", apply(sub, 3, 5)); // -2
int arr[] = {4,1,3,2};
qsort(arr, 4, sizeof(int), cmp_int_asc);
for (int i = 0; i < 4; i++) printf("%d ", arr[i]); // 1 2 3 4
return 0;
}
2) 구조체(struct) – “변수의 집”, 클래스 유사 추상화
개념/필요성
서로 연관된 여러 변수를 하나의 타입으로 묶어 가독성과 모듈성을 높입니다.
직접 접근: . / 간접 접근(포인터): ->
C에는 class가 없지만, struct + 함수(필요시 함수 포인터)로 “클래스 비슷한 추상화”가 가능합니다.
#include <stdio.h>
typedef struct {
int x, y;
} Point;
void move(Point *p, int dx, int dy) { // 간접 접근
p->x += dx;
p->y += dy;
}
int main() {
Point p = { .x = 10, .y = 20 }; // 직접 접근
printf("(%d,%d)\n", p.x, p.y);
move(&p, 5, -3);
printf("(%d,%d)\n", p.x, p.y);
return 0;
}
\
3) 공용체(union)
개념/필요성
여러 필드가 동일한 메모리 공간을 공유합니다. 가장 큰 필드의 크기만큼만 메모리를 차지하여 메모리를 절약합니다.
#include <stdio.h>
typedef enum { T_INT, T_FLOAT } Type;
typedef union {
int i;
float f;
} Value;
typedef struct {
Type type;
Value val;
} Number;
void print_number(const Number *n) {
if (n->type == T_INT) printf("int: %d\n", n->val.i);
else printf("float: %.2f\n", n->val.f);
}
int main() {
Number a = { T_INT, .val.i = 42 };
Number b = { T_FLOAT, .val.f = 3.14f };
print_number(&a);
print_number(&b);
return 0;
}
4) 열거형(enum) – “데이터들을 열거한 집합”
개념/필요성
의미 있는 이름의 정수 상수 집합으로 가독성 향상, switch와 궁합이 좋습니다.
비트 플래그에도 활용 가능합니다.
#include <stdio.h>
typedef enum { RED = 1, GREEN = 2, BLUE = 3 } Color;
void paint(Color c) {
switch (c) {
case RED: puts("RED"); break;
case GREEN: puts("GREEN"); break;
case BLUE: puts("BLUE"); break;
default: puts("Unknown");
}
}
/* 비트 플래그 예시 */
enum {
PERM_READ = 1 << 0,
PERM_WRITE = 1 << 1,
PERM_EXEC = 1 << 2
};
int main() {
paint(GREEN);
int perm = PERM_READ | PERM_EXEC;
if (perm & PERM_EXEC) puts("can exec");
return 0;
}
5) 메모리 영역(코드/데이터/힙/스택)
개념/필요성
코드(텍스트) 영역: 기계어 코드
데이터 영역: 전역/정적 변수
힙: 동적 할당 영역(malloc 등)
스택: 지역 변수, 호출 프레임(자동 수명)
#include <stdio.h>
#include <stdlib.h>
int g = 10; // 데이터 영역
const char *lit = "hi"; // 보통 읽기 전용(텍스트/RODATA)
int main() {
static int s = 20; // 데이터 영역(정적 저장)
int local = 30; // 스택
int *heap = malloc(sizeof(int)); // 힙
*heap = 40;
printf("code(main): %p\n", (void*)&main);
printf("global g : %p\n", (void*)&g);
printf("static s : %p\n", (void*)&s);
printf("literal : %p\n", (const void*)lit);
printf("stack var : %p\n", (void*)&local);
printf("heap ptr : %p\n", (void*)heap);
free(heap);
return 0;
}
6) 동적 메모리 할당/해제
개념/필요성
런타임에 크기를 결정하는 데이터 관리에 필수. malloc/calloc/realloc/free 사용.
체크리스트: NULL 검사, 소유권/수명 관리, 이중 해제 방지, realloc 실패 대비(임시 포인터), free 후 포인터 NULL.
#include <stdio.h>
#include <stdlib.h>
int main() {
size_t n = 5;
int *a = malloc(n * sizeof(int));
if (!a) return 1;
for (size_t i = 0; i < n; i++) a[i] = (int)i;
size_t m = 10;
int *tmp = realloc(a, m * sizeof(int)); // 실패 대비 임시 포인터
if (!tmp) { free(a); return 1; }
a = tmp;
for (size_t i = n; i < m; i++) a[i] = (int)i;
for (size_t i = 0; i < m; i++) printf("%d ", a[i]);
puts("");
free(a); a = NULL;
return 0;
}
7) 객체지향 철학 vs 구조적 프로그래밍 vs 폭포수 모델
개념/필요성
구조적(절차적) 프로그래밍: 순차/분기/반복, 함수 분해로 복잡도 관리.
객체지향 프로그래밍(OOP): 캡슐화, 상속, 다형성으로 모델링. C는 직접 지원 X → struct + 함수 포인터(vtable)로 유사 구현.
폭포수 모델: 개발 프로세스(요구→설계→구현→테스트→배포)를 단계적으로 진행하는 방법론. 언어/패러다임과는 별개의 개념.