본문 바로가기
IT/C++(CPP)

CPP 초급 강좌 22. C++ Explict type conversion(casting)

by 신림83 2020. 11. 1.
반응형

CPP 초급 강좌 22. C++ Explict type conversion(casting)

c++ 에서의 명시적 형 변환에 대해 적어보겠습니다.

 

코드를 봅시다.

int* p = malloc(sizeof(int) * 100);

int* p에 인트 100개의 매모리 공간을 할당하는 코드입니다. 해당 코드는 정상 동적하지 않습니다. 왜냐하면 malloc 의 반환형이 void* 이기 때문이죠.

 

c 스타일 코딩이라면 다음과 같이 처리할 수 있습니다.

int* p = (int*)malloc(sizeof(int) * 100);

반환 타입인 void*를 int* 로 명시적으로 형변환을 하여 사용할 수 있습니다. 정상동작하고 문제없는 코드입니다.

이를 그대로 사용해도 되겠지만, c++에서는 이와 다른 명시적 형변환들을 지원합니다. 어떤 것들이 있고, 어떤 문제가 있어서 지원하게 되었는지를 보도록 합시다.

 

위의 코드를 c++ 코드로 바꾼다면, 

int* p = static_cast<int*>(malloc(sizeof(int) * 100));

void* 반환타입을 int* 으로 변환하는 c++ 캐스팅입니다. 위와 결과는 같습니다.

 

c++ 추가적인 형변환 기능을 제공하는 이유는 기존 c캐스팅은 논리적이지 않고, 위험성이 있어 의도하지 않은 버그를 만들어 낼수 있기 때문입니다.

 

형이 다른 포인트의 변환

문제있게 사용할 경우에 대해 보도록 하겠습니다. 다음 코드를 봅시다.

int n = 0;
double* pd = &n;

int n을 선언하고 double*  pd를 만들어서 연결하고자 합니다. 이상해 보이지만 저렇게 하길 원한다고 합시다.

인트는 4바이트고, double 8바이트입니다. &n으로 주소를 반환해도, 기본적으로 해당 코드를 애러입니다.

 

다음 코드와 같이 명시적으로 캐스팅한다고 합시다.

int n = 0;
double* pd = (double*)&n;

해당 코드는 정상적으로 컴파일 되고, 동작되는 코드입니다. 이는 정말 괜찮은 코드일까요?

위의 코드 이후의 코딩을 한다고 가정해 봅시다.

int n = 0;
double* pd = (double*)&n;

*pd = 999.999;

 *pd = 999.999; 코드는 정상작동 할까요? 빌드 타임에서는 문제가 없습니다. 하지만 큰 확률로 런타임에서 값을 실 적용하는 코드가 돌아갈 때, 문제의 여지가 큽니다. 실제 pd의 원본의 형태는 int 타입입니다. 기본적으로 바이트 크기 또한 차이가 나는 형태입니다. *pd의 선언이 double* 로 선언되어 있어 코딩을 가능한 코드이지만 대입시에는 실제 할당된 크기보다 큰 데이터형을 넣는 행위라 허용되는 메모리 이상을 손댈 수가 있는 코드이고, 이런 버그는 정말 잡기 힘든 버그에 속합니다.(그리고 이런 류의 버그는 실행된다고 100% 크래쉬 난다고 보장되지도 않는 코드입니다. 프로그램의 구동상태 컨티션에 따라 문제가 있지만 그냥 넘어갈 수도 있어 보입니다.)

 

 그럼 위와 같은 코딩을 무조건 하지 말아야 하는 것일까요? 그건 또 아닙니다. 코딩을 하다보면 위와 같이 기타 포인터 타입으로 변환이 필요한 순간이 종종 옵니다. 하지만 값은 대입하지 말아야 하는 것이지요.

 

 원래 코딩한 사람의 의도가 반영이 되야 하고, 사용자 들이 이해도가 있어야 하는 부분입니다. 하지만 그런 의도를 모른채 사용하면 큰 문제를 발생시킬 수 있는 것입니다.

 

const의 포인트 변환

또 다른 경우를 보겠습니다.

const int cn = 100;
int* p = &cn;

가능한 코드 일까요? 아니요 되지 안습니다. const로 선언된 형태를 형태*로 받을수가 없죠.

하지만 c 캐스팅을 쓰면 가능합니다. 다음 코드를 봅시다.

const int cn = 100;
int* p = (int*)&cn;

*p = 200;

명시적 c캐스팅을 (int*) 통하여 형 변환이 됩니다. 런타임에도 문제없이 돌아갑니다. 심지어는 포인터로 값에 접근하여 대입코드또한 문제 없이 돌아 갑니다. 정말 의도한 바일까요? 이렇게 코딩해도 될까요?

 

 c의 명시적 캐스팅을 파워가 막강합니다. 하지만 많은 기능을 가지고 있어 정확히 개발자가 의도한 바를 다른 개발자들이 알기 쉽지 않은 단점이 있습니다.

 

 그래서 c++ 에서 해결책으로 새로운 캐스팅 기능이 나오게 됩니다.

