본문 바로가기

C++

segmentation fault 사례 6

[1. 개요]

C++ 표준에 근거한 멤버 변수 초기화 순서를 지키지 않음으로 인해 발생한 fault 사례 정리


[2. 참조자]

어떤 객체가 참조자를 멤버 변수로 포함하고 있는 상태에서,

이 참조자가 제대로 초기화 되지 않고, 이 참조자를 접근 할 때 문제가 발생 할 수 있다.

 

일반적으로 참조자가 포인터 처럼  NULL 값 이 들어오는 상황은 거의 없지만, (컴파일 오류..)

아래와 같은 C++ 표준 상, 참조자가 유효하지 않은 메모리를 가리키는 상황이 발생 할 수 있다.

- 멤버 변수는 선언된 순서대로 초기화 된다.

- 생성자 이니셜라이저 리스트 순서와 관계 없다.

- ISO/IEC 14882, 12.6.2


[3. 예제]

#include <iostream>

class A
{
    public:
        int a;
        A(const int _a) : a(_a) {}
};


class B
{
    public:
    const A& a;
    B(const A& _a) : a(_a) {}
};

class ABC
{
public:
#ifdef VALID
    const A& m_a;
    const B& m_b;

#else
    const B& m_b;
    const A& m_a;
#endif
    ABC(const A& _a) : m_a(_a), m_b(m_a) {}
};


int main()
{
    std::cout << "start" << std::endl;
    const A obj_a(77);
    std::cout << "A " << obj_a.a << std::endl;

    const B obj_b(obj_a);
    std::cout << "B " << obj_b.a.a << std::endl;

    ABC obj_abc(obj_a);
    std::cout << "ABC " << obj_abc.m_b.a.a << std::endl;

    std::cout << "end" << std::endl;
    return 0;
}

 

VAILD 매크로가 정의되지 않았다면, 해당 소스를 컴파일 후 실행하면 의도한 77 이 출력되지 않는다.

출력 결과
start
A 77
B 77
ABC -98693133
end

 

멤버 이니셜라이저 순서와 관계없이 멤버 변수가 선언 된 순서로 초기화 되기 때문에, 

클래스 ABC 의 멤버 m_b 는 아직 초기화 되지 않은 참조자를 가리키게 되는 것이다.

- 실제 컴파일 시 에는 포인터 값이 들어가기에,
  (참조자는 일종의 논리적인 모델이고, 실제 컴파일 시 에는 포인터가 되는 듯함..)
- m_b 가 초기화 되는 시점에는 유효하지 않은 포인터가 들어가게 된다.

 

위 예제는 정수 값 출력과 같은 간단한 예제이기에 fault 가 발생하지는 않았으나,

 

해당 참조자가 조금 더 복잡한 구조 였다면 fault 가 발생 할 수 있다.

'C++' 카테고리의 다른 글

fsanitize 컴파일 옵션  (2) 2025.07.02
buffer overflow 사례1  (0) 2025.06.26
Segmentation fault 사례5  (0) 2025.06.24
Placement new  (0) 2025.05.28
wchar_t 사용에 관하여 [2]  (0) 2025.05.13