etc./StackOverFlow

static_cast, dynamic_cast, const_cast 및 reinterpret_cast는 언제 사용해야 합니까?

청렴결백한 만능 재주꾼 2021. 11. 9. 00:28
반응형

질문자 :e.James


다음의 적절한 용도는 무엇입니까?

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C 스타일 캐스트 (type)value
  • 함수형 캐스트 type(value)

어떤 특정한 경우에 어떤 것을 사용할지 어떻게 결정합니까?



static_cast 는 사용을 시도해야 하는 첫 번째 캐스트입니다. 유형 간의 암시적 변환(예: int 에서 float 또는 포인터 void* )과 같은 작업을 수행하고 명시적 변환 함수(또는 암시적 변환 함수)를 호출할 수도 있습니다. 대부분의 경우 명시적으로 static_cast 명시할 필요는 없지만 T(something) 구문은 (T)something 과 동일하므로 피해야 합니다(나중에 자세히 설명). 그러나 T(something, something_else) 는 안전하고 생성자를 호출하도록 보장됩니다.

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 에는 몇 가지 제한 사항이 있습니다. 상속 계층 구조(소위 'dreaded diamond')에 동일한 유형의 개체가 여러 개 있고 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 가 실패합니다. 그런 경우에도 더 길고 더 명확한 옵션을 고려하십시오.

static_cast 수행 시 액세스 제어도 무시합니다. 즉, 다른 캐스트가 할 수 없는 작업을 수행할 수 있는 능력이 있음을 의미합니다. 그러나 이것은 대부분 엉터리이며 내 생각에는 C 스타일 캐스트를 피해야 하는 또 다른 이유일 뿐입니다.


Community Wiki

  • 상속 계층 내에서 포인터/참조를 변환 dynamic_cast 를 사용하십시오.

  • 일반 유형 변환 static_cast 를 사용하십시오.

  • 비트 패턴의 저수준 재 reinterpret_cast 위해 reinterpret_cast를 사용하십시오. 극도로 주의하여 사용하십시오.

  • const/volatile 을 캐스팅 const_cast 를 사용하세요. const-incorrect API를 사용하지 않는 한 이것을 피하십시오.


Fred Larson

(위에서 많은 이론 및 개념 설명이 제공됨)

다음은 내가 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 eg // 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); }

Sumit Arora

내부 구조를 조금이라도 알면 도움이 될 텐데...

static_cast

  • float 와 같은 스케일러 유형을 int 로 변환하는 방법을 이미 알고 있습니다. 그들을 위해 static_cast 를 사용하십시오.
  • A 에서 B 로 변환하도록 요청하면 static_cast A 를 매개변수로 B 의 생성자를 호출합니다. 대안으로, A 는 변환 연산자(즉, A::operator B() )를 가질 수 있습니다. B 이러한 생성자가 없거나 A 에 변환 연산자가 없으면 컴파일 시간 오류가 발생합니다.
  • A* 에서 B* *로의 캐스트는 A와 B가 상속 계층(또는 무효)에 있으면 항상 성공합니다. 그렇지 않으면 컴파일 오류가 발생합니다.
  • Gotcha : 기본 포인터를 파생 포인터로 캐스팅했지만 실제 객체가 실제로 파생 유형 이 아닌 경우 오류가 발생하지 않습니다. 잘못된 포인터가 발생하고 런타임에 segfault가 발생할 가능성이 높습니다. A& 에서 B& 마찬가지입니다.
  • Gotcha : Derived에서 Base로 또는 그 반대로 캐스트하면 사본이 생성됩니다! C#/Java에서 온 사람들에게 이것은 결과가 기본적으로 Derived에서 생성된 잘린 개체이기 때문에 매우 놀랄 수 있습니다.

dynamic_cast

  • dynamic_cast는 런타임 유형 정보를 사용하여 캐스트가 유효한지 알아냅니다. 예를 들어 포인터가 실제로 파생된 유형이 아닌 경우 (Base*) (Derived*) *)로의 변환이 실패할 수 있습니다.
  • 이것은 dynamic_cast가 static_cast에 비해 매우 비싸다는 것을 의미합니다!
  • A* to B* 의 경우 캐스트가 유효하지 않으면 dynamic_cast가 nullptr을 반환합니다.
  • A& 에서 B& &로의 경우 캐스트가 유효하지 않은 경우 dynamic_cast는 bad_cast 예외를 발생시킵니다.
  • 다른 캐스트와 달리 런타임 오버헤드가 있습니다.

const_cast

  • static_cast는 const에 대해 non-const를 수행할 수 있지만 다른 방향으로 갈 수는 없습니다. const_cast는 두 가지 방법을 모두 수행할 수 있습니다.
  • 이것이 편리한 한 가지 예는 키를 변경하지 않도록 요소만 const로 반환하는 set<T> 와 같은 일부 컨테이너를 반복하는 것입니다. 그러나 의도가 객체의 키가 아닌 멤버를 수정하는 것이라면 괜찮습니다. const_cast를 사용하여 constness를 제거할 수 있습니다.
  • 또 다른 예는 T& SomeClass::foo() const T& SomeClass::foo() const 를 구현하려는 경우입니다. 코드 중복을 피하기 위해 const_cast를 적용하여 한 함수의 값을 다른 함수에서 반환할 수 있습니다.