4가지의 캐스팅 연산자가 존재하며, 해당 연산자들은 확실한 용도를 가지고 있습니다.

 

c++ 의 4가지 캐스팅 연산자와 사용처를 봅시다.

static_cast

  정수-실수, 열거-정수형들의 변환,  void*->다른*, 배열-포인트, 함수->함수 포인트

 

reinterpret_cast

  서로 다른 포인터 타입 끼리의 변환, 정수-포인터 사이의 변환

 

const_cast

  상수성과 volatilie 속성을 제거하는 용도로 사용

 

dynamic_cast

  안전한 다운 캐스팅을 위해 사용, 실제 실행 시간에 캐스팅을 하며 오버헤드가 잇음

 

기본적으로 코딩은

 캐스팅연산자<원하는타입>(기존표현)

 - static_cast<int*>(&n) //요런식

 

하나씩 조금 더 자세하 봅시다.

 

static_cast

코드를 통해 설명해 보겠습니다.

void f(int n){}
void f(double d) {}

int main()
{
	int n = 10;
	const int cn = 10;
	double d = 99.99;

	int n1 = static_cast<int>(d);			//a
	int* p1 = static_cast<int*>(malloc(n));		//b

	auto p2 = static_cast<void(*)(int)>(&f);	//c

	int* p3 = static_cast<int*>(&d);		//e
	int* p4 = static_cast<int*>(&cn);		//f
}

a 가능한 코드입니다.

 실수형에서 정수형으로 변환합니다. 값 손실은 있지만 가능한 코드입니다.

 

b 가능한 코드입니다.

 void* -> int* 변환을 하는 코드입니다.

 

c 가능한 코드입니다.

 같은 이름으로 오버라이딩 된 함수중 하나를 명시적으로 표시하여 연결합니다.

 

d 불가능한 코드입니다.

 형용되지 않는 서로 다른 포인터 타입 간의 변활을 요구합니다. 이는 reinterpret_cast를 이용해야 합니다.

 

f 불가능한 코드입니다.

 cn 은 const 가 부여되어 있습니다. 이와같은 작업을 원한다면 const_cast를 이용해야 합니다.

 

reinterpret_cast

int n = 10;
double* pd = reinterpret_cast<double*>(&n);  //a

double* pd1 = reinterpret_cast<double*>(10); //b 

a 가능한 코드입니다.

 서로 다른 타입의 포인터 사이의 변환이 가능함을 보여줍니다.

 

b 가능한 코드입니다.

 정수와 포인터 사이의 변환을 보여줍니다. 어쩌다 한번씩 이런 코드가 필요한 경우가 있습니다.

 

const_cast

const int cn = 10;
volatile int vn = 10;

int* p1 = const_cast<int*>(&cn);		//a
int* p1 = const_cast<int*>(&vn);		//b

double* p3 = const_cast<double*>(&vn);          //c

a 가능한 코드입니다.

 p1은 const 상수성을 제거한 상태로 사용가능합니다.

 

b 가능한 코드입니다.

 volatile 속성을 제거한 상태로 사용가능합니다.

 

c 불가능한 코드입니다.

 서로 다른 타입의 포인터의 변환은 불가합니다.

 

dynamic_cast

 해당 캐스팅은 상속관련 내용에 대한 이해가 있어야 하므로 상속관련 글을 적은 이후에 다시 보는걸로 합시다.

 

c++ 에서 지원하는 캐스팅들은 딱 자신의 용도에서만 사용이 가능합니다.

 

한번에 뭔가 변환이 잘 안된다?

다음 코드를 봅시다.

volatile int vn = 10;
double* p3 = const_cast<double*>(&vn);  

const_cast c에서 오류가 있었던 코드입니다. 하지만 난 꼭 p3로 형 변환을 하고 싶다면 어떻게 해야 할까요?

 

해당 형 변환에는 두가지 작업 요소가 들어가 있습니다. 하나는 volatile 속성 제거이고, 또 하나는 서로 타른 타입의 포인터 변환입니다.  c++ 캐스팅을 이용하려면 두 번 캐스팅을 해줘야 합니다.

double* p3 = reinterpret_cast<double*>(const_cast<int*>(&vn)); //a

double* p3 = reinterpret_cast<double*>(
		const_cast<int*>(&vn)
		); //b

a와 b는 완전 동일한 코드입니다. b는 보기 편하시라고 좀 짤라보았습니다.

1차로 const_cast<int*>(&vn) 코드를 이용하여 volatile 속성을 제거해주고, 2차로 reinterpret_cast<double*>를 통해 서로 다른 포인터 형에 대한 변환을 해주었습니다.

 

두 작업을 순서를 바꿀수도 있습니다.

double* p3 = const_cast<double*>(reinterpret_cast<volatile double*>(&vn));

보기 쉬운 코드는 아닙니다.

 

만약 c 캐스팅 코드였다면,

double* p3 = (double*)&vn;

... c 캐스팅의 무서움을 또 한번 느낄 수 있습니다.

 

c++ 캐스팅 코드를 사용하여 코드 표현의 명확성을 올려봅시다. 

 

공부합시다. 쉽지 않네요...

봐주셔 감사합니다.

 

반응형

댓글