허상 참조
IT위키
더 많은 작업
허상 참조(Dangling Reference, 현수 참조)는 포인터나 참조가 이미 해제되었거나 수명이 끝난 기억 장소를 여전히 가리키고 있는 상태이다. 포인터일 때는 댕글링 포인터(허상 포인터)라고 한다.
참조 변수 자체는 살아 있는데 그 대상인 기억 장소는 시스템에 반납되었으므로, 그 참조로 읽으면 쓰레기 값이 나오고 쓰면 이미 다른 용도로 다시 할당된 데이터를 망가뜨린다. C·C++ 처럼 기억 장소 해제를 프로그래머가 직접 하는 언어에서 생긴다. 결과가 정해지지 않은 동작(undefined behavior)이라 바로 드러나지 않고 나중에 엉뚱한 곳에서 오류가 난다.
/* 1. 해제한 뒤에도 포인터를 쓴다 */
int *p = malloc(sizeof(int));
free(p);
*p = 10; /* p 는 허상 참조 */
/* 2. 지역 변수의 주소를 돌려준다 */
int *f(void) {
int x = 5;
return &x; /* 함수가 끝나면 x 의 기억 장소는 사라진다 */
}
/* 3. 별명 중 하나로 해제한다 */
int *a = malloc(sizeof(int));
int *b = a; /* a 와 b 는 별명 */
free(a); /* b 가 허상 참조가 된다 */
- 프로그램이 비정상 종료되거나 데이터가 조용히 망가진다
- 해제된 기억 장소를 공격자가 원하는 데이터로 다시 채우게 한 뒤 허상 포인터로 호출하게 만들면 임의 코드가 실행된다. 이것이 Use After Free 취약점이다
- 해제한 직후 포인터에 NULL 을 넣는다
- 지역 변수의 주소를 함수 밖으로 돌려주지 않는다
- C++ 의 스마트 포인터(unique_ptr, shared_ptr)로 수명을 자동 관리한다
- 가비지 컬렉션을 쓰는 언어(자바, 파이썬)는 참조가 남아 있는 객체를 해제하지 않으므로 허상 참조가 생기지 않는다
- Rust 는 컴파일할 때 빌림 검사기로 참조가 대상보다 오래 살지 못하게 막는다
- 언어 설계 기법으로 묘비(tombstone, 대상과 참조 사이에 중간 칸을 두고 해제 때 표시) 기법과 자물쇠와 열쇠(lock-and-key, 참조와 대상에 같은 값을 두고 접근 때 비교) 기법이 있다
| 구분 | 허상 참조 | 가비지 |
|---|---|---|
| 상태 | 참조는 있는데 기억 장소가 없다 | 기억 장소는 있는데 참조가 없다 |
| 원인 | 너무 일찍 해제 | 해제를 잊음, 참조를 덮어씀 |
| 결과 | 잘못된 읽기·쓰기, 보안 취약점 | 기억 장소 낭비, 메모리 누수 |