본문 바로가기

고급C,C++

데이터형 변환 연산자

데이터형 변환 과정을 더욱 엄격하게 규정한다.
dynamic_cast
const_cast
static_cast
reinterpret_cast



  • dynamic_cast
    • 어떤 클래스 계층 내에서 업캐스트를 허용하고, 다른 데이터형 변환은 허용하지 않는것
    • dynamic_cast <type_name> (expression)
  • const_cast
    • 어떤 값을 const나 volatile로 또는 그 반대로 변경하는 것이 유일한 목적인 데이터형 변환을 위한 것이다.
    • const_cast <type-name> (expression)
      이와 같은 데이터형 변환의 결과는 그 데이터형의 다른 어떤 측면이 변경되면 에러이다. 즉, type_name과 expression은 const나 volatile이 있느냐 없느냐에 따라 달라지는 것만 빼고는, 데이터형이 동일해야 한다. 예를 들어보자.
      High bar;
      const High * pbar = &bar;
      ...
      High * pb = const_cast<High*>(pbar); //맞다
      const Low * pl = const_cast<const Low *>(pbar); //틀리다.

#include <iostream>
using namespace std;
int main() {
const int i = 0;
//int* j = (int*)&i;
int *j  = const_cast<int*>(&i);  
*j = 5;

cout<<*j<<" "<<i<<endl;
cout<<j<<" "<<&i<<endl;
}
/*
i 와 j가 같은 메모리 영역을 가리키고 있음에도 불구하고 값이 다르다.
컴파일러 최적화 때문이군요.
시험삼아 다음과 같은 코드를 디스어셈블했습니다.
int i = 3;
const int c = 3;
cout << i << " " << c << endl;
0041B1DC  push        offset std::endl (4194BAh)       ; endl을 스택에 추가
0041B1E1 push 3 ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 3을 스택에 추가
0041B1E3  push        offset string " " (44E0C8h)       ; " "을 스택에 추가
0041B1E8  mov         eax,dword ptr [i]
0041B1EB  push        eax                                       ; i값을 eax에 넣은 후 스택에 추가
...
즉 const가 아닌 i값은 i변수의 값을 찾아 스택에 넣지만 const로 정의된 c값은 아예 상수 3을
스택에 넣는군요. 즉 const가 붙은 변수가 있으면, 컴파일러는 그 변수값이 변하지 않으리라 
판단하고, 그 변수가 사용된 곳을 찾아 모두 상수로 바꾸어 버립니다. 어셈블러루틴을 보면
알겠지만, 변수값을 꺼내서 스택에 넣는 것은 2단계, 상수를 직접 넣는 것은 1단계로 끝나니까요.
그러니 억지로 캐스팅해서 const변수값을 바꾸더라도 최초의 값으로 사용되는 것이죠.
volatile const int c = 3; 한다면 값이 같다는것을 볼 수 있다. 결국 최적화 문제..,
하지만 포인터일때는 잘 바뀜...
*/


#include <iostream>
#include <cstring>

void change(const int * pt, int n);
int main()
{
using namespace std;

char *pc;
const char msg[] = "안눙~";
// pc = msg;    // 컴파일 에러 발생
pc = const_cast<char*>(msg);  // 정상적으로 변환
strcpy( pc, "hello" );
puts( pc );
puts(msg);
std::cin.get();

volatile const int data = 10;
int * p = const_cast<int*>(&data);
*p = 200;
std::cout<<data<<std::endl;
std::cout<<*p<<std::endl;
std::cin.get();

const int * pConst_int = new int(10);
int * pInt = const_cast<int*>(pConst_int);
*pInt = 200;
cout<<*pConst_int<<endl;
cout<<*pInt<<endl;
std::cin.get();

/*
using namespace std;

int pop1 = 38383;
const int pop2 = 2000;

cout<<"pop1, pop2: "<<pop1<<","<<pop2<<endl;
change(&pop1,-1);
change(&pop2,-1);
cout<<"pop1, pop2 : "<<pop1<<","<<pop2<<endl;

cin.get();
return 0;*/

}

