2009.08.19 03:08

C++ new 연산자의 진실

Lyn
조회 수 57906 추천 수 0 댓글 2
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

new 연산자의 진실

 

C++에서는 메모리를 할당할 때 new 연산자를 사용합니다.

그리고 이 new 라는 연산자는 오버로딩이 가능합니다. 그럼 과연 이 new 의 정체는 뭘까요?

new 를 호출하면 실제로는 malloc 이 내부에서 다시 호출 된 다는 것은 일단 다 안다고 가정하고 넘어가겠습니다.

 

첫 코드 나갑니다. 연산자 new 를 오버로딩 한 경우입니다.

#include <stdio.h>

#include <stdlib.h>

 

class Test

{

public:

           int a;

 

           void* operator new(size_t size)

           {

                     printf("한 개 할당중\n");

                     Test* temp = (Test*)malloc(size);

                     return temp;

           }

           Test()

           {

                     printf("Test Constructor\n");

           }

};

int main()

{

           Test* t1;

 

           t1 = new Test;

 

           system("pause");

           return 0;

}

 

new 에는 size_t 형태의 파라메터가 하나 존재합니다. 이 파라메터로 할당 해야 할 크기를 알려줍니다. 단순히 그 크기로 메모리를 할당 하고 리턴 만 하엿습니다.

 

실행결과는 아래와 같습니다.

 

image1.png

 

우리는 부른적도 없는 생성자가 호출되었습니다. new 가 단순히 메모리 할당만 하도록 오버로딩 하엿는데요. 여기서 new 는 실제로는 오버로딩이 되지 않았다고 할 수 있겠습니다. 뭔가 수상하군요. 디스어셈블 해 보았습니다.

 

image2.png

 

이런 우리는 new 를 호출했을 뿐인데 실제로는 operator new 라는 함수와 생성자를 따로따로 호출 하고 있었네요. 그럼 우리가 수정한 것은 operator new 라는 함수 일 뿐 new 연산자 자체가 아니라는것을 알 수 있습니다.

 

심지어 윗줄에서는 할당 할 메모리 크기를 스택에 집어넣어주는 친절함(?) 까지 엿볼 수 있군요. Pop 이 호출부위 아래에 존재한다는 것은 operator new cdecl 방식으로 call 되고 있다고 짐작 할 수 있겠습니다.

 

그럼 new 는 단지 operator new 와 생성자를 호출 하고 있을 뿐이라면 이 두 함수를 따로 따로 호출하는것도 가능하지 않을까요? 다음과 같은 코드를 짜 보았습니다.

#include <stdio.h>

#include <stdlib.h>

 

class Test

{

public:

           int a;

 

           void* operator new(size_t size)

           {

                     printf("한 개 할당중\n");

                     Test* temp = (Test*)malloc(size);

                     return temp;

           }

           Test()

           {

                     printf("Test Constructor\n");

           }

};

int main()

{

           Test* t1;

 

           t1 = (Test*)Test::operator new(sizeof(Test));

           t1:Test();

 

           system("pause");

           return 0;

}

 

그리고 실행 해 보았습니다.

 

image3.png

 

이런 완전히 똑같군요.

 

 

그럼 이렇게 결론 내릴 수 있을까요? new 연산자는 operator new 함수를 호출 한 후, 생성자를 호출 해 주는 연산자이다.

 

그런데 이게 또 아닌거 같습니다. 왜냐면 이 두 함수에는 VMT(Virtual Methor Table) 을 생성 해 주는 부분이 없거든요. 가상함수가 한 개 이상 존재하고 상속관계가 있는 클래스(상속 받았던 상속 했던) 에는 반드시 VMT의 포인터가 존재합니다. 그럼 이 VMT는 언제 등록되었을까요? 살짝 코드를 고쳐보았습니다.

 

#include <stdio.h>

#include <stdlib.h>

 

class Test

{

public:

           int a;

 

           void* operator new(size_t size)

           {

                     printf("한 개 할당중\n");

                     Test* temp = (Test*)malloc(size);

                     return temp;

           }

           Test()

           {

                     printf("Test Constructor\n");

                     func();

           }

 

           virtual void func()

           {

                     printf("Test::func()\n");

           }

};

 

class Test2 : public Test

{

           virtual void func()

           {

                     printf("Test2::func()\n");

           }

};

int main()

{

           Test* t1;

 

           t1 = new Test2;

 

           system("pause");

           return 0;

}

 

 

생성자에서 func라는 가상함수를 호출 하고 있습니다.

