-
[번역/요약] C언어 메모리 부족(OOM) 상황 처리 방법Programming/번역&요약 2024. 11. 24. 19:18
Eli Bendersky의 'Handling Out of Memory Conditions in C' 글을 번역하고 정리했습니다. C언어에서 malloc이 실패하는 OOM 상황의 세 가지 처리 정책(복구/중단/Segfault)과 Glib, SQLite, Git 등 실제 프로젝트들의 구현 사례를 살펴봅니다."
원문: Handling Out of Memory Conditions in C
서론
malloc
함수의 반환값 0(NULL)은 메모리 할당 실패(Out of Memory, OOM)를 의미함- 애플리케이션은 이를 발견하고 적절하게 "처리"해야 하나, 어떻게 처리해야 하는지는 명확하지 않음
- 글은 실제 자주 사용되는 OOM 처리 방법들을 분석하고 통찰을 제공하고자 함
- OOM 처리 방법에 "정답"이란 없으며, 글은 임베디드가 아닌 데스크탑/서버 환경의 프로그램에 초점을 둠
OOM 처리 방법
OOM 처리 방법에는 크게 세 가지 처리 정책이 있음
- 복구(Recover) 정책
- 중단(Abort) 정책
- Segmentation Fault 정책
복구(Recover) 정책
- 도메인에 특화된 구현이 필요하고 구현 방법이 어려워 자주 사용되지는 않음
- 프로그램 복구란 주로 다음 중 하나를 의미함
- 메모리 자원 해제 후 재시도
- 사용자 작업 저장 후 종료
- 임시 리소스 정리 후 종료
- 구현이 어려운 이유는 다음과 같음
- 추가 메모리 할당 없이 복구 과정을 수행해야 함
- C언어는 예외 처리(Exception) 기능이 없어 오류 전파가 복잡하고 번거로움
중단(Abort) 정책
- 가장 일반적으로 사용하는 방식
- OOM 발생 시 에러 메시지만 출력하고 프로그램 종료
- 반환 값 검사(
if (ptr == NULL)
)가 필요 없어 코드가 간결해짐gnulib
의xmalloc
:malloc
함수가 실패(OOM 발생) 시 프로그램을 종료하는 래퍼 함수(wrapper function)
xmalloc
코드void *xmalloc(size_t size) { void *p = malloc(size); if (!p && size != 0) { xalloc_die(); // Abort } return p; }
Segmentation Fault 정책
- 아무 것도 하지 않는 가장 단순한 방식
- OOM으로 인해 생긴 NULL 포인터는 역참조 시 Segmentation Fault를 만들며 프로그램을 종료
- 어차피 프로그램이 종료될 것이므로 OOM 처리를 할 필요가 없다는 이유로 사용
- 에러 메시지 대신 코어 덤프를 활용해 문제 발생 시점을 파악
주요 라이브러리 사례 분석
Glib
- Glib: 크로스 플랫폼 유틸리티 라이브러리
- 두 개의 메모리 할당 함수(와 그 변형)를 제공, 프로그래머가 적절하게 선택할 수 있음
g_malloc
: OOM 발생 시 프로그램 종료 (중단 정책)g_try_malloc
: OOM 발생 시 NULL 반환 (복구 정책)
- 라이브러리 내부적으로는 주로 중단 정책을 사용함
SQLite
- SQLite: 내장형 경량 데이터베이스
- 다양한 메모리 관리 옵션 사용
- 일반
malloc
- 미리 메모리를 할당해 둔 정적 버퍼
- 디버깅용 메모리 할당자 (메모리 누수 및 오버플로우 검사 기능)
- 사용자 정의 메모리 할당자 지원
- 일반
- 기본적으로는 복구 정책을 사용
- 메모리 할당 크기를 할당한 블록의 맨 앞에 저장
- 따라서 NULL 포인터는 메모리 할당 크기가 0인 블럭으로 해석됨
- OOM이 발생해도 포인터를 그대로 반환
- 메모리 할당 크기를 할당한 블록의 맨 앞에 저장
SQLite의
sqlite3MemMalloc
코드static void *sqlite3MemMalloc(int nByte) { sqlite3_int64 *p; assert(nByte > 0); nByte = ROUND8(nByte); p = malloc(nByte + 8); if (p) { p[0] = nByte; // Size of the allocation p++; } return (void *)p; }
주요 애플리케이션 사례 분석
Git
- Git: 버전 관리 시스템
- 래퍼 함수
xmalloc
정의해 사용- OOM 발생 시 메모리 자원 해제 후 재시도 (복구 정책)
- 재시도 후에도 OOM 발생 시 프로그램 중단 (중단 정책)
Git의
xmalloc
코드void *xmalloc(size_t size) { void *ret = malloc(size); if (!ret && !size) { ret = malloc(1); } if (!ret) { release_pack_memory(size, -1); ret = malloc(size); if (!ret && !size) { ret = malloc(1); } if (!ret) { die("Out of memory, malloc failed"); } } #ifdef XMALLOC_POISON memset(ret, 0xA5, size); #endif return ret; }
Lighttpd
- Lighttpd: 초경량 웹 서버
- OOM 체크 없음, Segmentation Fault 정책 사용
Redis
- Redis: 키-값 데이터베이스
zmalloc
함수 사용- OOM 발생 시 NULL 반환, 애플리케이션에 처리 위임
- 애플리케이션은 OOM 발생 시 프로그램 중단 (중단 정책)
- 네트워크 레이어를 통해 클라이언트에게 에러 메시지를 전송하려면 메모리 할당이 필요
- 따라서 OOM 발생 시에는 전송이 불가능하므로 그냥 프로그램을 종료하는 것이 낫다고 판단
- 네트워크 레이어를 통해 클라이언트에게 에러 메시지를 전송하려면 메모리 할당이 필요
결론
- 코드 작성 시 어떤 OOM 처리 방법을 사용할지는 프로그램의 특성과 사용자 기대에 따라 다름
- 라이브러리 개발 시
- 라이브러리가 프로그램 동작을 강제해서는 안 되므로 복구 정책을 사용하는 것이 바람직함
- 가능하면 사용자 정의 메모리 할당자/에러 핸들러를 제공하면 좋음.
- 애플리케이션 개발 시
- 고신뢰성이 필요한 경우가 아니면 중단 정책을 권장
- 고신뢰성이 필요한 C 애플리케이션을 만드는 실력 있는 개발자라면 이미 이런 글이 필요 없을 것
xmalloc
같이 할당 함수를 감싸는 래퍼 함수를 정의하는 것을 추천- 반환 값 검사가 필요 없어 코드가 간결해짐
- 소프트웨어가 고도화 된 이후 적절한 복구 전략을 추가하더라도 수정이 간편(Git의 사례)
- 고신뢰성이 필요한 경우가 아니면 중단 정책을 권장