재해석_캐스트

  • 이것은 기본적으로 이 메모리 위치에서 이 바이트를 가져 와서 주어진 객체로 생각한다고 말합니다.
  • float 4바이트를 int 의 4바이트로 로드 float 비트가 어떻게 보이는지 확인할 수 있습니다.
  • 분명히 데이터가 유형에 맞지 않으면 segfault가 발생할 수 있습니다.
  • 이 캐스트에는 런타임 오버헤드가 없습니다.

Shital Shah

이것이 귀하의 질문에 대한 답변입니까?

reinterpret_cast 사용한 적이 없으며 그것이 필요한 경우에 실행되는 것이 나쁜 디자인의 냄새가 아닌지 궁금합니다. 내가 작업하는 코드 기반에서 dynamic_cast 가 많이 사용됩니다. static_cast 와의 차이점은 dynamic_cast 가 런타임 검사를 수행하여 원하는 대로(더 안전함) 또는 그렇지 않을 수(더 많은 오버헤드) 수행한다는 것입니다( msdn 참조).


andreas buykx

지금까지의 다른 답변 외에도 static_cast 가 충분하지 않아 reinterpret_cast 가 필요한 불분명한 예가 있습니다. 출력 매개변수에 다른 클래스(공통 기본 클래스를 공유하지 않음)의 객체에 대한 포인터를 반환하는 함수가 있다고 가정합니다. 이러한 함수의 실제 예는 CoCreateInstance() void** 마지막 매개변수 참조). 이 함수에서 특정 개체 클래스를 요청하여 포인터의 유형(COM 개체에 대해 자주 수행함)을 미리 알고 있다고 가정합니다. static_cast 를 사용하여 포인터에 대한 포인터를 void** 로 캐스팅할 수 없습니다. 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);

Serge Rogatch

다른 답변은 C++ 캐스트 간의 모든 차이점을 훌륭하게 설명했지만 C 스타일 캐스트 (Type) varType(var) 사용하지 말아야 하는 이유를 간단히 추가하고 싶습니다.

C++ 초보자의 경우 C 스타일 캐스트는 C++ 캐스트(static_cast<>(), dynamic_cast<>(), const_cast<>(), reinterpret_cast<>())에 대한 상위 집합 연산처럼 보이며 누군가 C++ 캐스트보다 선호할 수 있습니다. . 사실 C 스타일 캐스트는 상위 집합이며 쓰기에 더 짧습니다.

C 스타일 캐스트의 주요 문제는 개발자가 캐스트의 실제 의도를 숨긴다는 것입니다. C 스타일 캐스트는 static_cast<>() 및 dynamic_cast<>()에 의해 수행되는 일반적으로 안전한 캐스트에서 const_cast<>()와 같은 잠재적으로 위험한 캐스트에 이르기까지 사실상 모든 유형의 캐스팅을 수행할 수 있습니다. 여기서 const 수정자는 const 변수를 제거할 수 있습니다. 정수 값을 포인터로 재해석할 수도 있는 수정 및 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++의 저자) 책 The C++ Programming Language 4th edition - page 302의 짧은 인용문입니다.

이 C 스타일 캐스트는 명명된 변환 연산자보다 훨씬 더 위험합니다. 그 이유는 표기법이 대형 프로그램에서 발견하기 더 어렵고 프로그래머가 의도한 변환 유형이 명시적이지 않기 때문입니다.


Timmy_A

다운캐스트/업캐스트에 대한 static_castdynamic_castreinterpret_cast

이 답변에서는 구체적인 업캐스트/다운캐스트 예제에서 이 세 가지 메커니즘을 비교하고 기본 포인터/메모리/어셈블리에 어떤 일이 발생하는지 분석하여 비교 방법에 대한 구체적인 이해를 제공하고자 합니다.