우리는 Test2를 생성하였으므로 Test2::func() 가 출력 될 거라고 기대 할 수 있겠습니다. 결과를볼까요?

 

image4.png

 

어라 뭔가 이상합니다. Test::func() 가 출력되었네요. 이게 어찌 된 일일까요?

그 이유는 생성자를 호출 하는 시점에서는 VMT의 포인터가 제대로 등록 되지 않기 때문에, 가상함수 본래의 역할을 제대로 하지 못하는 겁니다. 디스어셈블 한 코드를 보겠습니다.

 

image5.png

 

생성자를 호출 한 다음에도 추가적인 작업을 하고 있습니다.

그 중 빨간 네모로 표시 된 부분이 가상함수가 없을때는 존재하지 않던 부분입니다.

즉 저 부분에서 VMT포인터를 추가하는 작업을 하고 있다고 생각 할 수 있겠습니다.

 

이제 new 의 진실이 밝혀졋군요.

new 연산자를 호출하면

 

1.     메모리 할당을 위해 operator  new 함수를 호출한다

2.     생성자를 호출한다

3.     VMT를 등록해 준다

 

3가지 과정으로 new 의 역할은 모두 종료됩니다.

쉽고 편하게 쓰던 연산자가 참 하는 일도 많네요 : )

 

그럼 또 다른 언어인 Delphi 의 경우는 어떨까요?

 

program Project2;

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils;

 

type

  Test = class

  public

    a : Integer;

 

    procedure func;virtual;

    constructor Create;

  end;

 

  Test2 = class(Test)

  public

    procedure func;override;

  end;

 

constructor Test.Create;

begin

  func;

end;

 

procedure Test.func;

begin

  WriteLn('Test.Func');

end;

 

procedure Test2.func;

begin

  WriteLn('Test2.Func');

end;

 

var

  T : Test;

begin

  T := Test2.Create;

  ReadLn;

end.

 

출력 결과는 아래와 같습니다.

image6.png

 

Delphi는 생성자 호출 시점이 VMT 생성 시점보다 뒤입니다.

Delphi 에서는 생성자 내에서 가상함수를 호출하더라도 정상적으로 작동합니다.

?

List of Articles
번호 제목 글쓴이 날짜 조회 수
157 .net 4.7 의 WPF 가 DX11 기반으로 돌아가네요 Lyn 2017.06.23 841
156 2014.10.14 일 Windows 업데이트 이후 VMWare Not enough physical memory available 오류 file Lyn 2014.10.16 19631
155 add "open as administrator" menu for gnome file explorer(nautilus) file Lyn 2016.06.25 4369
154 Android android.os.NetworkOnMainThreadException Lyn 2015.02.15 3230
153 Android Studio Gradle Error:Execution failed for task ':app:packageDebug'. > Duplicate files copied in 오류 해결 Lyn 2015.02.12 7021
152 Boost 빌드 방법 1 Lyn 2012.01.14 31536
151 Boost 에서 자기 자신의 shared_ptr 리턴하기 file Lyn 2010.03.02 37768
150 boost::icl::interval_map 원하는대로 변경해서 사용해보기 file Lyn 2013.09.23 22200
149 boost::interprocess::managed_shared_memory의 동작에 관하여 Lyn 2014.08.22 208494
148 boostpro 문 닫았네요.... file Lyn 2013.11.02 23830
147 C Runtime Library Exception Dump 남기기 두번째 방법 Lyn 2013.06.25 25803
146 C Runtime Library Exception Dump 남기기 첫번째방법 Lyn 2013.06.11 33024
» C++ new 연산자의 진실 2 file Lyn 2009.08.19 57906
144 C++ string&형 인자에 기본값 주기 Lyn 2012.07.24 25860
143 C++ Template 의 inner class 로 type 선언 하기 Lyn 2014.10.29 23102
142 C++ 에서 Nuget 사용해 보기 file Lyn 2015.03.18 10705
141 C++ 에서 타 언어의 try-finally 흉내내기 1 file Lyn 2015.01.05 5737
140 C++ 템플릿 상속에서 베이스 클래스의 멤버에 접근하는 경우의 문제 1 file Lyn 2012.08.29 30223
139 C++11 lambda 간단사용법과 성능비교. 4 file Lyn 2012.01.30 32321
138 C++11 Type Traits. 라이브러리가 프로그래머의 제어에서 벗어나다 1 file Lyn 2015.12.30 4632
Board Pagination Prev 1 2 3 4 5 6 7 ... 8 Next
/ 8