별빛의 낙하지 :: 'C++' 카테고리의 글 목록

STL 요소 만들어보기(4)

C++ 2012. 11. 16. 04:47 Posted by byulbit

#include <iostream>

#include <xutility>

#include <algorithm>


#include "Node.hpp"

#include "ListIterator.hpp"

#include "List.hpp"


using namespace STD; // 내가 만든 STL의 요소


int main()

{

List<> s; // Single Linked List

List<int, DNode<int>> d; // Double linked List


s.push_back(10);

s.push_back(12);

d.push_back(13);

d.push_back(14);


std::for_each( s.begin(), s.end(), [](int a) { std::cout << a << std::endl; } );

std::for_each( d.begin(), d.end(), [](int a) { std::cout << a << std::endl; } );


}


'C++' 카테고리의 다른 글

STL 요소 만들어보기(3)  (0) 2012.11.16
STL 요소 만들어보기(2)  (0) 2012.11.16
STL 요소 만들어보기(1)  (0) 2012.11.16
[C++] template을 사용하여 연결리스트 구현  (0) 2012.11.16
[C++0x] Move Operator  (0) 2012.11.16

STL 요소 만들어보기(3)

C++ 2012. 11. 16. 04:46 Posted by byulbit

#include <xutility> // bidirectional_iterator_tag


namespace STD {


template<class C >

class ListIterator

{

public:

typedef typename C::elem_type elem_type; // 요소 타입

typedef typename C::node_type node_type;

typedef typename C::node_pointer_type node_pointer_type;

typedef typename C::iterator iterator;


typedef typename C::value_type value_type;

typedef std::bidirectional_iterator_tag iterator_category;


typedef typename C::difference_type difference_type;

typedef typename C::pointer pointer;

typedef typename C::reference reference;


ListIterator(node_type* node) {

_ptr = node;

}


bool operator!=(iterator& rhs) {

return (this->_ptr) != rhs._ptr;

}


bool operator==(iterator& rhs) {

return (this->_ptr) == rhs._ptr;

}


elem_type operator*() {

return (_ptr->_data);

}



node_type* operator++() {

return _ptr = _ptr->next;

}


node_type* operator++(int c) {

return _ptr = _ptr->next;

}

private:

node_type* _ptr;

};


}

'C++' 카테고리의 다른 글

STL 요소 만들어보기(4)  (0) 2012.11.16
STL 요소 만들어보기(2)  (0) 2012.11.16
STL 요소 만들어보기(1)  (0) 2012.11.16
[C++] template을 사용하여 연결리스트 구현  (0) 2012.11.16
[C++0x] Move Operator  (0) 2012.11.16

STL 요소 만들어보기(2)

C++ 2012. 11. 16. 04:45 Posted by byulbit

namespace STD {

template<class C> class ListIterator; // LIstIterator에 대한 전방 선언

template<typename T> class Node; // Node에 대한 전방 선언

//template<typename T> class DNode; // DNode에 대한 전방 선언


template<typename T=int, class NODE=Node<T> >

class List

{

public:


typedef T elem_type;

typedef T* elem_pointer_type;


typedef NODE node_type;

typedef NODE* node_pointer_type;

typedef List<T,NODE> _THIS;

typedef ListIterator<_THIS> iterator;


// STL required

typedef T value_type;

typedef unsigned int difference_type;

typedef T* pointer;

typedef T& reference;


iterator begin() {

return ListIterator<_THIS>(_head->next);

}


iterator end() {

return ListIterator<_THIS>(_tail);

}


List() { 

_head = new NODE(0);

_tail = new NODE(0);

_tail->next = NULL;


_head->next = _tail;

_lastNode = _head;

}


void push_back(T element) {

NODE* elemNode = new NODE(element);

elemNode->next = _tail;


_lastNode->push_back( elemNode );

_lastNode = elemNode;

}


void push_back(NODE* next) {

_lastNode->push_back( next );

_lastNode = next;

}


private:

NODE* _head;

NODE* _tail;


NODE* _lastNode; // Last Node

};


}

