dynamic_cast背着你偷偷做了什么

 首先说到c++常用的四中转换类型,我们都很清楚,分别是下面四中

 

1 const_cast 
const_cast<目标类型>(标识符):目标类型只能是指针或者引用
2 static_cast 
    类似C风格的强制转换,进行无条件转换,静态类型转换:
    1)基类和子类之间的转换:其中子类指针转换为父类指针是安全的,但父类指针转换为子类指针是不安全的(基类和子类之间的动态类型转换建议用dynamic_cast)。
    2)基本数据类型转换,enum,struct,int,char,float等。static_cast不能进行无关类型(如非基类和子类)指针之间的转换。
    3)把任何类型的表达式转换成void类型。
    4)static_cast不能去掉类型的const、volatile属性(用const_cast)。
3 reinterpret_cast
    仅重新解释类型,但没有进行二进制的转换:
    1) 转换的类型必须是一个指针,应用、算术类型、函数指针或者成员指针。
    2) 在比特级别上进行转换,可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。但不能将非32bit的实例转成指针。
    3) 最普通的用途就是在函数指针类型之间进行转换。
    4) 很难保证移植性。
4dynamic_cast
    有条件转换,动态类型转换,运行时检查类型安全(转换失败返回NULL):
    1 将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理, 即会作出一定的判断。
    2 若对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
    3 若对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。
    4 dynamic_cast在将父类cast到子类时,父类必须要有虚函数,否则编译器会报错。
    5 dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
    6 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
    7 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

    因为这里主要说dynamic_cast,所以把dynamic_cast放最后一个说,怎么用我们都很清楚,但是有个关键点值得深究,为什么dynamic_cast在将父类cast到子类时,父类必须要有虚函数,否则编译器会报错,而当子类转换为父类的时候不需要呢? 

    要说明白这个问题得先说多态,多态分为两种一种是编译器的多态,还有一种是运行期的多态。然后说当子类转换为父类的时候为什么不需要,因为在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的,所以这种转换关系在编译时期编译器就明确的直到父子之间的继承关系,这个工作编译时期就已经完成了,所以不需要,可为什么dynamic_cast在将父类cast到子类时,为什么又需要了呢?这种情况正好对应的是运行期的多态,为什么这种情况在编译期搞不定了呢,很简单你想想,一个父亲可以又多个孩子,但是所有的孩子只有一个父亲(不接受杠精的反驳),当向上转换的时候,因为所有的子类只有一个父亲编译器很明确目标直接就搞定了,可是向下就不一样了,那么多孩子,如果有一天老大过来说我是老二,又过了一天又说我是老四(对应程序中错误的转换行为),要怎么辨别真伪呢,如果是错的怎么般,关键的地方在这如果错了会发生什么问题。非常危险,先说为什么这种错误的转换很危险,要明白类型转换实际上是对内存的截取或者扩展,为什么这样说呢,假如类D1继承了类B,B有一个变量int i,D有一个int y,那么内存结构是这样的

 

  

    当我们进行类型转换时,理论上发生了什么呢,当我们把子类强转换为父类时很简单,把D1中末尾属于自己的部分截掉,将父类强转为子类时只要把内存大小扩大子类大小字节就可以了,看到区别没有,当父类强转为子类时实际上时需要扩大内存区间映射到相应的子类对象上,如果转换的子类类型是错误的呢,如果两个子类的大小不一样,可能访问到非法内存,就算内存大小是一样的,但是内存和对象结构的对应关系是错误的,如果有指针变量,对象的指针变量也会变成野指针,直接乱掉,访问起来会造成灾难性的后果。

  那么如何确保转换的正确性呢,这也是dynamic_cast最重要的功能,这里就要说到才c++的RTTI机制,这里的RTTI指c++的运行时类型识别信息,我们知道只有带有虚函数的类才会生成虚函数表,虚函数指针指向了虚函数表,实际上在这张表里还有一个很重要的信息就是指向该类的typeinfo的指针,里面记录了该类的类型信息和继承关系,在进行动态类型转换时只要取虚函数表中的第-1个元素得到type_info类对象判断其真正的类型在进行操作(vptr实际上向后移动了一个位置,编译器取虚函数的时候vptr[0]是第一个虚函数指针,vptr[-1]就能取到指向type_info的指针,一般我们不会主动去取type_info指针).因此很简单运行时转换的时候只要取虚函数表中的typeinfo信息判断他的类型是否是你要转换的类型以及在继承关系的合法性就可以了,如果是非法的就转换失败返回空指针。这样就保证了转换的安全性,当然也有他的弊端,就是相比静态编译器的转换带来了运行时的开销,且集成关系越复杂开销越大,不过我们一般能通过虚函数的多态特性解决(这种虚函数的多态性是不需要遍历typeinfo的虚函数表已经进行了相应的替换)不会进行这种强制类型的转换,除非虚函数解决不了需要子类中特有的属性后者方法的时候才会进行这种运行时期的转换。

 怎么证明这种运行期的调用呢,先写一个小例子


