malloc -> void* 반환, (타입 변환)을 통해 사용.
타입 변환 유형 (비트열 재구성 여부)
[1] 값 타입 변환
특징) 의미를 유지하기 위해서, 원본 객체와 다른 비트열 재구성
int a = 123456789; // 2의 보수
float b = (float)a; // 부동소수점(지수 + 유효숫자)
[2] 참조 타입 변환
특징) 비트열을 재구성하지 않고, '관점'만 바꾸는 것
거의 쓸 일은 없지만, 포인터 타입 변환도 '참조 타입 변환'과 동일한 룰을 따름.
int a = 123456789; // 2의 보수
float b = (float&)a; // 부동소수점(지수 + 유효숫자)
안전도 분류
[1] 안전한 변환
의미가 항상 100% 완전히 일치하는 경우
같은 타입이면서 크기만 더 큰 바구니로 이동
작은 바구니 -> 큰 바구니로 이동 OK (업캐스팅)
ex) char -> short, short -> int, int -> __int64
[2] 불안전한 변환
의미가 항상 100% 일치한다고 보장하지 못하는 경우
타입이 다르거나
같은 타입이지만 큰 바구니 -> 작은 바구니 이동 (다운캐스팅)
ex) int -> float, int -> short
프로그래머 의도에 따른 분류
[1] 암시적 변환
이미 알려진 타입 변환 규칙에 따라서 컴파일러 '자동'으로 타입 변환
int a = 123456789;
float b = a; // 암시적으로 (warning 발생, 컴파일러 레벨 올리면 error 발생)
[2] 명시적 변환
int a = 123456789;
int b = (int)a; // 명시적
아무런 연관 관계가 없는 클래스 사이의 변환
[1] 연관 없는 클래스 사이의 '값 타입' 변환
특징) 일반적으로 안됨 (예외: 타입 변환 생성자, 타입 변환 연산자)
Kight knight;
Dog dog = (Dog)knight;
// 타입 변환 생성자
Dog(const Knight& knight)
{
...
}
// 타입 변환 연산자
operator Knight()
{
return (Knight)(*this);
}
Knight knight = dog;
[2] 연관 없는 클래스 사이의 참조 타입 변환
특징) 명시적으로는 OK
Knight knight;
// 어셈블리 : 포인터 = 참조
// [ 주소 ] -> [ Dog ]
Dog& dog = (Dog&)knight;
dog._cuteness = 12; // invalid memory
상속 관계에 있는 클래스 사이의 변환
[1] 상속 관계 클래스의 값 타입 변환
특징) 자식->부모 OK / 부모->자식 NO
Dog dog;
BullDog bulldog = (BullDog)dog; // 컴파일 에러
BullDog bulldog;
Dog dog = bulldog;
[2] 상속 관계 클래스의 참조 타입 변환
특징) 자식->부모 OK / 부모->자식 (암시적NO) (명시적OK)
Dog dog;
BullDog& bulldog = dog; // 컴파일 에러
bulldog._cuteness = 12; // invalid memory
BullDog& bulldog = (BullDog&)dog; // 가능
BullDog bulldog;
Dog& dog = bulldog;
결론)
[값 타입 변환] : 진짜 비트열도 바꾸고~ 논리적으로 말이 되게 바꾸는 변환
[참조 타입 변환] : 비트열은 냅두고 우리의 '관점'만 바꾸는 변환
타입 변환 (포인터)
class Knight
{
public:
int _hp = 0;
};
class Item
{
public:
Item()
{
cout << "Item()" << endl;
}
Item(int itemType) : _itemType(itemType)
{
}
Item(const Item& item)
{
cout << "Item(const Item&)" << endl;
}
~Item()
{
cout << "~Item()" << endl;
}
public:
int _itemType = 0;
int _itemDbld = 0;
char _dummy[4096] = {}; // 이런 저런 정보들로 인해 비대해진
};
enum ItemType
{
IT_WEAPON = 1,
IT_ARMOR = 2,
};
class Weapon : public Item
{
public:
Weapon() : Item(IT_WEAPON)
{
cout << "Weapon()" << endl;
}
~Weapon()
{
cout << "~Weapon()" << endl;
}
public:
int _damage = 0;
};
class Armor : public Item
{
public:
Armor() : Item(IT_ARMOR)
{
cout << "Armor()" << endl;
}
~Armor()
{
cout << "~Armor()"<< endl;
}
public:
int _defence = 0;
};
void TestItem(Item item)
{
}
void TestItemPtr(Item* item)
{
}
int main()
{
{
// Stack [ type(4) dbid(4) dummy(4096) ]
Item item;
// Stack [ 주소(4~8) ] -> Heap [ type(4) dbid(4) dummy(4096) ]
Item* item2 = new Item();
TestItem(item);
TestItem(*item2);
TestItemPtr(&item);
TestItemPtr(item2);
// 메모리 누수(Memory Leak) -> 점점 가용 메모리가 줄어들어서 Crash
delete item2;
}
// 배열
{
cout << "-----------------------------" << endl;
// 진짜 아이템이 100개 있는 것 (스택 메모리에 올라와 있는)
Item item3[100] = {};
cout << "-----------------------------" << endl;
// 아이템을 가리키는 바구니가 100개. 실제 아이템은 1개도 없을 수도 있음.
Item* item4[100] = {};
for (int i = 0; i < 100; i++)
item4[i] = new Item();
cout << "-----------------------------" << endl;
for (int i = 0; i < 100; i++)
delete item4[i];
cout << "-----------------------------" << endl;
}
// 연관성이 없는 클래스 사이의 포인터 변환 테스트
{
// Stack [ 주소 ] -> Heap [ _hp(4) ]
Knight* knight = new Knight();
// 암시적으로는 NO
// 명시적으로는 OK
// Stack [ 주소 ]
Item* item = (Item*)knight;
item->_itemType = 2;
item->_itemDbId = 1; // MSVC Debug 모드에서 메모리 시작, 끝 0xfdfdfdfd.
delete knight; // 여기서 터짐. HEAP CORRUPTION 0xfdfdfdfd가 1로 덮어져 있으니까.
}
// 부모 -> 자식 변환 테스트
{
Item* item = new Item();
Weapon* weapon = item; // 컴파일 에러
Weapon* weapon = (Weapon*)item;
weapon->_damage = 10;
delete item; // HEAP CORRUPTION
}
// 자식 -> 부모 변환 테스트
{
Weapon* weapon = new Weapon();
Item* item = weapon;
delete weapon;
}
// 명시적으로 타입 변환할 때는 항상 조심해야 한다!
// 암시적으로 될 때는 안전하다?
// -> 평생 명시적으로 타입 변환(캐스팅)은 안하면 되는 거 아닌가?
Item* inventory[20] = {};
srand((unsigned int)time(nullptr));
for (int i = 0; i < 20; i++)
{
int randValue = rand() % 2; // 0 ~ 1
switch (randValue)
{
case 0:
inventory[i] = new Weapon();
break;
case 1:
inventory[i] = new Armor();
break;
}
}
return 0;
}