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


2491

올바른 사용법은 무엇입니까?

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

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



3
다른 종류의 캐스트를 사용하는 유용한 구체적인 예를 보려면 이 다른 주제 에서 유사한 질문에 대한 첫 번째 답변을 확인할 수 있습니다 .
TeaMonkie 2019

2
위의 질문에 대한 정말 좋은 답변을 찾을 수 있습니다. 그러나 @ e.James는 "새로운 c ++ 캐스트 연산자로는 할 수있는 것이없고 c 스타일 캐스트에서는 할 수있는 것이 없습니다. 더 나은 코드 가독성을 위해 더 많거나 적은 것"이라고 덧붙였습니다.
BreakBadSP

새로운 캐스트가 @BreakBadSP 되지 나은 코드의 가독성 만. 그들은 값 대신 const를 캐스팅하거나 포인터를 캐스팅하는 것과 같은 위험한 일을하기가 더 어려워집니다. static_cast는 ac 스타일 캐스트보다 위험한 일을 할 가능성이 훨씬 적습니다!
FourtyTwo

@FourtyTwo 합의
BreakBadSP

답변:


2569

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

static_cast상속 계층을 통해 캐스트 할 수도 있습니다. 위쪽으로 (기본 클래스쪽으로) 캐스팅 할 때는 필요하지 않지만 아래쪽으로 캐스팅 할 때는 virtual상속을 통해 캐스팅하지 않는 한 사용할 수 있습니다 . 그러나 검사는 수행하지 않으며 static_cast계층을 실제로 개체 유형이 아닌 유형 으로 다운시키는 정의되지 않은 동작 입니다.


const_castconst변수 를 제거하거나 추가 하는 데 사용할 수 있습니다 . 다른 C ++ 캐스트는 그것을 제거 할 수 없습니다 (조차도 reinterpret_cast). 이전 const변수 를 수정 하면 원래 변수가 다음과 같은 경우에만 정의되지 않습니다 const. 로 const선언되지 않은 것에 대한 참조를 해제하는 데 사용하면 const안전합니다. const예를 들어 에 따라 멤버 함수를 오버로드 할 때 유용 할 수 있습니다 . const멤버 함수 오버로드를 호출하는 것과 같이 객체 에 추가 하는 데 사용될 수도 있습니다 .

const_castvolatile덜 일반적이지만 에서도 비슷하게 작동합니다 .


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 스타일 캐스트를 피하는 또 다른 이유입니다.


17
dynamic_cast는 다형성 유형에만 해당됩니다. 파생 클래스로 캐스팅 할 때만 사용해야합니다. 특히 dynamic_cast의 기능이 필요하지 않으면 static_cast가 첫 번째 옵션입니다. 그것은 일반적으로 기적적인 은색 총알 "유형 검사 캐스트"가 아닙니다.
jalf

2
좋은 답변입니다! 한 가지 간단한 설명 : 이중 포인터 / 참조가 자동으로 계층 구조를 캐스트하지 않기 때문에 Derived * &가 Base * &로 캐스트되는 경우 계층 구조를 캐스트하는 데 static_cast가 필요할 수 있습니다. 나는 2 분 전에 그러한 (정직하지 않은) 상황을 만났다. ;-)
bartgol

5
* "다른 C ++ 캐스트는 제거 할 수 const없습니다 reinterpret_cast" 무엇에 대해 reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
user541686

29
위에서 누락 된 중요한 세부 사항은 dynamic_cast가 정적 또는 reinterpret_cast에 비해 런타임 성능 저하가 있다는 것입니다. 실시간 소프트웨어와 같이 중요합니다.
jfritz42

5
reinterpret_castAPI의 불투명 한 데이터 유형 집합을 처리 할 때 종종 선택되는 무기 라고 언급 할 가치가 있습니다.
Class Skeleton

333

dynamic_cast상속 계층 내에서 포인터 / 참조를 변환하는 데 사용 합니다.

static_cast일반적인 유형 변환에 사용하십시오 .

reinterpret_cast비트 패턴의 저수준 재 해석에 사용 합니다. 매우주의해서 사용하십시오.

const_cast캐스팅에 사용 합니다 const/volatile. const-incorrect API를 사용하지 않으면 이것을 피하십시오.


