1. 더블 포인터 (이중 포인터)
더블 포인터는 포인터 변수를 가리키는 포인터입니다. 포인터도 변수이므로 주소를 가지며, 이 주소를 저장할 수 있습니다.
더블 포인터의 개념
일반 포인터가 변수의 주소를 저장한다면, 더블 포인터는 포인터 변수의 주소를 저장합니다.
int num = 10;
int *ptr = # // 싱글 포인터: num의 주소 저장
int **dptr = &ptr; // 더블 포인터: ptr의 주소 저장
메모리 구조:
num의 값 ptr의 값 dptr의 값
10 ← num의 주소 ← ptr의 주소
num ptr dptr
더블 포인터 선언
• 애스터리스크(*) 2개 사용
• "포인터의 포인터"를 의미
int **dptr;• 애스터리스크(*) 2개 사용
• "포인터의 포인터"를 의미
더블 포인터의 사용
#include <stdio.h>
int main(void) {
int num = 100;
int *ptr = # // num의 주소
int **dptr = &ptr; // ptr의 주소
printf("=== 값 출력 ===\n");
printf("num: %d\n", num);
printf("*ptr: %d\n", *ptr);
printf("**dptr: %d\n", **dptr);
printf("\n=== 주소 출력 ===\n");
printf("&num: %p\n", &num);
printf("ptr: %p\n", ptr);
printf("*dptr: %p\n", *dptr);
printf("\n&ptr: %p\n", &ptr);
printf("dptr: %p\n", dptr);
return 0;
}
실행 결과 보기 (예시)
=== 값 출력 === num: 100 *ptr: 100 **dptr: 100 === 주소 출력 === &num: 00B3FA14 ptr: 00B3FA14 *dptr: 00B3FA14 &ptr: 00B3FA08 dptr: 00B3FA08
**dptr은 최종적으로 num의 값(100)에 접근*dptr은 ptr이 저장한 주소(num의 주소)dptr은 ptr의 주소
더블 포인터로 값 변경하기
#include <stdio.h>
int main(void) {
int num = 50;
int *ptr = #
int **dptr = &ptr;
printf("변경 전: %d\n", num);
**dptr = 999; // num = 999와 동일
printf("변경 후: %d\n", num);
printf("*ptr: %d\n", *ptr);
printf("**dptr: %d\n", **dptr);
return 0;
}
실행 결과 보기
변경 전: 50 변경 후: 999 *ptr: 999 **dptr: 999
💡 더블 포인터 접근 방식
•
•
•
•
dptr: ptr의 주소•
*dptr: ptr이 저장한 값 (num의 주소)•
**dptr: num의 값
2. 더블 포인터와 함수
더블 포인터는 함수에서 포인터 자체를 변경할 때 유용합니다.
포인터 교환 함수
싱글 포인터만 사용하면 포인터가 가리키는 값만 교환할 수 있지만, 더블 포인터를 사용하면 포인터 자체를 교환할 수 있습니다.
#include <stdio.h>
void swapPointer(int **dptr1, int **dptr2) {
int *temp = *dptr1;
*dptr1 = *dptr2;
*dptr2 = temp;
}
int main(void) {
int num1 = 10, num2 = 20;
int *ptr1 = &num1;
int *ptr2 = &num2;
printf("=== 교환 전 ===\n");
printf("*ptr1: %d, *ptr2: %d\n", *ptr1, *ptr2);
swapPointer(&ptr1, &ptr2); // 포인터 자체를 교환
printf("\n=== 교환 후 ===\n");
printf("*ptr1: %d, *ptr2: %d\n", *ptr1, *ptr2);
printf("num1: %d, num2: %d\n", num1, num2);
return 0;
}
실행 결과 보기
=== 교환 전 === *ptr1: 10, *ptr2: 20 === 교환 후 === *ptr1: 20, *ptr2: 10 num1: 10, num2: 20
num1과 num2의 값은 그대로지만, ptr1과 ptr2가 가리키는 대상이 바뀌었습니다.
메모리 상태 변화:
[교환 전]
ptr1 → num1(10)
ptr2 → num2(20)
[교환 후]
ptr1 → num2(20)
ptr2 → num1(10)
더블 포인터로 배열 접근
#include <stdio.h>
void printArray(int **dptr, int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d ", *(*dptr + i));
}
printf("\n");
}
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;
int **dptr = &ptr;
printf("배열 출력: ");
printArray(dptr, 5);
return 0;
}
실행 결과 보기
배열 출력: 10 20 30 40 50
3. 다중 포인터 (삼중 포인터)
포인터는 이론적으로 무한대로 중첩할 수 있습니다. 실무에서는 드물지만, 개념 이해를 위해 삼중 포인터를 살펴보겠습니다.
삼중 포인터의 선언
int num = 100;
int *ptr = # // 싱글 포인터
int **dptr = &ptr; // 더블 포인터
int ***tptr = &dptr; // 삼중 포인터
관계도:
num의 값 ptr의 값 dptr의 값 tptr의 값
100 ← num의 주소 ← ptr의 주소 ← dptr의 주소
num ptr dptr tptr
삼중 포인터 예제
#include <stdio.h>
int main(void) {
int num = 777;
int *ptr = #
int **dptr = &ptr;
int ***tptr = &dptr;
printf("=== 값 출력 ===\n");
printf("num: %d\n", num);
printf("*ptr: %d\n", *ptr);
printf("**dptr: %d\n", **dptr);
printf("***tptr: %d\n", ***tptr);
// 삼중 포인터로 값 변경
***tptr = 999;
printf("\n=== 변경 후 ===\n");
printf("num: %d\n", num);
return 0;
}
실행 결과 보기
=== 값 출력 === num: 777 *ptr: 777 **dptr: 777 ***tptr: 777 === 변경 후 === num: 999
⚠️ 다중 포인터 주의사항
• 실무에서는 삼중 포인터 이상은 거의 사용하지 않음
• 코드 가독성이 크게 떨어짐
• 더블 포인터까지만 이해해도 충분
• 실무에서는 삼중 포인터 이상은 거의 사용하지 않음
• 코드 가독성이 크게 떨어짐
• 더블 포인터까지만 이해해도 충분
4. 함수 포인터
함수도 메모리에 저장되므로 주소를 가집니다. 함수 포인터는 함수의 주소를 저장하는 포인터입니다.
함수 포인터의 필요성
함수를 변수처럼 다루어 동적으로 호출하거나, 함수를 다른 함수에 전달할 수 있습니다.
함수 포인터 선언
// 함수 정의
int add(int a, int b) {
return a + b;
}
// 함수 포인터 선언
int (*funcPtr)(int, int);
// 함수 주소 저장
funcPtr = add;
함수 포인터 선언 형식
• 반환형과 매개변수 타입이 일치해야 함
• 괄호
반환형 (*포인터이름)(매개변수타입1, 매개변수타입2, ...);• 반환형과 매개변수 타입이 일치해야 함
• 괄호
(*포인터이름) 필수
함수 포인터 사용 예제
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int main(void) {
int (*funcPtr)(int, int); // 함수 포인터 선언
// 덧셈 함수 호출
funcPtr = add;
printf("10 + 5 = %d\n", funcPtr(10, 5));
// 뺄셈 함수 호출
funcPtr = subtract;
printf("10 - 5 = %d\n", funcPtr(10, 5));
// 곱셈 함수 호출
funcPtr = multiply;
printf("10 × 5 = %d\n", funcPtr(10, 5));
return 0;
}
실행 결과 보기
10 + 5 = 15 10 - 5 = 5 10 × 5 = 50
함수 포인터 배열
여러 함수를 배열로 관리할 수 있습니다.
#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(void) {
int (*calc[4])(int, int) = {add, sub, mul, div};
char *op[4] = {"+", "-", "×", "÷"};
int i;
printf("=== 계산기 ===\n");
for (i = 0; i < 4; i++) {
printf("20 %s 4 = %d\n", op[i], calc[i](20, 4));
}
return 0;
}
실행 결과 보기
=== 계산기 === 20 + 4 = 24 20 - 4 = 16 20 × 4 = 80 20 ÷ 4 = 5
함수 포인터를 매개변수로 전달
콜백 함수(Callback Function) 패턴에 활용됩니다.
#include <stdio.h>
void processArray(int arr[], int size, int (*func)(int)) {
int i;
for (i = 0; i < size; i++) {
arr[i] = func(arr[i]);
}
}
int square(int n) {
return n * n;
}
int triple(int n) {
return n * 3;
}
int main(void) {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5] = {1, 2, 3, 4, 5};
int i;
processArray(arr1, 5, square);
processArray(arr2, 5, triple);
printf("제곱: ");
for (i = 0; i < 5; i++) printf("%d ", arr1[i]);
printf("\n3배: ");
for (i = 0; i < 5; i++) printf("%d ", arr2[i]);
printf("\n");
return 0;
}
실행 결과 보기
제곱: 1 4 9 16 25 3배: 3 6 9 12 15
5. void 포인터
void 포인터는 자료형이 정해지지 않은 포인터입니다. 어떤 타입의 주소든 저장할 수 있습니다.
void 포인터의 특징
void *ptr; // void 포인터 선언
- 모든 타입의 주소를 저장 가능
- 포인터 연산 불가
- 역참조 전에 형 변환 필요
void 포인터 사용 예제
#include <stdio.h>
int main(void) {
int num = 100;
double pi = 3.14;
char ch = 'A';
void *ptr; // void 포인터 선언
// int형 주소 저장
ptr = #
printf("int: %d\n", *(int *)ptr);
// double형 주소 저장
ptr = π
printf("double: %.2f\n", *(double *)ptr);
// char형 주소 저장
ptr = &ch;
printf("char: %c\n", *(char *)ptr);
return 0;
}
실행 결과 보기
int: 100 double: 3.14 char: A
💡 void 포인터의 활용
• 범용 함수 구현 (예: qsort, memcpy)
• 동적 메모리 할당 (malloc의 반환 타입)
• 다양한 타입을 처리하는 구조체
• 범용 함수 구현 (예: qsort, memcpy)
• 동적 메모리 할당 (malloc의 반환 타입)
• 다양한 타입을 처리하는 구조체
6. 종합 실습
문제 1 - 더블 포인터 기초 (기초)
문제 1
다음 코드의 실행 결과는?
포인터의 심화
#include <stdio.h>
int main(void) {
int num = 50;
int *ptr = #
int **dptr = &ptr;
**dptr += 10;
printf("%d", num);
return 0;
}
문제 2 - 포인터 교환 (중급)
문제 2
다음 코드의 실행 결과는?
포인터의 심화
#include <stdio.h>
void swap(int **a, int **b) {
int *temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 10, y = 20;
int *p1 = &x;
int *p2 = &y;
swap(&p1, &p2);
printf("%d %d", *p1, *p2);
return 0;
}
문제 3 - 함수 포인터 (기초)
문제 3
다음 코드의 실행 결과는?
포인터의 심화
#include <stdio.h>
int calc(int a, int b) {
return a * b + 5;
}
int main(void) {
int (*fp)(int, int);
fp = calc;
printf("%d", fp(3, 4));
return 0;
}
문제 4 - 삼중 포인터 (중급)
문제 4
다음 코드의 실행 결과는?
포인터의 심화
#include <stdio.h>
int main(void) {
int num = 100;
int *p = #
int **dp = &p;
int ***tp = &dp;
***tp = 500;
printf("%d", num);
return 0;
}
문제 5 - 함수 포인터 배열 (고급)
문제 5
다음 코드의 실행 결과는?
포인터의 심화
func[0](5, 3)은 add(5, 3) = 8, func[1](4, 2)는 mul(4, 2) = 8
#include <stdio.h>
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int main(void) {
int (*func[2])(int, int) = {add, mul};
int result = func[0](5, 3) + func[1](4, 2);
printf("%d", result);
return 0;
}
문제 6 - void 포인터 (중급)
문제 6
void 포인터에 대한 설명으로 올바른 것은?
A. void 포인터는 포인터 연산이 가능하다.
B. void 포인터는 역참조할 때 형 변환이 필요하다.
C. void 포인터는 int형 주소만 저장할 수 있다.
D. void 포인터는 함수 포인터로 사용할 수 없다.
포인터의 심화
핵심 요약
1. 더블 포인터
• 선언:
• 포인터 변수의 주소를 저장
•
• 포인터 자체를 변경할 때 유용
2. 다중 포인터
• 삼중 포인터:
• 실무에서는 거의 사용 안 함
• 개념 이해 목적으로 학습
3. 함수 포인터
• 선언:
• 함수의 주소를 저장
• 함수를 동적으로 호출 가능
• 콜백 함수 구현에 활용
4. void 포인터
• 선언:
• 모든 타입의 주소 저장 가능
• 역참조 시 형 변환 필수
• 포인터 연산 불가
5. 활용 패턴
• 더블 포인터: Call-by-reference로 포인터 변경
• 함수 포인터: 범용 함수, 콜백 구현
• void 포인터: malloc, 범용 자료구조
• 선언:
int **dptr;• 포인터 변수의 주소를 저장
•
**dptr로 최종 값에 접근• 포인터 자체를 변경할 때 유용
2. 다중 포인터
• 삼중 포인터:
int ***tptr;• 실무에서는 거의 사용 안 함
• 개념 이해 목적으로 학습
3. 함수 포인터
• 선언:
반환형 (*이름)(매개변수...);• 함수의 주소를 저장
• 함수를 동적으로 호출 가능
• 콜백 함수 구현에 활용
4. void 포인터
• 선언:
void *ptr;• 모든 타입의 주소 저장 가능
• 역참조 시 형 변환 필수
• 포인터 연산 불가
5. 활용 패턴
• 더블 포인터: Call-by-reference로 포인터 변경
• 함수 포인터: 범용 함수, 콜백 구현
• void 포인터: malloc, 범용 자료구조
PREVIOUS3. 메모리 구조와 변수의 생명주기
NEXT5. 동적 메모리 할당