올바른 사용법은 무엇입니까?
static_cast
dynamic_cast
const_cast
reinterpret_cast
- C 스타일 캐스트
(type)value
- 함수형 캐스트
type(value)
어떤 경우에 어떤 것을 사용할지 어떻게 결정합니까?
올바른 사용법은 무엇입니까?
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
어떤 경우에 어떤 것을 사용할지 어떻게 결정합니까?
답변:
static_cast
첫 번째 캐스트입니다. 유형 간 암시 적 변환 (예 : 또는 포인터 ) 과 같은 작업 int
을 수행 float
하며 void*
명시 적 변환 함수 (또는 암시 적 함수)를 호출 할 수도 있습니다. 많은 경우 명시 적으로 언급 static_cast
할 필요는 없지만 T(something)
구문은 동일 (T)something
하며 피해야합니다 (나중에 자세히 설명). T(something, something_else)
그러나 A 는 안전하며 생성자를 호출하도록 보장됩니다.
static_cast
상속 계층을 통해 캐스트 할 수도 있습니다. 위쪽으로 (기본 클래스쪽으로) 캐스팅 할 때는 필요하지 않지만 아래쪽으로 캐스팅 할 때는 virtual
상속을 통해 캐스팅하지 않는 한 사용할 수 있습니다 . 그러나 검사는 수행하지 않으며 static_cast
계층을 실제로 개체 유형이 아닌 유형 으로 다운시키는 정의되지 않은 동작 입니다.
const_cast
const
변수 를 제거하거나 추가 하는 데 사용할 수 있습니다 . 다른 C ++ 캐스트는 그것을 제거 할 수 없습니다 (조차도 reinterpret_cast
). 이전 const
변수 를 수정 하면 원래 변수가 다음과 같은 경우에만 정의되지 않습니다 const
. 로 const
선언되지 않은 것에 대한 참조를 해제하는 데 사용하면 const
안전합니다. const
예를 들어 에 따라 멤버 함수를 오버로드 할 때 유용 할 수 있습니다 . const
멤버 함수 오버로드를 호출하는 것과 같이 객체 에 추가 하는 데 사용될 수도 있습니다 .
const_cast
volatile
덜 일반적이지만 에서도 비슷하게 작동합니다 .
dynamic_cast
다형성 처리에만 사용됩니다. 다형성 유형에 대한 포인터 또는 참조를 다른 클래스 유형으로 캐스트 할 수 있습니다 (다형성 유형에는 선언되거나 상속 된 가상 함수가 하나 이상 있습니다). 아래쪽으로 캐스팅하는 것 이상으로 사용할 수 있습니다. 옆으로 캐스팅하거나 다른 체인에 캐스팅 할 수도 있습니다. 는 dynamic_cast
원하는 개체를 추구하고 가능하다면 그것을 반환합니다. 그것이 불가능 nullptr
하면 포인터의 경우에 반환 되거나 std::bad_cast
참조의 경우에 던져 집니다.
dynamic_cast
그러나 몇 가지 제한 사항이 있습니다. 상속 계층 구조에 동일한 유형의 객체가 여러 개 있고 (소위 '두려운 다이아몬드') virtual
상속을 사용하지 않는 경우에는 작동하지 않습니다 . 그것은 또한 공공 상속을 통해서만 갈 수 있습니다-그것은 항상 여행 protected
이나 private
상속을 통과하지 못할 것 입니다. 그러나 이런 형태의 상속이 드물기 때문에 이것은 거의 문제가되지 않습니다.
reinterpret_cast
가장 위험한 캐스트이며 매우 드물게 사용해야합니다. 한 포인터에서 다른 포인터로 값을 캐스트하거나 포인터를 int
모든 다른 종류의 불쾌한 것들에 저장하는 것과 같이 한 유형을 다른 유형으로 직접 변환합니다 . 대부분의 단지 당신이받을 보장 reinterpret_cast
원래 형식으로 결과 다시 캐스팅 일반적으로, 당신은 동일한 가치를 얻을 것입니다 (하지만 하지 중간 형이 원래의 형태보다 작은 경우). reinterpret_cast
할 수없는 많은 전환이 있습니다. 원시 데이터 스트림을 실제 데이터로 변환하거나 정렬 된 데이터에 대한 포인터의 낮은 비트에 데이터를 저장하는 것과 같이 특히 이상한 변환 및 비트 조작에 주로 사용됩니다.
C 스타일 캐스트 와 함수 스타일 캐스트 캐스트 사용 (type)object
또는 type(object)
각각, 그리고 기능적으로 동일합니다. 성공한 다음 중 첫 번째로 정의됩니다.
const_cast
static_cast
(액세스 제한을 무시하지만)static_cast
(위 참조) const_cast
reinterpret_cast
reinterpret_cast
그런 다음 const_cast
따라서 어떤 경우에는 다른 캐스트 대신 사용할 수 있지만로 옮길 수 있기 때문에 매우 위험 할 수 있으며 reinterpret_cast
, static_cast
성공하거나 reinterpret_cast
실패 하지 않는 한 명시 적 캐스트가 필요한 경우 후자가 선호되어야합니다. . 그럼에도 불구하고 더 길고 더 명확한 옵션을 고려하십시오.
C 스타일 캐스트는 또한을 수행 할 때 액세스 제어를 무시하므로 static_cast
다른 캐스트가 수행 할 수없는 작업을 수행 할 수 있습니다. 이것은 대부분 kludge이며, 제 생각에는 C 스타일 캐스트를 피하는 또 다른 이유입니다.
const
없습니다 reinterpret_cast
" 무엇에 대해 reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
API의 불투명 한 데이터 유형 집합을 처리 할 때 종종 선택되는 무기 라고 언급 할 가치가 있습니다.
dynamic_cast
상속 계층 내에서 포인터 / 참조를 변환하는 데 사용 합니다.
static_cast
일반적인 유형 변환에 사용하십시오 .
reinterpret_cast
비트 패턴의 저수준 재 해석에 사용 합니다. 매우주의해서 사용하십시오.
const_cast
캐스팅에 사용 합니다 const/volatile
. const-incorrect API를 사용하지 않으면 이것을 피하십시오.
(위에 많은 이론적이고 개념적인 설명이 있습니다)
다음은 static_cast , dynamic_cast , const_cast , reinterpret_cast를 사용한 실제 예제 입니다.
(또한 설명을 이해하기 위해 이것을 참조하십시오 : http://www.cplusplus.com/doc/tutorial/typecasting/ )
static_cast :
OnEventData(void* pData)
{
......
// pData is a void* pData,
// EventData is a structure e.g.
// typedef struct _EventData {
// std::string id;
// std:: string remote_id;
// } EventData;
// On Some Situation a void pointer *pData
// has been static_casted as
// EventData* pointer
EventData *evtdata = static_cast<EventData*>(pData);
.....
}
dynamic_cast :
void DebugLog::OnMessage(Message *msg)
{
static DebugMsgData *debug;
static XYZMsgData *xyz;
if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
// debug message
}
else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
// xyz message
}
else/* if( ... )*/{
// ...
}
}
const_cast :
// *Passwd declared as a const
const unsigned char *Passwd
// on some situation it require to remove its constness
const_cast<unsigned char*>(Passwd)
reinterpret_cast :
typedef unsigned short uint16;
// Read Bytes returns that 2 bytes got read.
bool ByteBuffer::ReadUInt16(uint16& val) {
return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
static_cast<char*>(&val)
? 를 사용하는 것과 동일하지 않습니까?
static_cast
정의 된 변환이 포함 된 유형, 상속에 의한 가시적 관계 또는 ~에서 /로만 작동합니다 void *
. 다른 모든 것에는 다른 캐스트가 있습니다. reinterpret cast
모든 char *
유형의 객체는 객체의 표현을 읽을 수 있으며 키워드가 유용한 유일한 구현 중 하나이며 구현 / 정의되지 않은 동작의 생성자가 아닙니다. 그러나 이것은 '정상적인'전환으로 간주되지 않으므로 (보통) 매우 보수적 인 사람들에게는 허용되지 않습니다.static_cast
.
내부 정보를 조금만 아는 경우 도움이 될 수 있습니다 ...
static_cast
static_cast
그들을 위해 사용하십시오 .A
하는 B
, static_cast
통화B
의 생성자는 전달 A
PARAM한다. 또는 A
변환 연산자 (예 :)를 가질 수 있습니다 A::operator B()
. 경우 B
이러한 생성자가없는, 또는 A
변환 연산자가 없습니다, 당신은 컴파일 타임 오류가 발생합니다.A*
로는 B*
A와 B가 상속 계층 (또는 무효)에있는 경우 항상 그렇지 않으면 컴파일 에러가 성공합니다.A&
하다 B&
.dynamic_cast
(Base*)
할 (Derived*)
수 있습니다.A*
에 B*
주조 인 경우, 무효 다음 dynamic_cast는이 nullptr 반환합니다.A&
에 B&
캐스트가 유효하지 않은 경우 다음 dynamic_cast는이 bad_cast 예외를 throw합니다.const_cast
set<T>
는 키를 변경하지 않도록 요소로만 요소를 반환하는 컨테이너를 반복 하는 것입니다. 그러나 객체의 키가 아닌 멤버를 수정하려는 의도라면 괜찮을 것입니다. const_cast를 사용하여 constness를 제거 할 수 있습니다.T& SomeClass::foo()
뿐만 아니라 const T& SomeClass::foo() const
. 코드 중복을 피하기 위해 한 함수의 값을 다른 함수에서 반환하기 위해 const_cast를 적용 할 수 있습니다.reinterpret_cast
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.
운이 좋으면 런타임에 segfault가 발생할 수있는 UB를 얻습니다. 2. 다이나믹 캐스트는 크로스 캐스팅에도 사용될 수 있습니다. 3. 경우에 따라 캐스트 캐스팅으로 인해 UB가 발생할 수 있습니다. 사용하는 것은 mutable
논리적 const와를 구현하기 위해 더 나은 선택이 될 수 있습니다.
mutable
크로스 캐스팅 등의 다른 모든 합병증으로 압도하고 싶지 않았습니다 .
이것이 귀하의 질문에 대답 합니까 ?
나는 결코 사용하지 않았으며 reinterpret_cast
, 그것이 필요한 경우가 나쁜 디자인의 냄새가 아닌지 궁금합니다. 내가 작업하는 코드베이스 dynamic_cast
에서 많이 사용됩니다. 과의 차이는 static_cast
A가 있다는 것입니다 dynamic_cast
할 수있다 (안전) 또는 수도 없습니다 (오버 헤드)를 사용하면 (참조 원하는 것을 할 수 런타임 검사 수행 MSDN을 ).
reinterpret_cast
배열에서 데이터 조각을 추출 하는 데 사용 합니다. 예를 들어 char*
패킹 된 이진 데이터로 가득 찬 큰 버퍼 가 들어있는 경우 다양한 유형의 개별 프리미티브를 통해 이동해야합니다. 이와 같은 것 :template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
, 그다지 많이 사용되지는 않습니다.
reinterpret_cast
한 가지 이유로 만 사용 된 적이 있습니다 . 데이터베이스의 "blob"데이터 유형에 저장된 원시 오브젝트 데이터를 보았으며 데이터베이스에서 데이터를 검색 reinterpret_cast
할 때이 원시 데이터를 오브젝트로 변환하는 데 사용됩니다.
지금까지의 다른 답변 외에도 static_cast
충분하지 않아 필요한 것이 아닌 명백한 예가 reinterpret_cast
있습니다. 출력 매개 변수에 다른 클래스 (공통 기본 클래스를 공유하지 않는)의 오브젝트에 대한 포인터를 리턴하는 함수가 있다고 가정하십시오. 그러한 함수의 실제 예는 CoCreateInstance()
(마지막 매개 변수를 참조하십시오 void**
). 이 함수에서 특정 클래스의 객체를 요청한다고 가정하면 포인터의 유형 (COM 객체에 대해 자주 사용하는 유형)을 미리 알고 있습니다. 이 경우에 포인터로 포인터를 캐스팅 할 수 void**
와 static_cast
당신이 필요합니다 reinterpret_cast<void**>(&yourPointer)
.
코드에서 :
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
//static_cast<void**>(&pNetFwPolicy2) would give a compile error
reinterpret_cast<void**>(&pNetFwPolicy2) );
그러나 static_cast
간단한 포인터 (포인터에 대한 포인터가 아닌)에 대해서는 작동하므로 다음 코드를 피하기 위해 위의 코드를 다시 작성할 수 있습니다 reinterpret_cast
(추가 변수의 가격으로).
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
&static_cast<void*>(pNetFwPolicy2)
대신 대신 작동하지 static_cast<void**>(&pNetFwPolicy2)
않습니까?
다른 답변 멋지게 C ++ 캐스트 사이의 모든 차이를 설명하는 동안, 나는 당신이 C 스타일 캐스트를 사용하지 말아야하는 이유 짧은 노트 추가하고 싶은 (Type) var
와 Type(var)
.
C ++ 초보자의 경우 C 스타일 캐스트는 C ++ 캐스트 (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ())에 대한 수퍼 세트 작업 인 것처럼 보이고 누군가 C ++ 캐스트보다 선호 할 수 있습니다. . 실제로 C 스타일 캐스트는 슈퍼 세트이며 작성하기가 더 짧습니다.
C 스타일 캐스트의 주요 문제점은 캐스트의 개발자의 의도를 숨기고 있다는 것입니다. C 스타일 캐스트는 static_cast <> () 및 dynamic_cast <> ()에 의해 수행되는 일반적으로 안전한 캐스트에서 const_cast <> ()와 같은 잠재적으로 위험한 캐스트에 이르기까지 거의 모든 유형의 캐스트를 수행 할 수 있습니다. 정수 값을 포인터로 재 해석 할 수있는 수정 및 reinterpret_cast <> ()
샘플은 다음과 같습니다.
int a=rand(); // Random number.
int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.
int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.
int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.
*pa4=5; // Program crashes.
C ++ 캐스트가 언어에 추가 된 주된 이유는 개발자가 자신의 의도를 명확히 할 수 있도록했기 때문입니다. C ++에서 완벽하게 유효한 C 스타일 캐스트를 사용하면 코드를 작성하지 않은 다른 개발자에게 코드를 읽기 어렵고 오류가 발생하기 쉽습니다. 따라서 코드를 더 읽기 쉽고 명확하게하려면 항상 C 스타일 캐스트보다 C ++ 캐스트를 선호해야합니다.
Bjarne Stroustrup (C ++의 저자) 저서 C ++ Programming Language 4th edition-302 쪽에서 인용 한 내용은 다음과 같습니다.
이 C 스타일 캐스트는 큰 프로그램에서 표기법을 찾기가 더 어렵고 프로그래머가 의도 한 변환 종류가 명시 적이 지 않기 때문에 명명 된 변환 연산자보다 훨씬 위험합니다.
이해하기 위해 아래 코드 스 니펫을 고려하십시오.
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
라인 (4) 만 오류없이 컴파일됩니다. reinterpret_cast 만 사용하여 객체에 대한 포인터를 관련이없는 객체 유형에 대한 포인터로 변환 할 수 있습니다.
한 가지 주목해야이는 다음과 같습니다 dynamic_cast는이 그러나 대부분의 컴파일러에 그것은 또한 의미 주조되는 포인터의 구조체에는 가상 함수가 없기 때문에 컴파일 실패, 실행시에 실패 dynamic_cast는이 단지 다형성 클래스 포인터와 함께 작동합니다 .
C ++ 캐스트 사용시기 :
static_cast
다운 캐스트 / 업 캐스트에서 vs dynamic_cast
vs reinterpret_cast
내부보기
이 답변에서는 구체적인 업 캐스트 / 다운 캐스트 예제에서이 세 가지 메커니즘을 비교하고 기본 포인터 / 메모리 / 어셈블리에 어떤 일이 발생하는지 분석하여 이들이 어떻게 비교되는지에 대한 구체적인 이해를 제공하고자합니다.
나는 이것이 캐스트가 어떻게 다른지에 대한 좋은 직감을 줄 것이라고 믿습니다.
static_cast
: 런타임시 하나의 주소 오프셋 (런타임 영향이 낮음)을 수행하며 다운 캐스트가 올바른지 안전성을 확인하지 않습니다.
dyanamic_cast
:와 같은 런타임시 동일한 주소 오프셋을 수행 static_cast
하지만 RTTI를 사용하여 다운 캐스트가 올바른지 고가의 안전 검사를 수행합니다.
이 안전 점검을 통해 런타임에 nullptr
유효하지 않은 다운 캐스트를 나타내는 리턴 값을 확인하여 기본 클래스 포인터가 런타임에 지정된 유형인지 조회 할 수 있습니다 .
따라서 코드에서이를 확인 nullptr
하고 유효한 중단되지 않은 조치를 수행 할 수없는 경우 static_cast
동적 캐스트 대신 사용해야 합니다.
중단이 코드에서 수행 할 수있는 유일한 작업 인 경우 dynamic_cast
디버그 빌드 ( -NDEBUG
) 만 활성화하고 static_cast
다른 방법 (예 : 여기 에서 수행 한 것처럼)을 사용 하여 빠른 실행 속도를 늦추지 않을 수 있습니다.
reinterpret_cast
: 주소 오프셋조차도 런타임에 아무 것도 수행하지 않습니다. 포인터는 기본 클래스가 아니라 올바른 유형을 정확하게 가리켜 야합니다. 원시 바이트 스트림이 포함되지 않는 한 일반적으로 이것을 원하지 않습니다.
다음 코드 예제를 고려하십시오.
main.cpp
#include <iostream>
struct B1 {
B1(int int_in_b1) : int_in_b1(int_in_b1) {}
virtual ~B1() {}
void f0() {}
virtual int f1() { return 1; }
int int_in_b1;
};
struct B2 {
B2(int int_in_b2) : int_in_b2(int_in_b2) {}
virtual ~B2() {}
virtual int f2() { return 2; }
int int_in_b2;
};
struct D : public B1, public B2 {
D(int int_in_b1, int int_in_b2, int int_in_d)
: B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
void d() {}
int f2() { return 3; }
int int_in_d;
};
int main() {
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1, 2, 3};
// The memory layout must support the virtual method call use case.
b2s[0] = &b2;
// An upcast is an implicit static_cast<>().
b2s[1] = &d;
std::cout << "&d " << &d << std::endl;
std::cout << "b2s[0] " << b2s[0] << std::endl;
std::cout << "b2s[1] " << b2s[1] << std::endl;
std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;
// Now for some downcasts.
// Cannot be done implicitly
// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
// dp = (b2s[0]);
// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp = static_cast<D*>(b2s[0]);
std::cout << "static_cast<D*>(b2s[0]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = static_cast<D*>(b2s[1]);
std::cout << "static_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Segfault because dp is nullptr.
dp = dynamic_cast<D*>(b2s[0]);
std::cout << "dynamic_cast<D*>(b2s[0]) " << dp << std::endl;
//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = dynamic_cast<D*>(b2s[1]);
std::cout << "dynamic_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Undefined behaviour to an unrelated memory address because this
// did not calculate the offset to get from B2* to D*.
dp = reinterpret_cast<D*>(b2s[1]);
std::cout << "reinterpret_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}
다음을 사용하여 컴파일, 실행 및 분해하십시오.
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
어디는 setarch
되고 비활성화 ASLR에 사용 쉽게 실행을 비교할 수 있도록.
가능한 출력 :
&d 0x7fffffffc930
b2s[0] 0x7fffffffc920
b2s[1] 0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0]) 0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d 1
static_cast<D*>(b2s[1]) 0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d 3
dynamic_cast<D*>(b2s[0]) 0
dynamic_cast<D*>(b2s[1]) 0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767
이제 https://en.wikipedia.org/wiki/Virtual_method_table 에서 언급했듯이 가상 메소드 호출을 효율적으로 지원하려면 메모리 데이터 구조가 D
다음과 같아야합니다.
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
D:
+0: pointer to virtual method table of D (for B1)
+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)
+12: value of int_in_b2
+16: value of int_in_d
핵심 사실은 D
내부 의 메모리 데이터 구조와 내부 의 메모리 데이터 구조 와 호환되는 메모리 구조 를 포함 B1
한다는 것입니다 B2
.
따라서 우리는 중요한 결론에 도달합니다.
업 캐스트 또는 다운 캐스트는 컴파일 타임에 알려진 값만큼 포인터 값만 이동하면됩니다.
이 방법으로 D
기본 유형 배열에 전달되면 유형 캐스트는 실제로 해당 오프셋을 계산하고 B2
메모리 에서 유효한 것처럼 보이는 것을 가리 킵니다 .
b2s[1] = &d;
이 D
대신에 vtable이 B2
있기 때문에 모든 가상 통화는 투명하게 작동합니다.
이제 타입 캐스팅과 구체적인 예제 분석으로 돌아갈 수 있습니다.
stdout 출력에서 다음을 볼 수 있습니다.
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
따라서 암시 적으로 static_cast
수행 된 전체 D
데이터 구조에서 0x7fffffffc930 의 오프셋 B2
과 0x7fffffffc940 의 오프셋까지 올바르게 계산했습니다 . 또한 0x7fffffffc930과 0x7fffffffc940 사이에있는 것이 B1
데이터와 vtable 일 가능성이 있다고 추론합니다 .
그런 다음 다운 캐스트 섹션에서 이제 유효하지 않은 섹션이 실패하는 이유와 이유를 쉽게 이해할 수 있습니다.
static_cast<D*>(b2s[0]) 0x7fffffffc910
: 컴파일러는 컴파일 타임 바이트에서 0x10으로 올라가서 a B2
에서 포함으로 이동했습니다.D
그러나이 b2s[0]
아니 었으므로 D
이제 정의되지 않은 메모리 영역을 가리 킵니다.
분해는 다음과 같습니다.
49 dp = static_cast<D*>(b2s[0]);
0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax
0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433>
0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax
0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438>
0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)
우리는 GCC가 다음을 수행한다는 것을 알 수 있습니다.
D
존재하지 않는 것에 도달하기 위해 0x10을 빼십시오.dynamic_cast<D*>(b2s[0]) 0
: C ++은 실제로 캐스트가 유효하지 않고 리턴되었음을 발견했습니다 nullptr
!
컴파일 타임 에이 작업을 수행 할 수있는 방법이 없으며 분해에서 확인합니다.
59 dp = dynamic_cast<D*>(b2s[0]);
0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax
0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744>
0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx
0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D>
0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2>
0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi
0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt>
0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749>
0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)
먼저 NULL 검사가 있으며 입력이 NULL이면 NULL을 반환합니다.
그렇지 않으면 RDX, RSI 및 RDI에서 일부 인수를 설정하고을 호출합니다 __dynamic_cast
.
나는 이것을 더 분석 할 인내가 없지만, 다른 사람들이 말했듯이, 이것이 작동하는 유일한 방법 __dynamic_cast
은 클래스 계층을 나타내는 여분의 RTTI 인 메모리 데이터 구조에 액세스하는 것입니다.
따라서 B2
해당 테이블 의 항목에서 시작한 다음 D
typecast에 대한 vtable이에서 발견 될 때까지이 클래스 계층 구조를 따라 가십시오 b2s[0]
.
이것이 재 해석 캐스트가 잠재적으로 비싼 이유입니다! 다음은 복잡한 프로젝트에서 하나의 라이너 패치 dynamic_cast
를 사용하여 static_cast
런타임을 33 % 단축 한 예입니다! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
이것은 우리를 맹목적으로 믿습니다. D
at address 가 있다고 말했고 b2s[1]
컴파일러는 오프셋 계산을하지 않습니다.
그러나 D는 실제로 0x7fffffffc930에 있기 때문에 잘못되었습니다. 0x7fffffffc940은 D 내부의 B2와 같은 구조입니다! 휴지통에 액세스합니다.
-O0
값을 이동시키는 끔찍한 어셈블리 에서이를 확인할 수 있습니다 .
70 dp = reinterpret_cast<D*>(b2s[1]);
0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax
0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)
관련 질문 :
Ubuntu 18.04 amd64, GCC 7.4.0에서 테스트되었습니다.