타입 변환

namu·2024년 3월 26일

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;

결론)
[값 타입 변환] : 진짜 비트열도 바꾸고~ 논리적으로 말이 되게 바꾸는 변환

  • 논리적으로 말이 된다? (ex. BullDog -> Dog) OK
  • 논리적으로 말이 안된다 (ex. Dog -> BullDog, Dog -> Knight) 안됨.

[참조 타입 변환] : 비트열은 냅두고 우리의 '관점'만 바꾸는 변환

  • 명시적으로는 해주긴 하는데, 말 안해도 '그냥' (암시적)으로 해주는지는 안전성 여부와 연관 있음
    -- 안전하다? (ex. BullDog -> Dog&) '그냥' (암시적으로) OK
    -- 위험하다? (ex. Dog -> BullDog&)
    --- 메모리 침범 위험이 있는 경우는 '그냥' (암시적으로) 해주진 않음 (위험하니까)
    --- 명시적으로 정말 정말 하겠다고 최종 서명을 하면 OK

타입 변환 (포인터)

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;
}
profile
안녕하세요

0개의 댓글