'C++' 카테고리의 다른 글

STL 요소 만들어보기(4)  (0) 2012.11.16
STL 요소 만들어보기(3)  (0) 2012.11.16
STL 요소 만들어보기(1)  (0) 2012.11.16
[C++] template을 사용하여 연결리스트 구현  (0) 2012.11.16
[C++0x] Move Operator  (0) 2012.11.16

STL 요소 만들어보기(1)

C++ 2012. 11. 16. 04:41 Posted by byulbit

STL의 요소를 만들어보기

만드는 과정은 시간 날때마다 업데이트...

namespace STD {

	// Single Linked List
	template<typename T>
	class Node
	{
	public:
		Node(T element) {
			_data = element;
			next = NULL;
		}

		void push_back(Node<T>* right) {
			right->next = this->next;
			this->next = right;
		}

		T operator*() {
			return _data;
		}

		T* operator++() {
			this = this->next;
		}

		T _data;
		Node<T>* next;
	};

	// Double Linked List
	template<typename T>
	class DNode
	{
	public:
		DNode(T element) {
			_data = element;
			next = NULL;
			prev = NULL;
		}

		void push_back(DNode<T>* right) {
			right->next = this->next;
			this->next = right;
			right->prev = this;
		}

		T operator*() {
			return _data;
		}

		T* operator++() {
			this = this->next;
		}

		T _data;
		DNode<T>* next;
		DNode<T>* prev;
	};
}


'C++' 카테고리의 다른 글

STL 요소 만들어보기(3)  (0) 2012.11.16
STL 요소 만들어보기(2)  (0) 2012.11.16
[C++] template을 사용하여 연결리스트 구현  (0) 2012.11.16
[C++0x] Move Operator  (0) 2012.11.16
[C++0x] Move Constructor  (0) 2012.11.15

[C++] template을 사용하여 연결리스트 구현

C++ 2012. 11. 16. 01:56 Posted by byulbit

/* SingleLinkedList, DoubleLinkedList 둘 다 사용할 수 없을까? DoubleLinkedList를 만들기 위한 사전작업. */ #include <iostream> template<typename T> class Node { public: Node(T element) { _data = element; next = NULL; } void addToNext(Node<T>* right) { right->next = this->next; this->next = right; } T _data; Node* next; }; // DoubleLinekd List template<typename T> class DNode { public: DNode(T element) { _data = element; next = NULL; prev = NULL; } void addToNext(DNode<T>* right) { right->next = this->next; this->next = right; right->prev = this; } T _data; DNode<T>* next; DNode<T>* prev; }; template<typename T=int, template<typename L> class NODE=Node > class List { public: typedef T value_type; List() { _head = new NODE<T>(0); _tail = NULL; _lastNode = _head; } void addNext(T element) { NODE<T>* elemNode = new NODE<T>(element); _lastNode->addToNext( elemNode ); _lastNode = elemNode; } void addNext(NODE<T>* next) { _lastNode->addToNext( next ); _lastNode = next; } private: NODE<T>* _head; NODE<T>* _tail; NODE<T>* _lastNode; // Last Node }; int main() { List<int, Node> s; // Single Linked List List<int, DNode> d; // Double LInked LIst s.addNext(new Node<int>(10) ); s.addNext(new Node<int>(11) ); s.addNext(12); s.addNext(13); d.addNext(10); d.addNext(11); d.addNext(12); d.addNext(13); std::cout << std::endl; }


'C++' 카테고리의 다른 글

STL 요소 만들어보기(2)  (0) 2012.11.16
STL 요소 만들어보기(1)  (0) 2012.11.16
[C++0x] Move Operator  (0) 2012.11.16
[C++0x] Move Constructor  (0) 2012.11.15
[C++] C++ name decoration 혹은 name mangling  (0) 2012.11.13

[C++0x] Move Operator

