반응형
- 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 (1) | 2024.01.26 |
---|---|
static_cast (0) | 2024.01.26 |