2
dynamic_cast에주의하십시오. RTTI를 사용하므로 공유 라이브러리 경계에서 예상대로 작동하지 않습니다. 실행 파일과 공유 라이브러리를 독립적으로 빌드하기 때문에 다른 빌드간에 RTTI를 동기화하는 표준화 된 방법이 없습니다. 이러한 이유로 Qt 라이브러리에는 유형을 확인하기 위해 QObject 유형 정보를 사용하는 qobject_cast <>가 있습니다.
user3150128

198

(위에 많은 이론적이고 개념적인 설명이 있습니다)

다음은 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);
}

31
다른 답변 중 일부의 이론은 훌륭하지만 여전히 혼란 스럽습니다. 다른 답변을 읽은 후이 예제를 보면 실제로 모든 것이 합리적입니다. 그것은 예가없고, 여전히 확실하지 않았지만, 그들과 함께, 다른 답변이 무엇을 의미하는지 확신합니다.
Solx

1
reinterpret_cast의 마지막 사용법에 대해 : static_cast<char*>(&val)? 를 사용하는 것과 동일하지 않습니까?
Lorenzo Belli

3
@LorenzoBelli 물론 아닙니다. 해봤 어? 후자는 유효한 C ++이 아니며 컴파일을 차단합니다. static_cast정의 된 변환이 포함 된 유형, 상속에 의한 가시적 관계 또는 ~에서 /로만 작동합니다 void *. 다른 모든 것에는 다른 캐스트가 있습니다. reinterpret cast모든 char *유형의 객체는 객체의 표현을 읽을 수 있으며 키워드가 유용한 유일한 구현 중 하나이며 구현 / 정의되지 않은 동작의 생성자가 아닙니다. 그러나 이것은 '정상적인'전환으로 간주되지 않으므로 (보통) 매우 보수적 인 사람들에게는 허용되지 않습니다.static_cast .
underscore_d

2
reinterpret_cast는 데이터베이스와 같은 시스템 소프트웨어로 작업 할 때 매우 일반적입니다. 대부분의 경우 페이지에 저장된 데이터 유형에 대해 전혀 모르고 void 포인터를 반환하는 자체 페이지 관리자를 작성합니다. 재 해석 캐스트를 수행하고 원하는대로 추론하는 것이 더 높은 수준입니다.
Sohaib

1
const_cast 예제는 정의되지 않은 동작을 나타냅니다. const로 선언 된 변수는 확정 될 수 없습니다. 그러나 const 참조를 사용하는 함수에 전달되는 비 const로 선언 된 변수는 해당 함수에서 UB가 아닌 상태에서 해제 될 수 있습니다.
Johann Gerell

99

내부 정보를 조금만 아는 경우 도움이 될 수 있습니다 ...

static_cast

  • C ++ 컴파일러는 float과 같은 스케일러 유형을 int로 변환하는 방법을 이미 알고 있습니다. static_cast그들을 위해 사용하십시오 .
  • 당신은 유형 변환하는 컴파일러를 물어 보면 A하는 B, static_cast통화B 의 생성자는 전달 APARAM한다. 또는 A변환 연산자 (예 :)를 가질 수 있습니다 A::operator B(). 경우 B이러한 생성자가없는, 또는 A변환 연산자가 없습니다, 당신은 컴파일 타임 오류가 발생합니다.
  • 에서 캐스트 A*로는 B*A와 B가 상속 계층 (또는 무효)에있는 경우 항상 그렇지 않으면 컴파일 에러가 성공합니다.
  • 잡았다 : 기본 포인터를 파생 포인터로 캐스팅하지만 실제 객체가 실제로 파생되지 않은 유형 인 경우 오류가 발생 하지 않습니다 . 런타임에 포인터가 잘못되어 segfault가 발생할 가능성이 큽니다. 동일 A&하다 B&.
  • 잡았다 : Derived에서 Base로 캐스트하거나 그 반대로 캐스트하여 복사본을 만듭니다 ! C # / Java에서 온 사람들에게는 결과가 기본적으로 Derived에서 만든 잘게 잘린 객체이기 때문에 이것은 놀라운 일입니다.

dynamic_cast

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

const_cast

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

reinterpret_cast

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