C++ 2012. 11. 16. 00:20 Posted by byulbit
/*
	Move Constructor( 이동 연산자 )

	c++0x 에는 기존의 대입연산자에 이어서, 이동 연산자도 추가가 되었다.

*/

#include 

class MoveClass
{
public:
	// Default Constructor
	MoveClass() {
		std::cout << "Default Constructor Call" << std::endl;
	}
	
	// Copy Operator
	void operator=(MoveClass& a) {
		std::cout << "Copy Operator Call" << std::endl;
	}

	
	// Move Operator
	void operator=(MoveClass&& a) {
		std::cout << "Move Operator Call" << std::endl;
	}
};


// 값이 복사되는 곳에서는 일반적으로 복사 생성자가 호출된다.
MoveClass foo(MoveClass& a)
{
	MoveClass local;
	return local;		// 값이 복사 되는데, 우선순위가 존재한다.
						// MoveClass(const MoveClass& a) 형태의 복사 생성자가 존재하고,
						// MoveClass(MoveClass&& a) 형태의 이동 생성자가 존재하면
						// 이동 생성자를 호출한다.
}

int main()
{
	MoveClass a;			// Default Constructor Call
	MoveClass b;
	a = b;					// Copy operator Call
	
	a = std::move(b);		// move Operator Call

}

'C++' 카테고리의 다른 글

STL 요소 만들어보기(1)  (0) 2012.11.16
[C++] template을 사용하여 연결리스트 구현  (0) 2012.11.16
[C++0x] Move Constructor  (0) 2012.11.15
[C++] C++ name decoration 혹은 name mangling  (0) 2012.11.13
[C/C++] switch의 jump table  (0) 2012.11.07

[C++0x] Move Constructor

C++ 2012. 11. 15. 23:38 Posted by byulbit
/*
	Move Constructor( 이동 생성자 )

	c++0x 에는 r-value reference의 도입으로 인해서 Move Constructor가 생겼다.
	이동 생성자의 특징은 복사 대상의 데이터를 지우는 소유권 이전 방식이다.

	기존에 있던 reference는 그래서 이름이 l-value reference 로 불린다.
	l-value 와 r-value 의 차이는 대입식에서 왼쪽에 오느냐, 오른쪽에 오느냐의 차이인데
	
	간단히 말해서 l-value 는 변수라고 보면 되고, r-value는 상수라고 보면 된다.
	기존에 l-value reference는 l-value들만, 즉 변수들만 가리킬 수 있는 반면에
	r-value reference는 r-value 들만, 즉 상수들을 가리킬 수 있다.
	
	int b = 3;
	int& a = b;
	int& c = 3; // error
	
	int&& d = a; // error
	int&& d = 3; // ok.

	r-value reference가 사실 그냥 보기에는 이동 생성자와 전혀 무관해 보이는데, 이것은 기존에
	복사 생성자로 표현하기 어색했던, 소유권 이전방식을 적절히 표현하기에 적합하다.

	무슨 말이냐면,  기존에 auto_ptr 과 같은 소유권 이전의 스마트 포인터들은 값을 이동시키고 싶어하지만,
	적절한 표현이 없어서, const를 제외시킨 아래와 같은 방식으로 구현을 했었다.

	auto_ptr(auto_ptr& a)
	
	문제는 이것이 복사하려는 것인지, 이동하려는 것인지에 대한 명확한 지표가 되기 힘들다는 것.
	이 문제를 해결한 것이 r-value reference(&&)의 등장이다.
r
*/

#include 

class MoveClass
{
public:
	// Default Constructor
	MoveClass() {
		std::cout << "Default Constructor Call" << std::endl;
	}
	
	// Copy Constructor
	MoveClass(const MoveClass& a)	// 반드시 const로 해야 된다. 그렇지 않으면 Move Constructor가 제대로 호출되지 않는다.  
	{
		std::cout << "Copy Constructor Call" << std::endl;				

	}

	// Move Constructor
	MoveClass(MoveClass&& a)
	{
		std::cout << "Move Constructor Call" << std::endl;

	}
};


