[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 |