[2026] C++ Segmentation Fault | 세그멘테이션 폴트 크래시 원인과 해결

[2026] C++ Segmentation Fault | 세그멘테이션 폴트 크래시 원인과 해결

이 글의 핵심

C++ Segmentation Fault의 C++, Segmentation, Fault, 들어가며: Segmentation fault core dumped를 실전 예제와 함께 상세히 설명합니다.

들어가며: “Segmentation fault (core dumped)"

"프로그램이 갑자기 크래시해요”

C++에서 Segmentation Fault(세그폴트)는 잘못된 메모리 접근으로 발생하는 크래시입니다. 널 포인터, 댕글링 포인터, 배열 범위 초과 등이 주요 원인입니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 널 포인터 접근
int* ptr = nullptr;
*ptr = 42;  // Segmentation fault
// ❌ 댕글링 포인터
int* ptr = new int(42);
delete ptr;
*ptr = 99;  // Segmentation fault
// ❌ 배열 범위 초과
int arr[5];
arr[1000000] = 42;  // Segmentation fault

이 글에서 다루는 것:

  • Segmentation Fault 원인 10가지
  • 디버깅 방법
  • 예방 방법
  • 실전 해결책

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. Segmentation Fault란?
  2. 원인 10가지
  3. 디버깅 방법
  4. 예방 방법
  5. 정리

1. Segmentation Fault란?

정의

Segmentation Fault프로세스가 접근 권한이 없는 메모리에 접근할 때 발생하는 크래시입니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 메모리 레이아웃 (개념적)
// [텍스트] [데이터] [힙] [...] [스택]
//                            ↑
//                    접근 금지 영역
int* ptr = (int*)0x12345678;  // 임의의 주소
*ptr = 42;  // ❌ Segmentation fault

플랫폼별 이름

  • Linux/Unix: Segmentation fault (SIGSEGV)
  • Windows: Access Violation (0xC0000005)
  • macOS: EXC_BAD_ACCESS

2. 원인 10가지

원인 1: 널 포인터 접근

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 널 포인터
int* ptr = nullptr;
*ptr = 42;  // Segmentation fault
// ✅ nullptr 체크
int* ptr = nullptr;
if (ptr != nullptr) {
    *ptr = 42;
}

원인 2: 댕글링 포인터

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 댕글링 포인터
int* ptr = new int(42);
delete ptr;
*ptr = 99;  // Segmentation fault
// ✅ delete 후 nullptr
int* ptr = new int(42);
delete ptr;
ptr = nullptr;

원인 3: 배열 범위 초과

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 범위 초과
int arr[5];
arr[100] = 42;  // Segmentation fault
// ✅ 범위 체크
int arr[5];
int index = 100;
if (index >= 0 && index < 5) {
    arr[index] = 42;
}

원인 4: 스택 오버플로우

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 무한 재귀
void foo() {
    foo();  // Segmentation fault (스택 오버플로우)
}
// ✅ 종료 조건
void foo(int n) {
    if (n <= 0) return;
    foo(n - 1);
}

원인 5: 큰 지역 변수

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 스택에 큰 배열
void foo() {
    int arr[10000000];  // Segmentation fault (스택 오버플로우)
}
// ✅ 힙 할당
void foo() {
    auto arr = std::make_unique<int[]>(10000000);
}

원인 6: 지역 변수 반환

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 지역 변수 포인터 반환
int* foo() {
    int x = 42;
    return &x;  // 댕글링 포인터
}
int main() {
    int* ptr = foo();
    *ptr = 99;  // Segmentation fault
}
// ✅ 동적 할당
std::unique_ptr<int> foo() {
    return std::make_unique<int>(42);
}

원인 7: 초기화되지 않은 포인터

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 초기화 안 됨
int* ptr;  // 쓰레기 값
*ptr = 42;  // Segmentation fault
// ✅ 초기화
int* ptr = nullptr;
// 또는
int* ptr = new int(42);