// 값이 복사되는 곳에서는 일반적으로 복사 생성자가 호출된다.
MoveClass foo(MoveClass& a)
{
	MoveClass local;
	return local;		// 값이 복사 되는데, 우선순위가 존재한다.
						// MoveClass(const MoveClass& a) 형태의 복사 생성자가 존재하고,
						// MoveClass(MoveClass&& a) 형태의 이동 생성자가 존재하면
						// 이동 생성자를 호출한다.
}

int main()
{
	MoveClass a;	// Default Constructor Call

	foo(a);

	MoveClass b(a);					// Copy Consturctor call
	MoveClass b(std::move(a));		// Move Constructor call

}

[C++] C++ name decoration 혹은 name mangling

C++ 2012. 11. 13. 19:48 Posted by byulbit

C++은 함수 오버로딩을 지원하기 때문에 기존의 C스타일의 이름의 함수로는 함수오버로딩을 지원할 수 없다. 

따라서 함수 이름에 대한 데코레이션(decoration) 혹은 맹글링(mangling)이 필요하다.

(데코레이션과 맹글링은 이름을 꾸미는 것, 섞는 것이라는 그런 의미다.)

  

*네임 맹글링 자체가 함수를 구분해주기 위한 용도로써라고 이해하기 되면 약간 거시기 하다.

정확히 이야기하면 C에서도 함수를 부를때 네임 맹글링을 한다. 

"_"를 함수 이름에 붙이는데,  C++기준으로 보면 이건 네임맹글링 수준이 매우 미미하다.


여기서는 함수 오버로딩을 지원하기 위한 측면에서 살펴보겠다.

name mangling, 혹은 name decoration을 하기 위한 것은 컴파일러가 결정하게 되는데,

일반적으로 함수의 파라미터로 함수 지원한다고 생각했었으나 실제 테스트 해보니 그렇지 않았다.

VC++ Compiler에서 반환값도 네임 맹글링에 포함된다.

예를 들어서 아래는 반환값 test2(void) 의 함수 시그니처이다.

 

int __cdecl test2(void)" (?test2@@YAHXZ) -H(int return)

void __cdecl test2(void)" (?test2@@YAXXZ) -X(void return)

float __cdecl test2(void)" (?test2@@YAMXZ) -M(float return)

char __cdecl test2(void)" (?test2@@YADXZ) -D(char return)


보다시피 반환값이 바뀌게 되면 함수의 시그니처 또한 바뀐다.

약간 재밌는 것은 VS에서 C++에서 함수를 호출하는데, 함수의 원형 선언이 없으면 아래와 같은 에러를 
ClCompile 단계에서 낸다. ( C에서는 함수의 선언과 관계가 없이  정의가 존재하면, 함수의 선언과 여부없이 에러를 내지 않는다.)

error C3861: 'test2': identifier not found

위의 에러는 test2 함수를 선언하지 않은 경우로, test2에 대한 identifier 가 없다는 에러인데, 컴파일 단계, 즉 번역 단계에서 에러가 나는 것이라고 추론할 수 있다. 

  또한 C++에서는 이름이 같은 함수끼리의 함수 오버로딩 집합이 존재하며, 오버로딩 집합을 통해서 함수를 부를때 호출하는 측의 시그니처가 동일하지 않아도 오버로딩 해석에 따라서 함수를 부를 수가 있다.

함수에 대한 다른 오버로드한 함수가 존재해서 부르는 함수의 파라미터 개수가 맞는 것이 없다면 아래와 같은 오률르 낸다.
error C2661: 'test2' : no overloaded function takes 3 arguments


함수가 선언이 존재하고, 해당 함수의 정의가 존재하지 않으면 외부 심볼을 찾을 수 없다고 링커 오류를 낸다.
error LNK2019: unresolved external symbol "int __cdecl test3(void) )

#include <stdio.h>


void test2();

int main()
{
//printf("%d", test2());
test2();

}


