什么时候应该使用static_cast,dynamic_cast,const_cast和reinterpret_cast?


2490

的正确用法是:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C型演员表 (type)value
  • 功能样式转换 type(value)

如何决定在特定情况下使用哪个?



3
有关使用不同类型的强制转换的一些有用的具体示例,您可以检查另一个主题中类似问题的第一个答案。
TeaMonkie

2
您可以在上述问题中找到非常好的答案。但我想在这里再指出一点,@ e.James:“这些新的c ++强制转换运算符不能执行,c样式强制转换不能执行。为了更好的代码可读性,或多或少地添加了这些。”
BreakBadSP '18 -10-9

@BreakBadSP新的强制类型转换是不是只为更好的代码的可读性。它们在那里使做困难的事情变得更加困难,例如抛弃const或抛弃指针而不是其值。与ac样式转换相比,static_cast进行危险操作的可能性要小得多!
Fourty

@FourtyTwo同意
BreakBadSP

Answers:


2569

static_cast是您应该尝试使用的第一个演员表。它可以进行类型之间的隐式转换(例如intto float或指向的指针void*),还可以调用显式转换函数(或隐式函数)。在许多情况下,static_cast没有必要明确声明,但必须注意,T(something)语法(T)something与该语法等效,应避免使用(稍后会详细介绍)。T(something, something_else)但是,A 是安全的,并且保证可以调用构造函数。

static_cast也可以遍历继承层次结构。向上(朝向基类)进行强制转换时没有必要,但是向下进行转换时,只要不通过virtual继承进行转换就可以使用。但是,它不进行检查,static_cast将层次结构划分为实际上不是对象类型的类型是一种未定义的行为。


const_cast可用于删除或添加const到变量;没有其他C ++ cast能够删除它(甚至reinterpret_cast)。重要的是要注意,const仅当原始变量为时,才修改未定义的值const。如果您使用它const取消对未使用声明的内容的引用const,那是安全的。const例如,当基于重载成员函数时,这将很有用。它还可以用于添加const到对象,例如调用成员函数重载。

const_cast在上也是如此volatile,尽管这种情况并不常见。


dynamic_cast仅用于处理多态性。您可以将任何多态类型的指针或引用强制转换为任何其他类类型(多态类型至少具有一个声明或继承的虚函数)。您不仅可以向下投射,还可以使用它–您可以侧向投射,甚至向上投射另一个链。该dynamic_cast会寻求所需的对象,如果可能的话返回。如果不能,则nullptr在指针的情况下返回,或者std::bad_cast在引用的情况下抛出。

dynamic_cast但是有一些限制。如果在继承层次结构中有多个相同类型的对象(所谓的“可怕的钻石”),并且您没有使用virtual继承,则该方法不起作用。它也只能去通过公共继承-它将始终无法通过旅游protectedprivate继承。但是,这很少有问题,因为这种继承形式很少。


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,这意味着它们具有执行其他转换无法执行的操作的能力。但是,这主要是一种争执,在我看来,这是避免使用C样式强制转换的另一个原因。


17
dynamic_cast仅适用于多态类型。您只需要在转换为派生类时使用它。除非您特别需要dynamic_cast的功能,否则static_cast当然是第一个选择。总的来说,这不是奇迹般的银弹“类型检查演员”。
jalf

2
好答案!一个简短的说明:如果您有一个Derived *&要转换为Base *&,则可能需要static_cast来转换层次结构,因为双指针/引用不会自动地转换层次结构。两分钟前,我遇到了这种情况(坦率地说,不常见)。;-)
bartgol

5
*“没有其他C ++强制转换能够删除const(甚至无法删除reinterpret_cast)” ...真的吗?那reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
user541686

29
我认为,上面缺少的一个重要细节是,与static或reinterpret_cast相比,dynamic_cast的运行时性能会受到影响。这很重要,例如在实时软件中。
jfritz42

5
可能值得一提的reinterpret_cast是,在处理API的一组不透明数据类型时,这通常是选择的武器
Class Skeleton 2015年

333

使用dynamic_cast一个继承层次内进行转化的指针/引用。

使用static_cast普通类型转换。

使用reinterpret_cast低层次的重新解释的位模式。使用时要格外小心。

使用const_cast铸造远const/volatile。除非您使用const不正确的API,否则请避免这种情况。