//  Created by 杜国超 on 20/05/10.
//  Copyright © 2019年 杜国超. All rights reserved.
//

#include <iostream>


class Base
{
public:
    virtual void Say()
    {
        printf("I am base\n");
    }
};

class  Child : public Base
{
public:
    virtual void Say()
    {
        printf("I am Child\n");
    }
};


int main()
{
    Child* child = new Child;
    Base* base = dynamic_cast<Base*>(child);
    if (NULL != base)
    {
        base->Say();
    }
    Base* testBase = new Child;
    Child* testChild = dynamic_cast<Child*>(testBase);
    if (NULL != testChild)
    {
        testChild->Say();
    }
}

g++ main.cpp -S -o main.s转成汇编再看,太长了只截取main函数部分:

main:
.LFB1468:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	pushq	%rbx
	subq	$40, %rsp
	.cfi_offset 3, -24
	movl	$8, %edi
	call	_Znwm@PLT
	movq	%rax, %rbx
	movq	%rbx, %rdi
	call	_ZN5ChildC1Ev
	movq	%rbx, -24(%rbp)
	movq	-24(%rbp), %rax
	movq	%rax, -32(%rbp)
	cmpq	$0, -32(%rbp)
	je	.L10
	movq	-32(%rbp), %rax
	movq	(%rax), %rax
	addq	$16, %rax
	movq	(%rax), %rax
	movq	-32(%rbp), %rdx
	movq	%rdx, %rdi
	call	*%rax
.L10:
	movl	$8, %edi
	call	_Znwm@PLT
	movq	%rax, %rbx
	movq	%rbx, %rdi
	call	_ZN5ChildC1Ev
	movq	%rbx, -40(%rbp)
	movq	-40(%rbp), %rax
	testq	%rax, %rax
	je	.L11
	movl	$0, %ecx
	leaq	_ZTI5Child(%rip), %rdx
	leaq	_ZTI4Base(%rip), %rsi
	movq	%rax, %rdi
	call	__dynamic_cast@PLT
	jmp	.L12

  这里我们可以看到一个call    __dynamic_cast@PLT调用,为什么只有一个呢,因为我们第一个调用是子类向父类上行转换直接被编译器优化掉了。那么这个调用是什么呢,其声明在标准库cxxabi.h中

  // Dynamic cast runtime.

  // src2dst has the following possible values
  //  >-1: src_type is a unique public non-virtual base of dst_type
  //       dst_ptr + src2dst == src_ptr
  //   -1: unspecified relationship
  //   -2: src_type is not a public base of dst_type
  //   -3: src_type is a multiple public non-virtual base of dst_type
  void*
  __dynamic_cast(const void* __src_ptr, // Starting object.
		 const __class_type_info* __src_type, // Static type of object.
		 const __class_type_info* __dst_type, // Desired target type.
		 ptrdiff_t __src2dst); // How src and dst are related.

参数声明:
1 __src_ptr: source对象地址,NOT NULL(前面源代码我们看的如果是NULL就不会进到这儿来了),并且由于source是多态的,那么source对象的第一个域是指向虚函数表的指针。
2 __src_type: source对象的类类型typeinfo 的指针
3 __dst_type: destination对象的类类型typeinfo 的指针
4 __src2dst:两个指针的运算结果 , src2dst 的可能的值以及他的含义
   1) >-1: src_type is a unique public non-virtual base of dst_type
     dst_ptr + src2dst == src_ptr    src_type是dst_类型的唯一公共非虚拟基类
   2)  -1: unspecified relationship 不明确的关系
   3)  -2: src_type is not a public base of dst_type  src_type不是是dst_类型的公共基类
   4)   -3: src_type is a multiple public non-virtual base of dst_type src_type是dst_类型的多公共非虚拟基

函数的相关定义实现:

1 libstdc++-v3/libsupc++/dyncast.cc

// this is the external interface to the dynamic cast machinery
/* sub: source address to be adjusted; nonnull, and since the
 *      source object is polymorphic, *(void**)sub is a virtual pointer.
 * src: static type of the source object.
 * dst: destination type (the "T" in "dynamic_cast<T>(v)").
 * src2dst_offset: a static hint about the location of the
 *    source subobject with respect to the complete object;
 *    special negative values are:
 *       -1: no hint
 *       -2: src is not a public base of dst
 *       -3: src is a multiple public base type but never a
 *           virtual base type
 *    otherwise, the src type is a unique public nonvirtual
 *    base type of dst at offset src2dst_offset from the
 *    origin of dst.  */