그렇다면 반환값만 다른 함수의 선언이 있다면 어떻게 될까?
이 경우 아래와 같은 에러를 낸다.
 error LNK2019: unresolved external symbol "void __cdecl test2(void)" (?test2@@YAXXZ) referenced in function _main

이것이 에러가 나는 지점은 함수의 선언이 존재하는데, 정확히 일치하는 해당하는 함수의 정의를 찾지 못했다는 의미다.

자 그러면 A라는 함수의 원형을 선언해놓고, 호출하는 측에서 B라는 함수를 호출, 다른 오브젝트 파일에 B 함수를 정의하면 어떻게 될까?

void test2의 정의를 넣어보았다.

// test2.cpp
int test2()
{
printf("dfdfdf");
return 0;
}

void test2()
{
printf("111111");
}


그러자 이번에는 아래와 같은 에러 메시지가 나온다. 
error C2556: 'void test2(void)' : overloaded function differs only by return type from 'int test2(void)'
error C2371: 'test2' : redefinition; different basic types

즉 오버로딩 함수들은 return type이 다른 것은 인정하지 않는 다는 것을 알 수 있다.
그래서 매개변수가 있는 오버로드 함수를 추가해보았다.

int test2(int a, int b)
{
return a + b;
}

부르는 쪽에서 보았을 때 함수의 선언이 존재하지 않아서 아래와 같은 에러를 낸다.
error C2660: 'test2' : function does not take 2 arguments 

이것은 함수의 오버로딩 집합이 선언에 의해서 판별된다는 것을 의미한다.
C++은 함수의 원형과 관련된 존재가 필수적이라는 것을 알 수 있다.
C의 경우 함수의 심볼이 함수의 이름으로 한정된다.  
즉 여러 오브젝트 파일을 링크 할때, 함수의 이름이 같다면, 해당하는 함수를 추가하기가 힘들다.
아래와 같이 심볼이 중복 정의 되어 있다고 에러를 내기 때문이다.
 fatal error LNK1169: one or more multiply defined symbols found

그렇다면 C++은 어떠할까?
C++의 경우 함수원형의 선언이 필수적이지만, 시그니처가 일치한다면 해당하는 함수를 부를 수 있으며,
오버로딩을 이용하여 함수의 이름이 같지만, 매개변수가 다른 것들을 추가할 수 있다.
물론 이 경우도 함수를 사용할시에 사용할 함수의 선언이 필수적이다.
 

C에서는 부르는 쪽에서 해당 함수가 존재하지 않으면 당당히 링커에서 오류를 낸다. 
 error LNK2019: unresolved external symbol _test1
위의 에러를 보면 알겠지만, "_함수이름" 으로 C에서는 네임 맹글링을 하며, 
즉 C는 오버로딩이 지원이 안된다.
하지만 해당하는 심볼이 존재한다면 호출이 가능한데 예를 들어서 함수의 정의 자체가

int test(int a)
{
printf("test(int a)");
return 0;
}

이렇게 정의가 되어 있더라도, 해당하는 함수를 다른 매개변수로써 호출할 수 있다.
int main()
{
test("Dfdfdfdf",2);

}

위의 코드는 test(int a)를 호출한다. 이것이 가능한 이유는 함수를 호출 할때는 함수의 심볼을 보고 찾기 때문이다.  즉 C에서는 test(int a)나 test(const char*, int) 로 추정되는 것들이나 함수의 심볼 자체는 _test로 같은 것으로 여겨지게 된다.

보통 C++에서 함수의 매개변수와 반환값 등으로 네임 맹글링을 한다고 생각할 때, 그럼 C++에서는 어떻게 가변인자 함수를 만들고 부를까? 라는 의구심이 생길 수 있다.  왜냐면 함수의 심볼이 같아야 함수의 정의를 찾을 수 있고, 따라서 함수 호출이 가능하기 때문이다.
테스트 해본 결과 재밌는 사실은 매개변수의 갯수 뿐만 아니라, 가변인자 함수인 경우의 네임맹글링도 존재하는 것이다.