원인 8: 잘못된 캐스팅

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 잘못된 캐스팅
int x = 42;
std::string* ptr = (std::string*)&x;
ptr->length();  // Segmentation fault
// ✅ 올바른 타입 사용
int x = 42;
int* ptr = &x;

원인 9: 이중 해제

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 이중 해제
int* ptr = new int(42);
delete ptr;
delete ptr;  // Segmentation fault
// ✅ delete 후 nullptr
int* ptr = new int(42);
delete ptr;
ptr = nullptr;

원인 10: 문자열 리터럴 수정

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 문자열 리터럴 수정
char* str = "Hello";
str[0] = 'h';  // Segmentation fault (읽기 전용)
// ✅ 배열 사용
char str[] = "Hello";
str[0] = 'h';  // OK

3. 디버깅 방법

방법 1: GDB

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 디버그 심볼로 컴파일
g++ -g -o myapp main.cpp
# GDB 실행
gdb ./myapp
# 프로그램 실행
(gdb) run
# 크래시 위치 확인
(gdb) backtrace
(gdb) frame 0
(gdb) print ptr

방법 2: Valgrind

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Valgrind 실행
valgrind --leak-check=full ./myapp
# 출력 예시:
# Invalid write of size 4
#    at 0x4005A7: main (main.cpp:10)
#  Address 0x0 is not stack'd, malloc'd or (recently) free'd

방법 3: AddressSanitizer

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# ASan으로 컴파일
g++ -fsanitize=address -g -o myapp main.cpp
# 실행
./myapp
# 출력 예시:
# ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#     #0 0x4005a7 in main main.cpp:10

4. 예방 방법

방법 1: 스마트 포인터 사용

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ unique_ptr
auto ptr = std::make_unique<int>(42);
// 자동 해제, 댕글링 포인터 방지
// ✅ shared_ptr
auto ptr = std::make_shared<int>(42);
// 참조 카운팅, 자동 해제

방법 2: 컨테이너 사용

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ vector (범위 체크)
std::vector<int> vec = {1, 2, 3};
vec.at(100);  // 예외 던짐 (크래시 대신)
// ✅ array
std::array<int, 5> arr = {1, 2, 3, 4, 5};
arr.at(100);  // 예외 던짐

방법 3: nullptr 체크

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ nullptr 체크
void foo(int* ptr) {
    if (ptr == nullptr) {
        std::cerr << "Null pointer\n";
        return;
    }
    *ptr = 42;
}

방법 4: 컴파일러 경고 활성화

아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 모든 경고 활성화
g++ -Wall -Wextra -Werror main.cpp
# 경고 예시:
# warning: 'ptr' may be used uninitialized

정리

Segmentation Fault 원인

원인해결책
널 포인터nullptr 체크
댕글링 포인터스마트 포인터
배열 범위 초과at() 사용
스택 오버플로우힙 할당
초기화 안 됨nullptr 초기화
이중 해제스마트 포인터

핵심 규칙

  1. 스마트 포인터 사용 (댕글링 포인터 방지)
  2. nullptr 체크 (널 포인터 방지)
  3. at() 사용 (범위 체크)
  4. ASan 활성화 (디버깅)

체크리스트

  • 포인터 사용 전에 nullptr 체크를 하는가?
  • 스마트 포인터를 사용하는가?
  • 배열 범위를 체크하는가?
  • ASan으로 테스트하는가?

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


마치며

Segmentation Fault잘못된 메모리 접근으로 발생하는 크래시입니다. 핵심 원칙:

  1. 스마트 포인터 사용
  2. nullptr 체크
  3. ASan 활성화 원시 포인터보다 스마트 포인터를 사용해 안전하게 코딩하세요. 다음 단계: Segmentation Fault를 이해했다면, C++ 스마트 포인터 가이드에서 더 깊이 배워보세요.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3