변환 연산자 정보를 추가했지만 해결해야 할 몇 가지 사항이 있으며 너무 많이 업데이트하는 것이 불편하다고 느끼지 않습니다. 1. 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와를 구현하기 위해 더 나은 선택이 될 수 있습니다.
Adrian

1
@Adrian 당신은 모든 카운트에서 정확합니다. 답은 초보자 수준의 사람들을 위해 작성되었으며 mutable크로스 캐스팅 등의 다른 모든 합병증으로 압도하고 싶지 않았습니다 .
Shital Shah

16

이것이 귀하의 질문에 대답 합니까 ?

나는 결코 사용하지 않았으며 reinterpret_cast, 그것이 필요한 경우가 나쁜 디자인의 냄새가 아닌지 궁금합니다. 내가 작업하는 코드베이스 dynamic_cast에서 많이 사용됩니다. 과의 차이는 static_castA가 있다는 것입니다 dynamic_cast할 수있다 (안전) 또는 수도 없습니다 (오버 헤드)를 사용하면 (참조 원하는 것을 할 수 런타임 검사 수행 MSDN을 ).


3
reintrepret_cast를 한 가지 목적으로 사용했습니다. 비트를 두 배로 늘리십시오 (플랫폼에서 길이가 같은 길이).
Joshua

2
reinterpret_cast는 COM 객체 작업을 위해 필요합니다. CoCreateInstance ()에는 void ** (마지막 매개 변수) 유형의 출력 매개 변수가 있으며 "INetFwPolicy2 * pNetFwPolicy2"와 같이 선언 된 포인터를 전달합니다. 그렇게하려면 reinterpret_cast <void **> (& pNetFwPolicy2)와 같은 것을 작성해야합니다.
Serge Rogatch

1
아마도 다른 접근법이 있지만 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); }
James Matta

나는 한번도 사용하지 않았으며 reinterpret_cast, 그다지 많이 사용되지는 않습니다.
Pika 고래의 마법사

개인적으로 나는 reinterpret_cast한 가지 이유로 만 사용 된 적이 있습니다 . 데이터베이스의 "blob"데이터 유형에 저장된 원시 오브젝트 데이터를 보았으며 데이터베이스에서 데이터를 검색 reinterpret_cast할 때이 원시 데이터를 오브젝트로 변환하는 데 사용됩니다.
ImaginaryHuman072889

15

지금까지의 다른 답변 외에도 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)않습니까?
jp48

9

다른 답변 멋지게 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 <> ()와 같은 잠재적으로 위험한 캐스트에 이르기까지 거의 모든 유형의 캐스트를 수행 할 수 있습니다. 정수 값을 포인터로 재 해석 할 수있는 수정 및 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 스타일 캐스트는 큰 프로그램에서 표기법을 찾기가 더 어렵고 프로그래머가 의도 한 변환 종류가 명시 적이 지 않기 때문에 명명 된 변환 연산자보다 훨씬 위험합니다.


5

이해하기 위해 아래 코드 스 니펫을 고려하십시오.

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 를 값 변환을 수행하거나 클래스에서 수퍼 클래스로 포인터를 명시 적으로 업 캐스트해야하는 C 스타일 캐스트와 동등한 것으로 사용하십시오 .
  • const 한정자를 제거 하려면 const_cast 를 사용하십시오 .
  • reinterpret_cast 를 사용 하여 정수 및 기타 포인터 유형간에 포인터 유형을 안전하지 않은 변환을 수행 하십시오 . 우리가하는 일을 알고 앨리어싱 문제를 이해하는 경우에만 이것을 사용하십시오.

2

static_cast다운 캐스트 / 업 캐스트에서 vs dynamic_castvs 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가 다음을 수행한다는 것을 알 수 있습니다.

    • 포인터가 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 검사가 있으며 입력이 NULL이면 NULL을 반환합니다.

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

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

    따라서 B2해당 테이블 의 항목에서 시작한 다음 Dtypecast에 대한 vtable이에서 발견 될 때까지이 클래스 계층 구조를 따라 가십시오 b2s[0].

    이것이 재 해석 캐스트가 잠재적으로 비싼 이유입니다! 다음은 복잡한 프로젝트에서 하나의 라이너 패치 dynamic_cast를 사용하여 static_cast런타임을 33 % 단축 한 예입니다! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940이것은 우리를 맹목적으로 믿습니다. Dat 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에서 테스트되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.