2
注意dynamic_cast。它依赖RTTI,在跨共享库的范围内无法正常工作。仅仅是因为您独立地构建可执行文件和共享库,所以没有标准化的方法可以跨不同的构建同步RTTI。因此,在Qt库中存在qobject_cast <>,它使用QObject类型信息来检查类型。
user3150128 '18

198

(上面已经给出了很多理论和概念上的解释)

下面是当我使用static_castdynamic_castconst_castreinterpret_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 2014年

1
关于reinterpret_cast的最后用法:这与using不一样static_cast<char*>(&val)吗?
洛伦佐·贝利

3
@LorenzoBelli当然不是。你试过了吗?后者不是有效的C ++,并且会阻止编译。static_cast仅在定义了转换的类型之间,通过继承可见的关系或到/ from之间起作用void *。除了其他方面,还有其他演员。允许reinterpret cast任何char *类型的参数读取任何对象的表示-这是该关键字有用的唯一情况之一,而不是猖implementation的实现/未定义行为的生成器。但这不被视为“正常”转换,因此(通常)非常保守的人不允许这样做static_cast
underscore_d

2
当您使用数据库等系统软件时,reinterpret_cast非常常见。大多数情况下,您编写自己的页面管理器时并不知道页面中存储的数据类型是什么,而只是返回空指针。由更高级别来重新解释演员表并将其推断为他们想要的任何内容。
Sohaib

1
const_cast示例显示未定义行为。声明为const的变量不能被解构。但是,声明为非常量的变量传递给采用const引用的函数时,可以在该函数中对其进行解构,而不必将其作为UB。
Johann Gerell

99

如果您不了解内部原理,可能会有所帮助...

static_cast

  • C ++编译器已经知道如何在定标器类型之间进行转换,例如float到int。使用static_cast他们。
  • 当您从类型要求编译器转换ABstatic_cast调用B的构造函数传递A的PARAM。或者,A可以有一个转换运算符(即A::operator B())。如果B没有这样的构造函数,或者A没有转换运算符,则会出现编译时错误。
  • 如果A和B在继承层次结构(或void)中,则从成功转换A*B*总是成功,否则会出现编译错误。
  • 陷阱:如果将基本指针转换为派生指针,但是如果实际对象不是真正派生类型,则不会出错。您会得到错误的指针,并且很可能在运行时出现段错误。同样适用A&B&
  • 疑难杂症:从衍生角色转换为基础角色,反之亦然,创建副本!对于使用C#/ Java的人来说,这可能是一个巨大的惊喜,因为结果基本上是从Derived创建的对象中切掉的。

dynamic_cast

  • dynamic_cast使用运行时类型信息来确定强制转换是否有效。例如,如果指针实际上不是派生类型,则(Base*)to (Derived*)可能会失败。
  • 这意味着,与static_cast相比,dynamic_cast非常昂贵!
  • 对于A*to B*,如果强制转换无效,则dynamic_cast将返回nullptr。
  • 对于A&B&投的是无效的,那么将dynamic_cast会抛出bad_cast异常。
  • 与其他强制转换不同,运行时会有开销。

const_cast

  • 虽然static_cast可以执行非const到const的操作,但它不能走其他方向。const_cast可以同时执行两种方式。
  • 一个方便使用的示例是遍历某个容器set<T>,例如,该容器仅将其元素返回为const以确保您不更改其键。但是,如果您打算修改对象的非关键成员,那么应该可以。您可以使用const_cast删除常数。
  • 另一个示例是您想同时实现T& SomeClass::foo()const T& SomeClass::foo() const。为了避免代码重复,可以将const_cast应用于从另一个函数返回的值。

reinterpret_cast

  • 这基本上就是说,将这些字节放在此内存位置,并将其视为给定对象。
  • 例如,您可以将4字节的float加载到4字节的int中,以查看float中的位的样子。
  • 显然,如果该类型的数据不正确,则可能会出现段错误。
  • 此转换没有运行时开销。

我添加了转换运算符信息,但是还有一些其他问题也应该修复,并且我对此不太满意。这些项目是: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.您将获得UB,如果幸运的话,它可能会在运行时导致段错误。2.动态铸造也可以用于交叉铸造。3.在某些情况下,const转换可能导致UB。使用mutable可能是实现逻辑常数的更好选择。
阿德里安

1
@Adrian,您在所有方面都是正确的。答案是在或多或少的入门级的水平为乡亲写的,我并没有想用自带的所有其他并发症压倒他们mutable,跨铸造等
清淳沙阿

