chaoskcuf's lab

POST : 프로그래밍/TIP& Study

[C++] C++ 고수들에게 질문합니다. NULL 포인터 함수 참조는 항상 죽을까요?

class ConsoleHelper
{
public:
    void PrintLine()
    {
        printf("-----");
    }

    void SetValue(int a)
    {
        m_someValue = a;
    }

private:
    int m_someValue;
};

int _tmain(int argc, _TCHAR* argv[])
{
    ConsoleHelper* pConsoleHelper = NULL;
    pConsoleHelper->PrintLine();

   

    return 0;
}


오늘 약간은 흥미로운 주제를 발견해서 여러분에게도 퀴즈를 하나 내겠습니다.

위의 코드는 당연히 컴파일시에 아무 문제가 없습니다.
그러나 런타임에서 어떠한 일이 발생할까요?

Access violation이 일어나서 프로그램이 죽을까요?


2

정답은 "정상적으로 실행된다" 입니다.
(사실 저렇게 싱겁게 죽어버리면 포스팅할 꺼리가 안되겠죠.. ;;;;)

여기서 많은 의문점이 생겨날 것 입니다.
'이때까지 그토록 삽질하며 무수히도 죽여왔던 방법인데 죽지 않는다니..' 라구요..
도대체 ConsoleHelper 클래스 객체를 생성하지도 않았는데 어떻게 멤버 함수를 참조할 수 있습니까!! 말이 됩니까!! 라는 반응도 나올 수 있겠군요

이유는 살펴보겠습니다.

3

C++에서는 함수가 고정된 주소를 호출하게 되어있기 때문입니다.

그럼 위의 코드를 살짝 수정해서 Access violation 을 일어나게 하는 방법은 뭘까요?

int _tmain(int argc, _TCHAR* argv[])
{
    ConsoleHelper* pConsoleHelper = NULL;
    pConsoleHelper->PrintLine();

    pConsoleHelper->SetValue(1);

    return 0;
}


위의 코드에서 SetValue라는 또 다른 멤버 함수를 호출합니다.

1

짝짝짝! 드디어 프로그램이 죽었습니다.


그런데 이 함수는 PrintLine() 함수와 차이가 뭘까요? 음.. m_someValue 라는 멤버 변수에 접근하네요.

4

SetValue의 어셈블리 코드를 살펴보면 m_someValue라는 멤버 변수를 알기 위해 this를 참조하게 되는데,
당연히 객체가 할당되지 않았기 때문에

mov eax, dword ptr [this]   이 부분에서 eax 레지스터에는 0이 할당되고
mov ecx, dword ptr [a]      이 부분에서는 SerValue의 파라미터로 전달된 1이 할당되고
mov dword ptr [eax], ecx   바로 이부분에서 eax 가 포인터가 0인 값을 참조하여 Access violation 이 발생하게 됩니다.

즉, Class의 객체가 NULL이어서 프로그램이 비정상 종료되는 대부분이 바로 Class의 멤버 변수를 접근하면서 생기는 문제입니다.
(멤버함수를 실행해서가 아닙니다.)


그렇다면 여기서 C++ 고수분들이 태클거실지도 모르겠습니다.
멤버 변수에 접근하지 않았는데도 Access violation이 일어났다고...

그런 경우는 어떤 경우일까요.
함수의 주소가 바로 맵핑되지 않는 경우겠지요?
바로 virtual function 입니다.

자 그럼 virtual function으로 프로그램을 죽이기 위해서 코드를 살짝 바꾸어 보겠습니다.

class ConsoleHelper
{
public:
    virtual void PrintLine() //기존의 PrintLine 함수를 virtual로 정의하였습니다.
    {
        printf("-----");
    }

    void SetValue(int a)
    {
        m_someValue = a;
    }

private:
    int m_someValue;
};

int _tmain(int argc, _TCHAR* argv[])
{
    ConsoleHelper* pConsoleHelper = NULL;
    pConsoleHelper->PrintLine();

    return 0;
}


5

virtual function은 함수의 주소가 Runtime에 결정이 되기 때문에
위의 코드에서

mov eax,dword ptr [pConsoleHelper] 이 부분에서 eax 레지스터에 0이 할당되고
mov edx,dword ptr [eax] 이 부분에서 포인터 주소가 0을 참조해서 Access violation이 일어납니다.

이제 좀 궁금중이 풀리셨는지 모르겠네요.
이번기회에 NULL pointer 객체인데 왜 안죽냐를 가지고, 조금 심도있게 살펴본 기회였던것 같습니다.
그리고 어떻게 Access Violation이 일어나서 죽는지도 알아봤습니다.

어디까지나 이것은 C/C++ 언어적 특성입니다.
그리고 저런게 가능하다고 할 지라도 실제코드에서 저렇게 NULL 객체에 함수를 사용하시는 분은 없겠죠?
혹시라도 똑같은 객체에서 어떤 멤버함수는 실행되고 다른 멤버함수는 violation이 일어나는 경우 객체가 NULL인지 살펴보면 되겠군요. :)

그럼 참고로 C#은 어떨까요?
여러분이 일반적으로 알고계시는 멤버함수를 실행할 수 없습니다.

    class ConsoleHelper
    {
        public void PrintLine()
        {
            Console.WriteLine("-----");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ConsoleHelper consoleHelper = null;
            consoleHelper.PrintLine();
        }
    }

위와 같이 C#으로 동일한 내용의 코드를 작성하였습니다.

그리고 아래와 같이 디어셈을 하였습니다.

6 

mov ecx,dword ptr [dbp-40h] 를 실행하고 ecx 레지스터에는 0이 할당되었습니다.
cmp dword ptr [ecx], ecx 를 실행하다 아래와 같이 Exceptioin이 나게 됩니다.

7

이올린에 북마크하기(0) 이올린에 추천하기(0)
top

tags

, , , ,

posted at

2008년 07월 01일 14시 11분


CONTENTS

chaoskcuf's lab
BLOG main image

RSS 2.0Tattertools 믹시
최근 글 최근 댓글 최근 트랙백
카테고리
사이트 링크