void sprintfs(char *Result, const char *Format,  ...  ); 

이런 함수가 존재할 경우에

sprintfs("%d %d %d %d %d", &a, &b, &c, &d, &e); 이렇게 함수를 호출하는 경우와
sprintfs("%d %d %d %d", &a, &b, &c, &d);  이런 경우에 대해서 가변 인자 함수가 아니라면 심볼이 일치 하지 않아서 함수를 찾을 수 없어야 한다.

확인해본 결과 가변인자 함수의 경우 시그니처는 sprintfs@@YAXPADPBDZZ 로 동일하며 부르는 쪽의 매개변수가 달라져도,  심볼은 같으므로, 호출이 가능하다.


[C/C++] switch의 jump table

C++ 2012. 11. 7. 02:58 Posted by byulbit

switch 문의 경우 case 조건이 4개 이상일때 단순한 비교값 jump가 아니라 jump table을 만들고, 
jump table에 이동할 case값을 기록한다. 
그리고 그 값을 참조하여 이동한다. 

그럼 실제로 한번 살펴보자.

#include <stdio.h>

int main() { int a; scanf("%d", &a); switch(a) { case 0: printf("0000"); break; case 1: printf("1111"); break; case 2: printf("2222"); break; case 3: printf("3333"); break; case 4: printf("4444"); break; case 5: printf("5555"); break; } }

  

8: switch(a)

000A1039 8B 45 F8             mov         eax,dword ptr [a]  

000A103C 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax  

000A1042 83 BD 30 FF FF FF 05 cmp         dword ptr [ebp-0D0h],5  

000A1049 0F 87 A1 00 00 00    ja          $LN1+17h (0A10F0h)  

000A104F 8B 8D 30 FF FF FF    mov         ecx,dword ptr [ebp-0D0h]  

000A1055 FF 24 8D 30 11 0A 00 jmp         dword ptr  (0A1130h)[ecx*4]  

     9: {

    10: case 0:

    11: printf("0000");

000A105C 8B F4                mov         esi,esp  

000A105E 68 54 31 0A 00       push        offset string "0000" (0A3154h)  

000A1063 FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A1069 83 C4 04             add         esp,4  

000A106C 3B F4                cmp         esi,esp  

000A106E E8 ED 00 00 00       call        _RTC_CheckEsp (0A1160h)  

    12: break;

000A1073 EB 7B                jmp         $LN1+17h (0A10F0h)  

    13: case 1:

    14: printf("1111");

000A1075 8B F4                mov         esi,esp  

000A1077 68 4C 31 0A 00       push        offset string "1111" (0A314Ch)  

000A107C FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A1082 83 C4 04             add         esp,4  

000A1085 3B F4                cmp         esi,esp  

000A1087 E8 D4 00 00 00       call        _RTC_CheckEsp (0A1160h)  

    15: break;

000A108C EB 62                jmp         $LN1+17h (0A10F0h)  

    16: case 2:

    17: printf("2222");

000A108E 8B F4                mov         esi,esp  

000A1090 68 44 31 0A 00       push        offset string "2222" (0A3144h)  

000A1095 FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A109B 83 C4 04             add         esp,4  

000A109E 3B F4                cmp         esi,esp  

000A10A0 E8 BB 00 00 00       call        _RTC_CheckEsp (0A1160h)  

    18: break;

.........

    27: break;

    28: }

    29: 

    30: 

    31: }


000A1039 8B 45 F8             mov         eax,dword ptr [a]  : 변수 a 값을 eax에 대입한다.  

000A103C 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax     :      eax를 ebp-0D0h에 기록한다.

000A1042 83 BD 30 FF FF FF 05 cmp         dword ptr [ebp-0D0h],5      :      case 범위를 넘기는가 비교

000A1049 0F 87 A1 00 00 00    ja          $LN1+17h (0A10F0h)               : ja(jump above) 5 보다 크면 switch case를 통과한다.