16

请问这个回答你的问题?

我从未使用过reinterpret_cast,并且想知道是否遇到需要它的箱子是否散发出不良的设计味道。在我研究的代码库中,dynamic_cast使用了很多代码。与之不同的 static_cast是,dynamic_cast运行时检查可能是您想要的(更安全)或可能不是(更多开销)(请参阅msdn)。


3
我已经将reintrepret_cast用于一个目的-使位变成双倍(在我的平台上长度相同)。
约书亚

2
需要reinterpret_cast,例如用于处理COM对象。CoCreateInstance()的输出参数类型为void **(最后一个参数),您将在其中传递声明为“ INetFwPolicy2 * pNetFwPolicy2”的指针。为此,您需要编写类似reinterpret_cast <void **>(&pNetFwPolicy2)的内容。
Serge Rogatch,2015年

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); }
詹姆斯·马塔

我从来没有使用过reinterpret_cast,没有太多用途。
鲸鱼巫师皮卡

就我个人而言,我只见过reinterpret_cast出于一个原因。我已经看到原始对象数据存储到数据库中的“ blob”数据类型,然后当从数据库中检索数据时,该数据reinterpret_cast用于将这些原始数据转换为对象。
ImaginaryHuman072889

15

除了到目前为止的其他答案之外,这是一个不明显的示例,其中static_cast不够,因此reinterpret_cast是必需的。假设有一个函数,该函数在输出参数中返回指向不同类(不共享公共基类)的对象的指针。此类功能的一个真实示例是CoCreateInstance()(请参阅最后一个参数,实际上是void**)。假设您从此函数请求特定的对象类,因此您预先知道了指针的类型(通常对COM对象执行此操作)。在这种情况下,您不能将指针转换为void**with 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 <>()这样的潜在危险的转换,可以删除const修饰符,以便const变量可以被修改并重新解释_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 ++编程语言》第4版-第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 ++ cast

  • 使用static_cast等效于进行值转换的C样式强制转换,或者在我们需要将指针从类显式上载到其超类时使用。
  • 使用const_cast删除const限定词。
  • 使用reinterpret_cast将指针类型与整数和其他指针类型进行不安全的转换。仅当我们知道我们在做什么并且了解别名问题时才使用此功能。

2

static_cast向下/向上显示vs dynamic_castvs reinterpret_cast内部视图

在这个答案中,我想在一个具体的向上/向下转换示例中比较这三种机制,并分析底层指针/内存/程序集发生了什么,以对它们如何进行比较有一个具体的了解。

我相信这将很好地说明这些演员的不同之处:

  • static_cast:在运行时进行一个地址偏移(对运行时的影响较小),并且没有安全检查向下转换是否正确。

  • dyanamic_cast:在运行时像一样执行相同的地址偏移量static_cast,但也使用RTTI进行昂贵的安全检查以确保下行正确。

    通过此安全检查,您可以在运行时通过检查基类指针是否为给定类型来检查其返回是否nullptr指示无效的向下转换,从而查询该类型。

    因此,如果您的代码无法进行检查nullptr并采取有效的非中止操作,则应使用static_cast而不是动态强制转换。

    如果中止是您的代码可以采取的唯一措施,则也许您只想启用dynamic_castin debug builds(-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在其内部包含与内部B1B2内部兼容的存储结构。

因此,我们得出了关键的结论:

向上或向下转换只需要将指针值移动编译时已知的值

这样,当D传递给基本类型数组时,类型转换实际上会计算该偏移量,并指向看起来与B2内存中的有效值完全相同的内容:

b2s[1] = &d;

除了这个有一个vtable D而不是B2,因此所有虚拟调用都是透明的。

现在,我们终于可以返回类型转换并分析我们的具体示例。

从stdout输出中,我们看到:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

因此,static_cast此处的隐式操作正确地计算了从D0x7fffffffcc930 的完整数据结构到0x7fffffffc940处的偏移量B2。我们还推断出介于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
    • 否则,从中减去0x10以达到D不存在的
  • 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从中找到类型转换的vtable b2s[0]

    这就是为什么重新解释演员表可能会很昂贵的原因!这是一个示例,其中在一个复杂项目中,一个线性补丁将a转换为a dynamic_cast可以static_cast将运行时间减少33%!

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940这个只是盲目地相信我们:我们说有一个Dat地址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上测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.