C와 C++에서 포인터는 메모리를 직접 다루는 강력한 도구이지만, 잘못 사용하면 심각한 버그의 원인이 됩니다. 이 글에서는 댕글링 포인터(Dangling Pointer), 보이드 포인터(Void Pointer), 널 포인터(Null Pointer), 와일드 포인터(Wild Pointer)의 개념과 발생 원인, 그리고 실제 코드 예제를 통해 안전한 포인터 사용법을 알아봅니다.
1. 댕글링 포인터(Dangling Pointer)란?
댕글링 포인터란 이미 해제되었거나 삭제된 메모리 위치를 가리키는 포인터를 말합니다. 해당 메모리에는 더 이상 유효한 데이터가 없으므로, 이런 포인터에 접근하면 예측할 수 없는 동작이나 프로그램 충돌이 발생할 수 있습니다.
포인터가 댕글링 포인터가 되는 대표적인 상황은 다음 세 가지입니다.
1-1. 함수 호출 시 지역 변수 반환
함수 내부의 지역 변수가 static이 아니라면, 함수가 종료되는 순간 해당 변수의 메모리는 소멸합니다. 따라서 지역 변수의 주소를 반환하는 포인터는 댕글링 포인터가 됩니다.
int *show(void) {
int n = 76; /* ... */
return &n; // 위험! n은 함수 종료 후 소멸됨
}
출력 결과:
이 프로그램은 유효하지 않은 가비지(garbage) 주소를 반환합니다.
1-2. 메모리 해제(free) 후
동적으로 할당한 메모리를 free()로 해제한 뒤에도 포인터는 여전히 그 주소를 가리키고 있어 댕글링 포인터가 됩니다. 해제 직후 포인터에 NULL을 대입하면 이 문제를 예방할 수 있습니다.
#include <stdlib.h>
#include <stdio.h>
int main() {
float *p = (float *)malloc(sizeof(float)); // 동적 메모리 할당
free(p); // free() 호출 후 p는 댕글링 포인터가 됨
p = NULL; // NULL을 대입하면 더 이상 댕글링 포인터가 아님
return 0;
}
1-3. 변수가 스코프(scope)를 벗어날 때
블록({ }) 내부에서 선언된 변수의 주소를 외부 포인터에 저장하면, 블록이 끝나는 순간 변수가 소멸하여 포인터가 댕글링 상태가 됩니다.
int main() {
int *p;
// 일부 코드...
{
int c;
p = &c; // 블록 내부 지역 변수의 주소 저장
} // 여기서 c가 소멸
// 일부 코드...
// 이 시점부터 p는 댕글링 포인터!
return 0;
}
2. 보이드 포인터(Void Pointer)란?
C 언어에서 void 포인터는 특정 자료형과 연관되지 않은 포인터입니다. 어떤 자료형의 변수 주소든 가리킬 수 있어 범용 포인터(general purpose pointer)라고도 불립니다.
주요 제약 사항
- 포인터 연산 불가: void 포인터는 가리키는 대상의 크기가 정해져 있지 않아 증감 연산 등 포인터 산술을 사용할 수 없습니다.
- 직접 역참조(dereference) 불가: 값을 읽거나 쓰려면 반드시 적절한 자료형으로 형 변환(casting)해야 합니다.
예제 코드
#include <stdlib.h>
#include <stdio.h>
int main() {
int a = 7;
float b = 7.6;
void *p;
p = &a;
printf("정수형 변수 = %d", *((int*) p)); // int*로 형 변환 후 역참조
p = &b;
printf("\n실수형 변수 = %f", *((float*) p)); // float*로 형 변환 후 역참조
return 0;
}
출력 결과:
정수형 변수 = 7 실수형 변수 = 7.600000
동작 원리(알고리즘)
- 정수형 변수
a와 실수형 변수b를 각각 초기화합니다. - void 포인터
p를 선언합니다. p = &a;→ void 포인터가 정수형 변수의 주소를 가리킵니다.(int*)p→ void 타입을 int 포인터로 형 변환하여 값을 출력합니다.p = &b;→ 같은 포인터가 이번에는 실수형 변수를 가리킵니다.(float*)p→ float 포인터로 형 변환하여 값을 출력합니다.
3. 널 포인터(Null Pointer)란?
널 포인터는 아무것도 가리키지 않는 포인터입니다. 유효하지 않은 메모리 접근을 방지하기 위한 안전장치로 널리 사용됩니다.
널 포인터의 주요 용도
- 초기화: 포인터 변수에 아직 유효한 메모리 주소를 할당하지 않았을 때
NULL로 초기화합니다. - 함수 인자 전달: 유효한 메모리 주소를 넘기고 싶지 않을 때 함수 인자로
NULL을 전달합니다. - 오류 처리: 포인터를 사용하기 전에
NULL여부를 검사하여, NULL이 아닐 때만 역참조하도록 안전한 코드를 작성할 수 있습니다.
예제 코드
#include <iostream>
#include <stdio.h>
int main() {
int *p = NULL; // 포인터를 NULL로 초기화
printf("포인터의 값 = %u", p);
return 0;
}
출력 결과:
포인터의 값 = 0
4. 와일드 포인터(Wild Pointer)란?
와일드 포인터는 초기화되지 않은 채 임의의 메모리 위치를 가리키는 포인터입니다. NULL조차 아니기 때문에 어디를 가리키는지 알 수 없으며, 역참조 시 프로그램 오류나 데이터 손상을 일으킬 수 있습니다.
int main() {
int *ptr; // 초기화되지 않은 와일드 포인터
*ptr = 5; // 매우 위험! 알 수 없는 메모리에 값 대입
return 0;
}
마무리: 안전한 포인터 사용 습관
네 가지 포인터 유형을 정리하면 다음과 같습니다.
- 댕글링 포인터: 해제되었거나 소멸된 메모리를 가리킴 → 해제 후
NULL대입으로 예방 - void 포인터: 자료형에 독립적인 범용 포인터 → 사용 시 반드시 형 변환 필요
- 널 포인터: 아무것도 가리키지 않음 → 초기화 및 오류 검사에 활용
- 와일드 포인터: 초기화되지 않은 포인터 → 선언 즉시
NULL또는 유효한 주소로 초기화 필수
포인터를 선언할 때 항상 초기화하고, 메모리를 해제한 후에는 포인터를 NULL로 설정하며, 사용 전에 유효성을 검사하는 습관을 들이면 대부분의 포인터 관련 오류를 예방할 수 있습니다.