C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 강좌/문서
C++Builder Programming Tutorial&Docments
[201] 연산자 new 의 진실
Lyn [tohnokanna] 23689 읽음    2009-08-18 18:12
new 연산자의 진실

C++에서는 메모리를 할당할 때 new 연산자를 사용합니다.
그리고 이 new 라는 연산자는 오버로딩이 가능합니다. 그럼 과연 이 new 의 정체는 뭘까요?
new 를 호출하면 실제로는 malloc 이 내부에서 다시 호출 된 다는 것은 일단 다 안다고 가정하고 넘어가겠습니다.

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

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 형태의 파라메터가 하나 존재합니다. 이 파라메터로 할당 해야 할 크기를 알려줍니다. 단순히 그 크기로 메모리를 할당 하고 리턴 만 하엿습니다.

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



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



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

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

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

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

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



이런 완전히 똑같군요.


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

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

#include 
#include 

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() 가 출력 될 거라고 기대 할 수 있겠습니다. 결과를볼까요?



어라 뭔가 이상합니다. Test::func() 가 출력되었네요. 이게 어찌 된 일일까요?
그 이유는 생성자를 호출 하는 시점에서는 VMT의 포인터가 제대로 등록 되지 않기 때문에, 가상함수 본래의 역할을 제대로 하지 못하는 겁니다. 디스어셈블 한 코드를 보겠습니다.



생성자를 호출 한 다음에도 추가적인 작업을 하고 있습니다.
그 중 빨간 네모로 표시 된 부분이 가상함수가 없을때는 존재하지 않던 부분입니다.
즉 저 부분에서 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.

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



Delphi는 생성자 호출 시점이 VMT 생성 시점보다 뒤입니다.
즉 Delphi 에서는 생성자 내에서 가상함수를 호출하더라도 정상적으로 작동합니다.
장성호 [nasilso]   2009-08-18 18:51 X
좋은 강좌네요.  잘 봤습니다.
장성호 [nasilso]   2009-08-18 19:29 X
CBuilder에서 TObject를 상속받으면
   - Delphi랑 비슷해지네요 (디어셈블코드를 보니) 생성자 Test2()가 바로 호출되네요..
   - 그리고 new 연산자 오버로딩해도 동작안하구..

...  
크레브 [kkol]   2009-08-20 11:27 X
강좌에 그림이 안보이는데.. 파일 클릭해서 보면되긴지만..
다른분들은 보이나요?
훌륭한 분석입니다. ~~ ^^
유진양 [ujinyang]   2010-10-07 01:41 X
CBuilder가 델파이의 VCL을 사용하기 위해 이런 호환레이어를 추가한 것으로
델파이와 같은 클래스를 만들려면 클래스 선언시 DELPHICLASS 를 추가하면
된다는 내용을 어느책에서 본듯하네요. ( 확인해보지 않아서 )

+ -

관련 글 리스트
201 연산자 new 의 진실 Lyn 23689 2009/08/18
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.