나는 이것이 그 캐스트가 어떻게 다른지에 대한 좋은 직관을 줄 것이라고 믿습니다.

  • static_cast : 런타임에 하나의 주소 오프셋을 수행하고(낮은 런타임 영향) 다운캐스트가 올바른지 확인하는 안전 검사가 없습니다.

  • dyanamic_cast static_cast 와 같은 주소 오프셋을 수행하지만 RTTI를 사용하여 다운캐스트가 올바른지 확인하는 값비싼 안전 검사도 수행합니다.

    이 안전 검사를 사용하면 잘못된 다운캐스트를 나타내는 nullptr 의 반환을 검사하여 런타임 시 기본 클래스 포인터가 지정된 유형인지 쿼리할 수 있습니다.

    따라서 코드가 해당 nullptr 을 확인할 수 없고 유효한 비중단 조치를 취할 수 없는 경우 동적 캐스트 대신 static_cast

    중단이 코드에서 수행할 수 있는 유일한 작업인 경우 디버그 빌드( -NDEBUG dynamic_cast 만 활성화하고 그렇지 않으면 static_cast 사용하여 예를 들어 여기에서 수행한 것처럼 빠른 실행 속도를 늦추지 않도록 할 수 있습니다.

  • reinterpret_cast : 런타임에 아무 것도 하지 않으며 주소 오프셋도 마찬가지입니다. 포인터는 기본 클래스가 작동하지 않는 경우에도 올바른 유형을 정확하게 가리켜야 합니다. 원시 바이트 스트림이 포함되지 않는 한 일반적으로 이것을 원하지 않습니다.

다음 코드 예제를 고려하십시오.

메인.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 에서 언급했듯이 가상 메서드 호출을 효율적으로 지원하기 위해 B1의 메모리 데이터 구조가 다음 형식이라고 가정합니다.

 B1: +0: pointer to virtual method table of B1 +4: value of int_in_b1

B2 의 형식은 다음과 같습니다.

 B2: +0: pointer to virtual method table of B2 +4: value of int_in_b2

D 메모리 데이터 구조는 다음과 같아야 합니다.

 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 의 메모리 데이터 구조 B1B2 의 메모리 구조와 동일한 메모리 구조를 포함한다는 것입니다. 즉:

  • +0은 D에 대한 B1 vtable 다음에 int_in_b1
  • +8은 D에 대한 B2 vtable 뒤에 int_in_b2

따라서 우리는 다음과 같은 중요한 결론에 도달합니다.

업캐스트 또는 다운캐스트는 컴파일 시간에 알려진 값만큼 포인터 값을 이동하기만 하면 됩니다.

이렇게 하면 D 가 기본 유형 배열에 전달될 때 유형 캐스트가 실제로 오프셋을 계산하고 B2 B2 대신 D 대한 vtable이 있으므로 모든 가상 통화는 투명하게 작동합니다.

예:

 b2s[1] = &d;

해당 B2와 같은 데이터 구조에 도달하려면 d + 8의 주소를 가져오기만 하면 됩니다.

이제 마침내 유형 캐스팅과 구체적인 예제 분석으로 돌아갈 수 있습니다.

stdout 출력에서 다음을 볼 수 있습니다.

 &d 0x7fffffffc930 b2s[1] 0x7fffffffc940

따라서 거기에서 수행된 암시적 static_cast D 데이터 구조에서 0x7fffffffc940에 있는 것과 같은 B2 까지의 오프셋을 올바르게 계산했습니다. 우리는 또한 0x7fffffffc930과 0x7fffffffc940 사이에 있는 것이 B1 데이터와 vtable일 가능성이 있다고 추론합니다.

그런 다음 다운캐스트 섹션에서 이제 잘못된 섹션이 실패하는 방법과 이유를 쉽게 이해할 수 있습니다.

  • static_cast<D*>(b2s[0]) 0x7fffffffc910 : 컴파일러가 컴파일 타임 바이트에서 0x10으로 올라가서 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가 하는 것을 봅니다:

    • 포인터가 NULL인지 확인하고 그렇다면 NULL을 반환합니다.
    • 그렇지 않으면 존재하지 않는 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 검사가 있으며 einput이 NULL이면 NULL을 반환합니다.

    그렇지 않으면 RDX, RSI 및 RDI에서 일부 인수를 설정하고 __dynamic_cast 호출합니다.

    나는 이것을 더 이상 분석할 인내심이 없지만 다른 사람들이 말했듯이 이것이 작동하는 유일한 방법은 __dynamic_cast 가 클래스 계층 구조를 나타내는 일부 추가 RTTI 메모리 내 데이터 구조에 액세스하는 것입니다.

    따라서 B2 b2s[0] D typecast에 대한 vtable을 찾을 때까지 이 클래스 계층 구조를 따라야 합니다.

    이것이 다이나믹 캐스트가 잠재적으로 비용이 많이 드는 이유입니다! 다음은 dynamic_caststatic_cast 로 변환하는 단일 라이너 패치가 런타임을 33% 단축한 예입니다! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940 이것은 우리를 맹목적으로 믿습니다. 우리는 주소 b2s[1] D 가 있다고 말했고 컴파일러는 오프셋 계산을 하지 않습니다.

    그러나 이것은 잘못된 것입니다. 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에서 테스트되었습니다.


Ciro Santilli 新疆再教育营六四事件法轮功郝海东

이해를 돕기 위해 아래 코드 스니펫을 살펴보겠습니다.

 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++ 캐스트를 사용하는 경우 :

  • 값 변환을 수행하는 C 스타일 캐스트와 동일하거나 클래스에서 상위 클래스로 포인터를 명시적으로 업캐스트해야 하는 경우 static_cast 를 사용합니다.
  • const 한정자를 제거 하려면 const_cast 를 사용하세요.
  • reinterpret_cast 를 사용하여 정수 및 기타 포인터 유형 간에 포인터 유형의 안전하지 않은 변환을 수행합니다. 우리가 하는 일을 알고 앨리어싱 문제를 이해하는 경우에만 이것을 사용하십시오.

pkthapa

출처 : http:www.stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used

반응형