2016. 7. 27. 12:49



C++ 캐스팅(cast)에는 총 4가지 종류가 있습니다.


바로


static_cast

reinterpret_cast

const_cast

dynamic_cast 

입니다.


각각 무슨 기능을 하는지 알아 보자면


 

 static_cast


명시적 형 변환을 위한 캐스트 연산자


 reinterpret_cast


무조건 적으로 변환한다.(강제 변환)  ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다.


 const_cast

 

const 즉 상수성을 제거 할때 사용됩니다.


 dynamic_cast 

 

RTTI( Run Time Type Information )를 위한 캐스팅 입니다.



하지만, 굳이 귀찮게 위의 방법을 써야하는 이유가 뭘까요?


저희가 C언어 쓸때는 그냥 (Type) 으로 캐스팅 연산을 했었습니다.


다음과 같은 경우를 봅시다.


1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
 
int main()
{
 
    int m_4byte = 0;
    double* m_pointer = (double*)&n;
    *m_pointer = 1.23;
 
    return -1;
}
cs


무엇이 문제 인지 보이시나요?


위와 같은 방법을 사용하면 어떻게 될까요?


에러가 납니다.  직접 VS를 열어서 빌드해보세요.


그럼 다음 경우를 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
 
int main()
{
 
    
    const int c_int = 50;
    int * p_int = (int*)&c_int;
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}
cs

다음과 같이 코드를 작성 한 후 실행하면 어떤 결과가 나올까요?
---------------------------
50
100
---------------------------
이 나오게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
 
int main()
{
 
    
    int value = 50;
    const int c_int = value;
    int * p_int = (int*)&c_int;
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}
cs


하지만 다음과 같이 코드를 작성 한 후 실행 하게되면

-------------------------
100
100
-------------------------


이 나오게 됩니다.


이는 const의 값이 결정되는 시점에 따라 달라집니다만, 그건 나중에 const에 관련 글을 적을때 적도록 하겠습니다.


이 2개의 코드로 알아야 하는것은 C에서 지원하는 cast 방식은, 불안전 하다는 것입니다.


2개의 코드 다 에러를 나타내지 않습니다. 하지만 비정상적인 결과를 출력하죠.


과연 이게 올바른 걸까요?


그럼 static_cast로 변경을 해봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
int main()
{
    int value = 50;
    const int c_int = value;
    int * p_int = static_cast<int*>(&c_int);
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}

cs


컴파일러는 const 또는 다른 형식 한정자로 캐스팅 할수없습니다. 라며 에러를 띄우고 빌드를 하지 않습니다.

불안전 하기 때문이죠.


만약 const값을 변하게 하려는 의도가 있다면, const_cast를 사용해야 할것입니다.


그렇기에 안정하고 정상적인 캐스팅을 위하면 C++에서는 기본적으로 명시적캐스팅인 static_cast를 사용하도록 합시다.


다음은 reinterpret_cast 입니다.


reinterpret_cast는 제가 위에 이렇게 적어 놨을겁니다.


무조건 적으로 변환한다.(강제 변환)  ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다.


여기서 중요하게 보셔야 할 부분은 '메모리' 단위 입니다.


메모리 단위란 무엇일까요?


그럼 이 코드를 자세히 봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    return -1;
}
cs


위의 코드에서 class C는 class A와 B를 다중 상속 받은 상태입니다. 그리고 A와 B는 각각 4byte짜리의 int를 가지고 있죠.

그럼 C를 선언 하는 순간 메모리는 어떻게 잡힐까요?


Class C

 Class A

 int a;

 4 Byte

 Class B

 int b;

 4 Byte

 C클래스 멤버

 int c;

 4 Byte


다음과 같이 구조를 가질껍니다.


그럼 위와 같은 메모리 구조를 가졌을때 아래와 같이 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    A* p_a = &c;
 
    cout << &<< end;    
    cout << p_a << end;
 
    return -1;
}
cs


코드를 작성 한후 실행하면 2개의 결과 값은 똑같이 나올겁니다.


하지만 이 코드를 어떨까요?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    B* p_b = &c;
 
    cout << &<< end;    
    cout << p_b << end;
 
    return -1;
}
cs


C클래스가 부모인 B클래스로 암시적 캐스팅이 된 형태 입니다.


실행 하면 결과는


------------------------------------

&c의 주소값

&c의 주소값 + 4byte 

------------------------------------


암시적 변환으로 메모리의 주소값이 바뀌었습니다.


물론 자식 -> 부모 변환이라 static_cast를 사용하셔도 됩니다.


하지만 reinterpret_cast를 사용하면 어떻게 될까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    B* p_b = reinterpret_cast<B*>(&c);
 
    cout << &<< end;    
    cout << p_b << end;
 
    return -1;
}
cs

놀랍게도 2개의 출력값이 같은 주솟값을 가지게 됩니다.


메모리 단위로 분해해서 재 생성 하기 때문이죠.


이와 같은 방법을 전 아직 어디서 잘 사용해야 할지 모르겠습니다. ( 나중에 알게되면 꼭 수정하겠습니다. ) 


하지만 저기서 p_b가 가진 멤버변수인 b를 수정하게 되면 A클래스가 가진 a가 수정이 된답니다.




Class C

 Class A

 int a;

 4 Byte

 Class B

 int b;

 4 Byte

 C클래스 멤버

 int c;

 4 Byte


위와 같은 구조가 

Class C

 Class B

 int b;

 4 Byte

 Class C

 int c;

 4 Byte


 ( )

 (4 Byte )


이렇게 조립 되었을뿐 메모리 주소를 변하지 않았기 때문이죠.


전 처음 이 사실을 알았을때는 정말 신기했습니다.  잘만 사용하면 메모리 단위로 잘 쪼개서 사용 할 수 있기 때문이죠.



마지막으로 dynamic_cast입니다.


dynamic_cast는 RTTI(Runtime Type Information)를 위한 캐스팅이라고 제가 위에 적었는데요.


런타임에 상속 계츨 관계를 가로지르거나 다운 캐스팅시 사용되는 캐스팅 연산자 라고 생각하시면 편합니다.


즉, 부모가 자식으로 캐스팅이 될 수 없는데, 런타임중 판단으로 가능하게 된다는거죠.


단 dynamic_cast는 다형성을 띄지 않은 객체간의 변환은 불가능하며, 컴파일이 에러를 낸답니다.


즉 다형성을 가지려면 클래스간 virtual 멤버 함수가 있어야 합니다.


코드로 표현하면 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;
 
class A {
public
    int a; 
 
    virtual void test(){}
};
 
class C : public A
{
    int c;
    virtual void test(){}
};
int main()
{
    C c;
    A* pA = &c; 
    C* c2 = dynamic_cast<C*>(pA);
    
    return -1;
}
cs


하지만 저 dynamic_cast가 올바르지 않았을경우는 null값을 반환하니 주의 하시기 바랍니다.


읽어주셔서 감사합니다

Posted by 시리시안

댓글을 달아 주세요