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

什么是正确的用法:

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

699054 次浏览

这个能回答你的问题吗?

我从未使用过reinterpret_cast,并且想知道遇到需要它的情况是否不是糟糕设计的味道。在我工作的代码库中,dynamic_cast被大量使用。与static_cast的区别在于dynamic_cast进行运行时检查,这可能(更安全),也可能(更多开销)是你想要的(参见msdn)。

  • 使用dynamic_cast在继承层次结构中转换指针/引用。

  • 使用static_cast进行普通类型转换。

  • 使用reinterpret_cast对位模式进行低级重新解释。极度谨慎使用。

  • 使用const_cast来丢弃const/volatile。除非您坚持使用const不正确的API,否则请避免这种情况。

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

static_cast也可以通过继承层次结构进行转换。向上转换(朝向基类)时这是不必要的,但向下转换时,只要它不通过virtual继承进行转换,就可以使用它。但是,它不做检查,并且static_cast将层次结构向下转换为实际上不是对象类型的类型是未定义的行为。


#0可用于将const删除或添加到变量中;没有其他C++转换能够删除它(即使是reinterpret_cast)。重要的是要注意,只有当原始变量为const时,修改以前的const值才是未定义的;如果您使用它来从对未用const声明的东西的引用中删除const,它是安全的。例如,当基于const重载成员函数时,这很有用。它也可以用于将const添加到对象中,例如调用成员函数重载。

const_cast也同样适用于volatile,尽管这不太常见。


#0专门用于处理多态。你可以将指向任何多态类型的指针或引用强制转换为任何其他类类型(多态类型至少有一个声明或继承的虚函数)。你可以使用它不仅仅是向下强制转换-你可以横向强制转换,甚至向上强制转换另一个链。dynamic_cast将寻找所需的对象并在可能的情况下返回它。如果不能,它将在指针的情况下返回nullptr,或者在引用的情况下抛出std::bad_cast

不过,dynamic_cast有一些限制。如果继承层次结构中有多个相同类型的对象(所谓的“可怕的钻石”)并且您不使用virtual继承,它就不起作用。它也只能通过公共继承——它总是无法通过protectedprivate继承。然而,这很少是一个问题,因为这种形式的继承很少。


#0是最危险的类型转换,应该非常谨慎地使用。它将一种类型直接转换为另一种类型-例如将值从一个指针转换为另一个指针,或将指针存储在int中,或者各种其他令人讨厌的事情。在很大程度上,使用reinterpret_cast得到的唯一保证是,通常如果您将结果转换回原始类型,您将获得完全相同的值(但如果中间类型小于原始类型,则为不是)。有许多转换#0也无法做到。它主要用于特别奇怪的转换和位操作,例如将原始数据流转换为实际数据,或将数据存储在指向对齐数据的指针的低位中。


c风格演员阵容函数式强制转换分别使用(type)objecttype(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风格强制转换的另一个原因。

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

下面是我使用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);}

除了目前为止的其他答案之外,这里还有一个不明显的例子,其中static_cast不足以满足reinterpret_cast的需要。假设有一个函数在输出参数中返回指向不同类对象的指针(它们不共享公共基类)。这种函数的一个真实例子是#2(参见最后一个参数,实际上是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 errorreinterpret_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

  • C++编译器已经知道如何在floatint等缩放器类型之间进行转换。请使用static_cast
  • 当你要求编译器将类型A转换为B时,static_cast会调用B的构造函数将A作为参数传递。或者,A可以有一个转换运算符(即A::operator B())。如果B没有这样的构造函数,或者A没有转换运算符,那么你会得到编译时错误。
  • 如果A和B处于继承层次结构(或无效)中,则从A*B*的转换总是成功,否则会出现编译错误。
  • Gotcha:如果您将基指针转换为派生指针,但如果实际对象不是真正的派生类型,那么您不要会得到错误。您会得到坏指针,并且很可能在运行时出现段错误。A&B&也是如此。
  • Gotcha:从派生转换为基础或替代创建新的副本!对于来自C#/Java的人来说,这可能是一个巨大的惊喜,因为结果基本上是从派生创建的切掉的对象。

dynamic_cast

  • dynamic_cast使用运行时类型信息来确定强制转换是否有效。例如,如果指针实际上不是派生类型,(Base*)(Derived*)可能会失败。
  • 这意味着,与static_cast相比,dynamic_cast非常昂贵!
  • 对于A*B*,如果cast无效,则dynamic_cast将返回nullptr。
  • 对于A&B&,如果cast无效,则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中的位是什么样子。
  • 显然,如果数据对类型不正确,您可能会得到分段错误。
  • 此转换没有运行时开销。

虽然其他答案很好地描述了C++转换之间的所有差异,但我想添加一个简短的说明,为什么您不应该使用C风格的转换(Type) varType(var)

对于初学者C++,C风格的强制转换看起来像是C++强制转换(static_cast<>(),dynamic_cast<>(),const_cast<>(),reinterpret_cast<>())的超集操作,有些人可能更喜欢它们而不是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++的作者)的《C++编程语言》第四版的简短引用-第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作为进行值转换的C风格转换的等价物,或者当我们需要显式地将指针从类向上转换到其超类时。
  • 使用const_cast删除const限定符。
  • 使用reinterpret_cast进行指针类型与整数和其他指针类型之间的不安全转换。只有当我们知道我们在做什么并且我们理解混淆现象时才使用它。

static_cast vsdynamic_cast vsreinterpret_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;
// OKdp = 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;
// OKdp = 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.cppsetarch `uname -m` -R ./main.outgdb -batch -ex "disassemble/rs main" main.out

