[C++] nullptr 로 멤버 함수 호출하기

Will-Big·2025년 2월 9일

Cpp

목록 보기
4/8

C++에서 멤버 함수를 호출하면 컴파일러는 암시적으로 호출 대상 객체의 주소인 this 포인터를 전달합니다.
따라서 만약 객체 포인터가 nullptr인 경우, 즉 유효하지 않은 주소를 갖고 있을 때 멤버 함수를 호출하면 정의되지 않은 동작(Undefined Behavior, UB) 이 발생할 수 있습니다.
이 글에서는 구체적으로 다음과 같은 경우에 대해 살펴보겠습니다.

  1. 멤버 변수를 접근하는 멤버 함수 호출
  2. 멤버 변수를 접근하지 않는 멤버 함수 호출
  3. static 멤버 함수 호출
  4. 멤버 변수를 접근하지 않는 가상 함수 호출
  5. 멤버 변수를 접근하는 가상 함수 호출

1. 멤버 변수에 접근하는 멤버 함수 호출

멤버 함수 내에서 멤버 변수에 접근하는 경우, 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;
}

2. 멤버 변수를 접근하지 않는 멤버 함수 호출

멤버 함수 내부에서 멤버 변수나 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;
}

3. static 멤버 함수 호출

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;
}

4. 멤버 변수를 접근하지 않는 가상 함수 호출

가상 함수는 런타임에 호출할 함수 주소를 결정하기 위해 vtable lookup 을 수행합니다.
비록 가상 함수의 본문 내에서 멤버 변수를 접근하지 않더라도, vtable lookup 과정에서 암시적으로 전달되는 this 포인터가 필요합니다.
따라서 thisnullptr인 경우, 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임에는 변함이 없습니다.

5. 멤버 변수를 접근하는 가상 함수 호출

이 경우는 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 멤버 함수나 전역 함수를 사용하는 방법을 고려해보세요.
  • 명시적 호출:
    가상 함수의 특정 구현을 호출하고자 한다면, 명시적으로 클래스 이름을 사용하여 호출하는 방법도 있으나, 기본적으로 호출 대상 객체가 유효한지 항상 확인해야 합니다.
profile
개발자가 되고싶어요

0개의 댓글