C++/C++ static, reinterpert, dynamic cast

reinterpret_cast

재윤 2024. 1. 26. 16:46
반응형
  • reinterpret_cast()는 static_cast()보다 안전성은 조금 떨어지며, C++에서 가장 강력하면서도 위험한 형변환 중 하나이다.
  • C++ 타입 규칙에서 허용하지 않더라도 상황에 따라 캐스팅하는 것이 적합할 때 사용할 수 있다. 예를 들어 서로 관련이 없는 레퍼런스끼리 변환할 수도 있다.
  • 주로 다른 포인터 타입 간의 형변환에 사용됨.

 

reinterpret_cast의 기본 형태

  • new_type은 형변환하고자 하는 새로운 타입을 나타내며, expression은 형변환할 값이나 포인터.
new_type = reinterpret_cast<new_type>(expression);

 

포인터 간 형변환 예제를 통해 알아보자

#include <iostream>

int main() {
    int intValue = 42;

    // int 포인터를 double 포인터로 reinterpret_cast 형변환
    int *intPointer = &intValue;
    double *doublePointer = reinterpret_cast<double*>(intPointer);

    // double 포인터를 사용하여 값 출력
    std::cout << "int 값: " << intValue << std::endl;
    std::cout << "double 값: " << *doublePointer << std::endl;

    return 0;
}

결과

jaeyojun@c3r1s6 C++ % ./a.out 
int 값: 42
double 값: 2.07508e-322

뭔가 이상한 걸 알 수가 있다. 왜 그럴까?

  • reinterpret_cast를 사용하여 서로 다른 타입의 포인터로 형변환은 포인터가 가리키는 메모리의 해석이 변경되기 때문.
  • 코드에서 int 값을 double 값으로 형변환하려고 했지만, int와 double은 메모리 구조 및 표현 방식이 다르기 때문에 이러한 형변환은 예측할 수 없는 결과를 가져올 수 있다.
  • 컴파일러 및 플랫폼에 따라 다르게 동작할 수 있으며, 또한 메모리 정렬 등에 의해 런타임 오류가 발생할 수 있다.
  • 결과값이 이쁘게 나오려면 타입 간의 변환을 명시적으로 처리하거나, 더 안전한 형변환 연산자를 사용해야한다.

결과값을 이쁘게 나오도록 만들어보자

static_cast:

#include <iostream>

int main() {

    // int를 double로 안전하게 형변환
    int intValue = 42;
    double doubleValue = static_cast<double>(intValue);

    std::cout << "int 값: " << intValue << std::endl;
    std::cout << "double 값: " << doubleValue << std::endl;

    return 0;
}

결과

jaeyojun@c3r1s6 C++ % ./a.out                     
int 값: 42
double 값: 42

 

reinterpret_cast()를 좀 더 많이 사용하는 방법을 알아보자

  • 상속 계층에서 아무런 관련이 없는 포인터 타입끼리도 변환할 수 있다.
  • 이런 포인터는 흔히 void* 타입으로 캐스팅한다. 이 작업은 내부적으로 처리되기 때문에 명시적으로 캐스팅하지 않아도 된다. 하지만 이렇게 void로 변환한 것을 다시 원래 타입으로 캐스팅할 때는 reinterpret_cast()를 사용해야 한다. **
  • void 포인터는 메모리의 특정 지점을 가리키는 포인터일 뿐 void* 포인터 자체에는 아무런 타입 정보가 없기 때문.

예제를 통해 배워보자

#include <iostream>

int main() {
    // int 타입의 변수
    int intValue = 42;

    // int 포인터를 void 포인터로 형변환
    void* voidPointer = reinterpret_cast<void*>(&intValue);

    // void 포인터를 다시 int 포인터로 형변환
    int* intPointer = reinterpret_cast<int*>(voidPointer);

    // 결과 출력
    std::cout << "원래 값: " << *intPointer << std::endl;

    return 0;
}

결과

jaeyojun@c3r1s6 C++ % c++ reinterpret_cast.cpp    
jaeyojun@c3r1s6 C++ % ./a.out 
원래 값: 42

 

위에서 본 예제를 통해 좀 더 응용할 수 있다.

main.cpp

  • 좀 더 깊게 설명해보자
  • Data 클래스가 있다. 현재 Data 클래스의 prev가 있으며, Data를 가리키는 next를 선언하였다.
  • prev._data에 초기화를 해주고 prev를 uintptr_t에 캐스팅을 해서 넣어주는데 uintptr_t 자료형은 그냥 주소값을 담아주는 자료형이라고 생각하면 된다.
  • prev의 주소값을 ptr에 담아준다(원본이 그대로 담긴다 왜? 레퍼런스 사용).
  • 그 후 Data를 가리키는 next에 ptr을 reinterpret_cast<Data *>한 결과값을 넣어준다.
  • 그러면 next가 prev와 연결된다.
#include "Data.hpp"

uintptr_t serialize(Data* ptr)
{
	return(reinterpret_cast<uintptr_t>(ptr));
}

Data* deserialize(uintptr_t raw)
{
	return(reinterpret_cast<Data *>(raw));
}

int main(void)
{
	Data prev;
	Data *next;
	uintptr_t ptr;

	prev._data = "jaeyojun";
	std::cout << "prev : " << prev._data << std::endl;

	ptr = serialize(&prev);
	std::cout << "ptr : " << ptr << std::endl;

	next = deserialize(ptr);
	std::cout << "next : " << next->_data << std::endl;

	std::cout << "=========================================\\n";

	Data d;
	Data *a;
	uintptr_t memory;

	d._data = "jaeyo";

	memory = serialize(&d);
	std::cout << "memory : " << memory << std::endl;

	a = deserialize(memory);

	std::cout << "a : " << a->_data << std::endl;

	return (0);
}

 

결과

jaeyojun@c3r1s6 ex01 % ./a.out 
prev : jaeyojun
ptr : 140732815328928
next : jaeyojun
=========================================
memory : 140732815328872
a : jaeyo
  • reinterpret_cast()가 모든 것에 강력한 것은 아니다. 무엇에 캐스팅될 수 있는지에 대한 제약이 있다. 다만 reinterpret_cast()를 사용할 때 주의해서 사용해야 하는데, 이는 reinterpret_cast()가 타입 검사를 하지 않고 변환할 수 있기 때문.
반응형

'C++ > C++ static, reinterpert, dynamic cast' 카테고리의 다른 글

dynamic_cast  (2) 2024.01.26
static_cast  (0) 2024.01.26