C++에서 멤버 함수를 호출하면 컴파일러는 암시적으로 호출 대상 객체의 주소인 this 포인터를 전달합니다.
따라서 만약 객체 포인터가 nullptr인 경우, 즉 유효하지 않은 주소를 갖고 있을 때 멤버 함수를 호출하면 정의되지 않은 동작(Undefined Behavior, UB) 이 발생할 수 있습니다.
이 글에서는 구체적으로 다음과 같은 경우에 대해 살펴보겠습니다.
static 멤버 함수 호출멤버 함수 내에서 멤버 변수에 접근하는 경우, this 포인터가 nullptr이면 즉시 nullptr 역참조가 발생하여 segmentation fault 등 런타임 에러가 발생합니다.
#include <iostream>
struct MyClass {
int value = 42;
void accessMember() {
// 멤버 변수에 접근하므로, this가 nullptr면 런타임 에러 발생!
std::cout << "value: " << value << std::endl;
}
};
int main() {
MyClass* ptr = nullptr;
// ptr->accessMember() 호출 시 nullptr를 역참조 → UB 발생
ptr->accessMember();
return 0;
}
멤버 함수 내부에서 멤버 변수나 this를 사용하지 않는 경우, 실행 결과가 문제없이 보일 수 있습니다.
하지만 표준상 이 경우에도 여전히 this 포인터는 암시적으로 전달되므로 정의되지 않은 동작(UB)임에 주의해야 합니다.
#include <iostream>
struct MyClass {
void noAccess() {
// 멤버 변수를 사용하지 않음.
std::cout << "Hello from noAccess()" << std::endl;
}
};
int main() {
MyClass* ptr = nullptr;
// 정상적으로 출력될 수도 있으나, UB에 해당합니다.
ptr->noAccess();
return 0;
}
static 멤버 함수는 클래스에 속한 일반 함수와 같아서, this 포인터가 전달되지 않습니다.
따라서 객체의 유효성 여부와 무관하게 호출할 수 있습니다.
#include <iostream>
struct MyClass {
static void staticFunc() {
std::cout << "Hello from staticFunc()" << std::endl;
}
};
int main() {
MyClass* ptr = nullptr;
// ptr을 통해 호출해도, 내부적으로 this를 사용하지 않으므로 안전합니다.
ptr->staticFunc();
// 권장하는 호출 방법: MyClass::staticFunc();
return 0;
}
가상 함수는 런타임에 호출할 함수 주소를 결정하기 위해 vtable lookup 을 수행합니다.
비록 가상 함수의 본문 내에서 멤버 변수를 접근하지 않더라도, vtable lookup 과정에서 암시적으로 전달되는 this 포인터가 필요합니다.
따라서 this가 nullptr인 경우, vtable 조회 시 nullptr 역참조가 발생하여 정의되지 않은 동작(UB)이 발생할 수 있습니다.
#include <iostream>
struct MyClass {
virtual void virtualNoAccess() {
// 멤버 변수에 접근하지 않지만, 호출 시 vtable lookup을 위해 this가 필요합니다.
std::cout << "Hello from virtualNoAccess()" << std::endl;
}
};
int main() {
MyClass* ptr = nullptr;
// vtable lookup 시 nullptr 역참조 → UB 발생
ptr->virtualNoAccess();
return 0;
}
추가 설명:
4번의 경우, "명시적으로" 오류 메시지가 출력되거나 즉시 크래시가 발생한다고 보장할 수 없습니다.
실제로 시스템이나 컴파일러에 따라 동작이 달라질 수 있지만, UB임에는 변함이 없습니다.
이 경우는 1번과 4번의 문제가 모두 복합되어 발생합니다.
즉, vtable lookup 과정에서 this 포인터가 필요하고, 그 이후 멤버 변수 접근으로 인해 추가적인 nullptr 역참조가 일어납니다.
따라서 매우 위험하며, 정의되지 않은 동작입니다.
#include <iostream>
struct MyClass {
int value = 100;
virtual void virtualAccess() {
// vtable lookup 이후, 멤버 변수 접근 → UB 발생
std::cout << "value: " << value << std::endl;
}
};
int main() {
MyClass* ptr = nullptr;
// vtable 조회와 멤버 변수 접근 모두 문제를 일으킵니다.
ptr->virtualAccess();
return 0;
}
nullptr 객체에서 non-static 멤버 함수를 호출하는 것은 멤버 변수에 접근 여부와 관계없이 UB를 발생시킬 수 있습니다.
특히 가상 함수 호출은 내부적으로 vtable lookup을 위해 this 포인터를 사용하므로, 멤버 변수를 사용하지 않더라도 UB에 해당합니다.
nullptr 인지 확인합니다.static 함수 활용:static 멤버 함수나 전역 함수를 사용하는 방법을 고려해보세요.