mov         dword ptr [ebp-0D0h], eax : 조건변수 값(eax)을 ebp-0D0h 번지에 기록함을 알 수 있다.

// 여기서는 001AF934~001AF936번지를 쓰고 있음을 알 수 있다. 

001AF934 02 00                add         al,byte ptr [eax]  

001AF936 00 00                add         byte ptr [eax],al  


000A1055 FF 24 8D 30 11 0A 00 jmp      :   dword ptr  (0A1130h)[ecx*4]  명령을 통해서

000A1130 + 8h 에 있는 값으로 jmp를 한다.( dword ptr  (0A1130h)[ecx*4]  )

000A1138h에는 000A108E 주소값이 들어 있으며, 이 주소는 case 2의 시작하는 주소와 같다.


<case 2>

000A1138 8E                   db          8eh  

000A1139 10                   db          10h  

000A113A 0A                   db          0ah  

000A113B 00                   db          00h  


<switch table>

000A1130 5C                   db          5ch  

000A1131 10                   db          10h  

000A1132 0A                   db          0ah  

000A1133 00                   db          00h  

000A1134 75                   db          75h  

000A1135 10                   db          10h  

000A1136 0A                   db          0ah  

000A1137 00                   db          00h  

000A1138 8E                   db          8eh  

000A1139 10                   db          10h  

000A113A 0A                   db          0ah  

000A113B 00                   db          00h  

000A113C A7                   db          a7h  

000A113D 10                   db          10h  

000A113E 0A                   db          0ah  

000A113F 00                   db          00h  

000A1140 C0                   db          c0h  

000A1141 10                   db          10h  

000A1142 0A                   db          0ah  

000A1143 00                   db          00h  

000A1144 D9                   db          d9h  

000A1145 10                   db          10h  

000A1146 0A                   db          0ah  

000A1147 00                   db          00h


    16:  case 2:

    17:  printf("2222");

000A108E 8B F4                mov         esi,esp  

000A1090 68 44 31 0A 00       push        offset string "2222" (0A3144h)  

000A1095 FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A109B 83 C4 04             add         esp,4  

000A109E 3B F4                cmp         esi,esp  




C++ 관련 책을 보면 아래와 같은 코드에서 C++ 컴파일러가 클래스 내에서 생성자가 정의되어 있지 않을 경우, 기본 생성자(default constructor)를 삽입한다고 알려져 있다.

하지만 실제로는 컴파일러 구현체에 따라서 달라지겠지만, 내가 현재 주로 쓰고 있는 VC++에서는 그러한 뻘짓(?)을 하지 않는다.


class Ref
{
public:
	int a;
	int b;

};

int main()
{
	Ref a;
	a.a = 12;
	a.b = 13;
	Ref b = a;

}

실제 어셈블리로 변환된 코드는 아래와 같다.

Ref a;

a.a = 12;

002A101E  mov         dword ptr [a],0Ch  

a.b = 13;

002A1025  mov         dword ptr [ebp-8],0Dh  

Ref b = a;

002A102C  mov         eax,dword ptr [a]  

002A102F  mov         dword ptr [b],eax  

002A1032  mov         ecx,dword ptr [ebp-8]  

002A1035  mov         dword ptr [ebp-18h],ecx  


놀랍지 않은가?  보시다시피 기본 생성자를 부를꺼라고 예상했지만, 실제로는 그러한 코드는 없다.

또한 디폴트 복사 생성자도 존재하지 않음을 알 수 있다.


만약 생성자가 존재하는 상태에서 기본 복사 생성자도 존재할까?

아래의 코드를 디스어셈블 해보았다.

class Ref
{
public:
	int a;
	int b;
	Ref(int a, int b) { cout << "1 "; }
};

int main()
{
	Ref a(1, 2);
	Ref b = a;
}


그 결과 아래와 같은 코드로 변환이 되었음을 알 수 있었다.