extern "C" void *
__dynamic_cast (const void *src_ptr,    // object started from
                const __class_type_info *src_type, // type of the starting object
                const __class_type_info *dst_type, // desired target type
                ptrdiff_t src2dst) // how src and dst are related
  {
  const void *vtable = *static_cast <const void *const *> (src_ptr);
  const vtable_prefix *prefix =
      adjust_pointer <vtable_prefix> (vtable, 
                                      -offsetof (vtable_prefix, origin));
  const void *whole_ptr =
      adjust_pointer <void> (src_ptr, prefix->whole_object);
  const __class_type_info *whole_type = prefix->whole_type;
  __class_type_info::__dyncast_result result;
  // If the whole object vptr doesn't refer to the whole object type, we're
  // in the middle of constructing a primary base, and src is a separate
  // base.  This has undefined behavior and we can't find anything outside
  // of the base we're actually constructing, so fail now rather than
  // segfault later trying to use a vbase offset that doesn't exist.
  const void *whole_vtable = *static_cast <const void *const *> (whole_ptr);
  const vtable_prefix *whole_prefix =
    adjust_pointer <vtable_prefix> (whole_vtable,
                                    -offsetof (vtable_prefix, origin));
  if (whole_prefix->whole_type != whole_type)
    return NULL;
  
  whole_type->__do_dyncast (src2dst, __class_type_info::__contained_public,
                            dst_type, whole_ptr, src_type, src_ptr, result);
  if (!result.dst_ptr)
    return NULL;
  if (contained_public_p (result.dst2src))
    // Src is known to be a public base of dst.
    return const_cast <void *> (result.dst_ptr);
  if (contained_public_p (__class_type_info::__sub_kind (result.whole2src & result.whole2dst)))
    // Both src and dst are known to be public bases of whole. Found a valid
    // cross cast.
    return const_cast <void *> (result.dst_ptr);
  if (contained_nonvirtual_p (result.whole2src))
    // Src is known to be a non-public nonvirtual base of whole, and not a
    // base of dst. Found an invalid cross cast, which cannot also be a down
    // cast
    return NULL;
  if (result.dst2src == __class_type_info::__unknown)
    result.dst2src = dst_type->__find_public_src (src2dst, result.dst_ptr,
                                                  src_type, src_ptr);
  if (contained_public_p (result.dst2src))
    // Found a valid down cast
    return const_cast <void *> (result.dst_ptr);
  // Must be an invalid down cast, or the cross cast wasn't bettered
  return NULL;
}

2  libstdc++-v3/libsupc++/class_type_info.cc

// Copyright (C) 1994-2017 Free Software Foundation, Inc.
//
// This file is part of GCC.
//
// GCC is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3, or (at your option)
// any later version.
// GCC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.
#include "tinfo.h"
namespace __cxxabiv1 {
__class_type_info::
~__class_type_info ()
{}
bool __class_type_info::
__do_catch (const type_info *thr_type,
            void **thr_obj,
            unsigned outer) const
{
  if (*this == *thr_type)
    return true;
  if (outer >= 4)
    // Neither `A' nor `A *'.
    return false;
  return thr_type->__do_upcast (this, thr_obj);
}
bool __class_type_info::
__do_upcast (const __class_type_info *dst_type,
             void **obj_ptr) const
{
  __upcast_result result (__vmi_class_type_info::__flags_unknown_mask);
  
  __do_upcast (dst_type, *obj_ptr, result);
  if (!contained_public_p (result.part2dst))
    return false;
  *obj_ptr = const_cast <void *> (result.dst_ptr);
  return true;
}
__class_type_info::__sub_kind __class_type_info::
__do_find_public_src (ptrdiff_t,
                      const void *obj_ptr,
                      const __class_type_info *,
                      const void *src_ptr) const
{
  if (src_ptr == obj_ptr)
    // Must be our type, as the pointers match.
    return __contained_public;
  return __not_contained;
}
bool __class_type_info::
__do_dyncast (ptrdiff_t,
              __sub_kind access_path,
              const __class_type_info *dst_type,
              const void *obj_ptr,
              const __class_type_info *src_type,
              const void *src_ptr,
              __dyncast_result &__restrict result) const
{
  if (obj_ptr == src_ptr && *this == *src_type)
    {
      // The src object we started from. Indicate how we are accessible from
      // the most derived object.
      result.whole2src = access_path;
      return false;
    }
  if (*this == *dst_type)
    {
      result.dst_ptr = obj_ptr;
      result.whole2dst = access_path;
      result.dst2src = __not_contained;
      return false;
    }
  return false;
}
bool __class_type_info::
__do_upcast (const __class_type_info *dst, const void *obj,
             __upcast_result &__restrict result) const
{
  if (*this == *dst)
    {
      result.dst_ptr = obj;
      result.base_type = nonvirtual_base_type;
      result.part2dst = __contained_public;
      return true;
    }
  return false;
}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值