其中setarch用于禁用ASLR,以便更容易比较运行。

可能的输出:

&d           0x7fffffffc930b2s[0]       0x7fffffffc920b2s[1]       0x7fffffffc940b2s[0]->f2() 2b2s[1]->f2() 3static_cast<D*>(b2s[0])            0x7fffffffc910static_cast<D*>(b2s[0])->int_in_d  1static_cast<D*>(b2s[1])            0x7fffffffc930static_cast<D*>(b2s[1])->int_in_d  3dynamic_cast<D*>(b2s[0])           0dynamic_cast<D*>(b2s[1])           0x7fffffffc930dynamic_cast<D*>(b2s[1])->int_in_d 3reinterpret_cast<D*>(b2s[1])           0x7fffffffc940reinterpret_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看起来完全像一个B1,其中D的B1 vtable后跟int_in_b1
  • +8看起来与B2完全一样,B2 vtable for D后跟int_in_b2

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

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

这样,当D被传递到基类型数组时,类型转换实际上会计算该偏移量并指向看起来与内存中的有效B2完全相同的东西,除了这个具有D而不是B2的vtable,因此所有虚拟调用都是透明的。

例如:

b2s[1] = &d;

只需获取d+8的地址即可到达相应的类B2数据结构。

现在,我们终于可以回到类型铸造和我们的具体例子的分析。

从stdout输出中我们可以看到:

&d           0x7fffffffc930b2s[1]       0x7fffffffc940

因此,在那里完成的隐式static_cast确实正确计算了从0x7fffffffc930处的完整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),%rax0x0000000000000fcc <+418>:   48 85 c0        test   %rax,%rax0x0000000000000fcf <+421>:   74 0a   je     0xfdb <main()+433>0x0000000000000fd1 <+423>:   48 8b 45 d0     mov    -0x30(%rbp),%rax0x0000000000000fd5 <+427>:   48 83 e8 10     sub    $0x10,%rax0x0000000000000fd9 <+431>:   eb 05   jmp    0xfe0 <main()+438>0x0000000000000fdb <+433>:   b8 00 00 00 00  mov    $0x0,%eax0x0000000000000fe0 <+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),%rax0x00000000000010f0 <+710>:   48 85 c0        test   %rax,%rax0x00000000000010f3 <+713>:   74 1d   je     0x1112 <main()+744>0x00000000000010f5 <+715>:   b9 10 00 00 00  mov    $0x10,%ecx0x00000000000010fa <+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,%rdi0x000000000000110b <+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,%eax0x0000000000001117 <+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]

    这就是为什么动态转换可能很昂贵!这是例如,在一个复杂的项目中将#0转换为#1的单行补丁将运行时间减少了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),%rax0x00000000000011fe <+980>:   48 89 45 98     mov    %rax,-0x68(%rbp)

相关问题:

在Ubuntu 18.04 amd64、GCC 7.4.0上测试。

reinterpret_cast的不错的特性,在其他答案中没有提到,它允许我们为函数类型创建一种void*指针。通常,对于对象类型,使用static_cast来检索存储在void*中的指针的原始类型:

  int i = 13;void *p = &i;auto *pi = static_cast<int*>(p);

对于函数,我们必须使用reinterpret_cast两次:

#include<iostream>
using any_fcn_ptr_t = void(*)();

void print(int i){std::cout << i <<std::endl;}
int main(){//Create type-erased pointer to function:auto any_ptr = reinterpret_cast<any_fcn_ptr_t>(&print);  
//Retrieve the original pointer:auto ptr = reinterpret_cast< void(*)(int) >(any_ptr);  
ptr(7);}

对于reinterpret_cast,我们甚至可以得到一个类似的空洞指针,用于指向成员函数的指针。

与普通的void*static_cast一样,C++保证ptr指向print函数(只要我们将正确的类型传递给reinterpret_cast)。

让我们看看示例中reinterpret_caststatic_cast的区别:

#include <iostream>using namespace std;
class A{int a;};
class B{int b;};
class C : public A, public B{int c;};
int main()\{\{B b;cout << &b << endl;cout << static_cast<C *>(&b) << endl;      // 1cout << reinterpret_cast<C *>(&b) << endl; // 2}cout << endl;{C c;cout << &c << endl;cout << static_cast<B *>(&c) << endl;      // 3cout << reinterpret_cast<B *>(&c) << endl; // 4}cout << endl;{A a;cout << &a << endl;cout << static_cast<C *>(&a) << endl;cout << reinterpret_cast<C *>(&a) << endl;}cout << endl;{C c;cout << &c << endl;cout << static_cast<A *>(&c) << endl;cout << reinterpret_cast<A *>(&c) << endl;}return 0;}

生成输出:

0x7ffcede34f0c0x7ffcede34f08 // 10x7ffcede34f0c // 2
0x7ffcede34f0c0x7ffcede34f10 // 30x7ffcede34f0c // 4
0x7ffcede34f0c0x7ffcede34f0c0x7ffcede34f0c
0x7ffcede34f0c0x7ffcede34f0c0x7ffcede34f0c

请注意,输出12是不同的,以及34。为什么会这样?其中一个是static_cast,另一个是reinterpret_cast,在这两种情况下都是相同类型的相同输入。

情况可以在下图中可视化:

可视化

C包含一个B,但B的起始地址与C不同。static_cast正确地计算了CB的地址。然而reinterpret_cast返回我们作为输入给出的相同地址,这对于这种情况是不正确的:该地址没有B

但是,在AC指针之间转换时,这两种转换都会返回相同的结果,因为它们碰巧从同一位置开始,顺便说一句,标准无论如何都不能保证这一点。