Ref a(1, 2);

00EC101E  push        2  

00EC1020  push        1  

00EC1022  lea         ecx,[a]  

00EC1025  call        Ref::Ref (0EC1090h)                     // 생성자 호출 

Ref b = a;

00EC102A  mov         eax,dword ptr [a]  

00EC102D  mov         dword ptr [b],eax  

00EC1030  mov         ecx,dword ptr [ebp-8]  

00EC1033  mov         dword ptr [ebp-18h],ecx  

즉 사용자가 정의한 생성자는 호출되었지만, 복사 생성자는 역시 존재하지 않음을 알 수 있다.

객체의 동적 할당인 경우는 어떻게 될까?

아래의 코드를 보자

class Ref
{
public:
//	Ref(){ cout << "constructor call" << endl; }
//	~Ref() { cout << "destructor call" << endl; }

};

int main()
{
	Ref* p = new Ref;

	delete p;


}

Ref* p = new Ref;

00EA101E  push        1  

00EA1020  call        operator new (0EA106Ch)  

00EA1025  add         esp,4  

00EA1028  mov         dword ptr [ebp-0E0h],eax  

00EA102E  mov         eax,dword ptr [ebp-0E0h]  

00EA1034  mov         dword ptr [p],eax  


delete p;

00EA1037  mov         eax,dword ptr [p]  

00EA103A  mov         dword ptr [ebp-0D4h],eax  

00EA1040  mov         ecx,dword ptr [ebp-0D4h]  

00EA1046  push        ecx  

00EA1047  call        operator delete (0EA1066h)  

00EA104C  add         esp,4  

즉 여전히 기본 생성자는 찾아 볼 수가 없다. 메모리 할당, 해제와 관련된 함수 호출이 있을 뿐이다.  여기서 재미있는 것은 소멸자 또한 없다는 사실이다.

비교를 하기 위해서 아래는 명시적으로 생성자를 지정한 경우의 동적할당 어셈코드이다.


class Ref
{
public:
	Ref(){ cout << "constructor call" << endl; }
	~Ref() { cout << "destructor call" << endl; }

};

int main()
{
	Ref* p = new Ref;

	delete p;


}

Ref* p = new Ref;

0080103D  push        1  

0080103F  call        operator new (801B7Ah)  

00801044  add         esp,4  

00801047  mov         dword ptr [ebp-0F8h],eax  

0080104D  mov         dword ptr [ebp-4],0  

00801054  cmp         dword ptr [ebp-0F8h],0  

0080105B  je          main+70h (801070h)  

0080105D  mov         ecx,dword ptr [ebp-0F8h]  

00801063  call        Ref::Ref (801100h)  

00801068  mov         dword ptr [ebp-10Ch],eax  

0080106E  jmp         main+7Ah (80107Ah)  

00801070  mov         dword ptr [ebp-10Ch],0  

0080107A  mov         eax,dword ptr [ebp-10Ch]  

00801080  mov         dword ptr [ebp-104h],eax  

00801086  mov         dword ptr [ebp-4],0FFFFFFFFh  

0080108D  mov         ecx,dword ptr [ebp-104h]  

00801093  mov         dword ptr [ebp-14h],ecx  


delete p;

00801096  mov         eax,dword ptr [ebp-14h]  

00801099  mov         dword ptr [ebp-0E0h],eax  

0080109F  mov         ecx,dword ptr [ebp-0E0h]  

008010A5  mov         dword ptr [ebp-0ECh],ecx  

008010AB  cmp         dword ptr [ebp-0ECh],0  

008010B2  je          main+0C9h (8010C9h)  

008010B4  push        1  

008010B6  mov         ecx,dword ptr [ebp-0ECh]  

008010BC  call        Ref::`scalar deleting destructor' (801170h)  

008010C1  mov         dword ptr [ebp-10Ch],eax  

008010C7  jmp         main+0D3h (8010D3h)  

008010C9  mov         dword ptr [ebp-10Ch],0