void change(const int * pt, int n)
{
int * pc;
if(n<0)
{
pc = const_cast<int *>(pt);
*pc = 100;
}
}
//디버깅으로 잘 확인해 보라!!
const_cast연산자는 const int * pt로부터 const를 제거할 수 있다. 그 결과로 컴파일러는 change()에 있는 다음과 같은 명령문을 받아들인다.
*pc = 100;
그러나 pop2는 const로 선언되어 있기 때문에, 컴파일러가 어떤 변경도 할 수 없도록 막는다.

  • static_cast
    • static_cast <type_name>(expression)
    • type_name이 그 expression이 가지는 데이터형으로 암시적으로 변환 될 수 있을 때만 유효하다. 그렇지 않은 경우에 그 데이터형 변환은 에러이다.
      High가 Low의 기초 클래스이고, Pond가 이들과 관련이 없는 클래스라고 가정하다. High에서 Low로 그리고 Low에서 High로 변환하는 것은 유효하다. 그러나 Low에서 Pond로 변환하는 것은 허용되지 않는다.

      High bar;
      Low blow;
      High * pb = static_cast<High *>(&blow); //유효한 업캐스트
      Low * pl = static_cast<High *>(&bar); //유효한 다운캐스트
      Pond * pmer = static_cast<Pond *> (&blow); //유효하지 않다. Pond는 관련이 없다.

      여기서 첫 번째 변환은, 업캐스트가 명시작으로 이루어질 수 있으므로 유효하다. 기초 클래스 포인터를 파생 클래스 포인터로 변환하는 두 번째 변환은, 데이터형 변환이 없으면 이루어질 수 없다. 그러나 그 반대로의 데이터형 변환은 명시적인 데이터형 변환이 없어도 이루어질 수 있으므로, 다운캐스트를 위해 static_cast를 사용하는 것이 유효하다.

일반적으로 C/C++ 언어에서는 지정한 데이터형으로 강제 형변환(casting)이 가능하다.

하지만 강제 형변환을 할 경우 원하지 않는 결과가 발생할 수 있으므로, 이런 문제를 미연에 방지하기 위해

static_cast를 사용하며, static_cast는 의미 없는 포인터의 형변환을 막아 준다. 사용 예는 다음과 같다.

 

#include <stdio.h>

#include <typeinfo>

 

class T1
{
public:
    virtual void print1() { puts( "t1p1" ); }
};


class T2 : public T1
{
public:
    virtual void print1() { puts( "t2p1" ); }
    virtual void print2() { puts( "t2p2" ); }
};


void main()
{
    T1 *t1 = new T1;
    T2 *t2 = new T2;
    T1 *t;

    t = (T1*)t1;      // C 언어 스타일의 형변환(casting), 컴파일 에러 없음

    t  = static_cast<T1*>(t1);    // C++ 스타일의 형변환(casting), 컴파일 에러 없음
    t  = static_cast<T1*>(t2);    // Up-casting, 컴파일 에러 없음
    t2 = static_cast<T2*>(t1);   // Down-casting, 컴파일 에러 없음

 

    int i, *pi;
    pi = (int*)&i;   // C 언어 스타일의 형변환, 컴파일 에러 없음
    pi = static_cast<int*>(&i);  // C++ 스타일의 형변환, 컴파일 에러 없음

 

    pi = (int*)t;      // C 스타일, 컴파일 에러 없음
    pi = static_cast<int*>(t);  // C++ 스타일, 컴파일 에러 발생
}



  • reinterpret_cast
    • reinterpret_cast 연산자는 본래부터 위험한 데이터형 변환을 하기 위한 것이다. 이 연산자는 const를 버리는 것을 허용하는 것이 아니라, 다른 편법적인 일을 수행한다. 가끔 프로그래머는 시스템에 의존적인 다소 편법적인 처리를 해야한다. reinterpret_cast 연산자는 그러한 처리를 보다 쉽게 추적할 수 있게 한다.
    • reinterpret_cast<type_name>(expression)

    • struct dat{short a; short b};
      long value = 0xA224B118;
      dat * pd = reinterpret_cast<dat*>(&value);
      cout<<pd->a; //그 값의 처음 2바이트를 출력한다.
    • 1234568이라는 정수형값을 정수형 포인터로 바꾸어 pi에 대입 할수도 있고 이 값을 다시 문자형 포인터로 바꾸어 pc에 대입할수도 있다

      상속 관계에 있지 않은 포인터 끼리도 변환가능하다 대입을 허가하기는 하지만 이렇게 대입한후 pi,pc 포인터를 사용해서 발생하는 문제는 전적으로 개발자가

      책임져야 한다 일종의 강제 변환이므로 안전하지 않고 이식성도 없다

       이 연산자는 포인터 타입간의 변환이나 포인터와 수치형 데이타 변환에만 사용하며 기본타입들끼리의 변환에는 사용할수 없다 예를 들어 정수형을

      실수형으로 바꾸거나 실수형을 정수형으로 바꾸는 것은 허락하지 않는다.  이럴때는 static_cast연산자를 사용한다. 또한 함수포인터를 데이터 포인터로 또는 데이터 포인터를 함수포인터로 캐스트 할 수 없다.