`
kmplayer
  • 浏览: 512657 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

C-Style类型转换引发的问题

阅读更多
转载自http://jackaldire.com/201001/c-style-cast-issues/

有人在byr论坛C++版上问了这样一道C++面试题:
#include <iostream>
using namespace std;

class A 
{
    public: void fun()    {    }
};

class B: public A 
{
    public: virtual void fun() {    }
};

class C: public B
{
    public: void fun(){}
};

class D: virtual public A 
{
public:
    void fun(){}
};

int main(void)  
{
    ((A*)NULL)->fun();
    ((B*)NULL)->fun(); //发生错误
    ((C*)NULL)->fun(); //发生错误
    ((D*)NULL)->fun(); 
    return 0;
}


为什么((C*)NULL)->fun();会发生错误?
原因:B的fun是虚函数, 而C继承B, 所以通过C对象指针调用fun的时候触发基类virtual方法的多态特性, 需要查通过vptr查询虚表, 但是对NULL强制类型转换并不会设置vptr(vptr的设置是在构造函数中完成的),编译器就把地址0(NULL)中的内容当vptr,一解引用就 segment fault了。

其实一开始看((A*)NULL)->fun();这句也觉得别扭,没有构造对象就直接调用非static的成员方法,居然还没有运行错误。想了一想,实际上在编译期,编译器都会通过一个name mangling机制将类的成员函数转换成一个具有唯一名字的非成员函数。程序开始运行时,成员函数和非成员函数一样都被载入到内存。所以只要在调用的成员函数不用到需要由类构造函数构造的成分(比如成员变量和vptr),这种通过指针调用成员函数就不会出错。

有人在回帖中写了一道更恶心的题目:
#include <iostream>
using namespace std;

class A
{
public:
    virtual void fun(float) { cout << "A"; }
};

class B
{
public:
    virtual void fun(int) { cout << "B"; }
};

class C: public B, public A
{
public:
    void fun(float) { cout << "C float"; }
    void fun(int)   { cout << "C int"; }
};

int main(void)
{
    C* p = new C;
    ((B*)(A*)p)->fun(1);
    delete p;
    return 0;
}

输出结果:C float

首先输出C是确定的,因为无论怎么转型, p都是指向一个C对象,而A、B中的fun都是虚函数,所以调用的肯定是C类方法。输出float看起来比较诡异,原因肯定在这个(B*)(A*)c-style转型上。

    cout << p <<endl;
    cout << (A*)p << endl;
    cout << (B*)p << endl;
    cout << (B*)(A*)p << endl;

上面代码的输出为:
0x3e3f78
0x3e3f7c
0x3e3f78
0x3e3f7c
可见(B*)(A*)p和(A*)p是相同的.

分析如下:
C继承了A和B,所以一个C对象里包含了一个A对象和B对象。C对象默认和它包含的B对象对齐.由上面的输出知道(A*)p使得p偏移到了C对象中的A对象,是个static_cast。接下来(B*)再对指向A对象的p进行转型,这时候问题就出现了,B对象并不包含A对象,编译器也不知道p指向的A对象实际上是包含在一个C对象里,所以 (B*)这次转换,只是改变了p的静态类型,并没有改变p指向的位置,是个reinterpret_cast.也就是说p还是指向一个A对象。所以通过p调用fun(1)的时候,查的是A的虚表,先查到virtual void A::fun(float),再到void C::fun(float)


这个问题的关键就在(B*)(A*)p这两个C-Style的函数式转型(functional cast)上:
(A*)p是从派生类C到基类A的转换,有可能是个向从派生类到基类的static_cast,也有可能是个简单的reinterpret_cast.
从运行结果上看(A*)是个static_cast,而由(A*)到(B*)的转换不可能是static_cast(static_cast (static_cast(p)))是编译不过的),所以只能是reinterpret_cast.

这里又出现了一个问题,为什么(A*)p是个static_cast而不是reinterpret_cast?
至少这与我的直觉不服,在ISO C++标准5.4节找到如下定义(见图):



也就是说一个C-style的强制类型转换如果可以解释成多个列表里的C++ style转型,取在列表里位置最前面的一个。static_cast在reinterpret_cast前,所以得到了下面的结果:
(B*)(A*)p 等价于 reinterpret_cast(static_cast(p))


由这道题目说明(Conclusion):

   1. 千万不要用C-style对对象指针进行转型,请用C++-style的static_cast、reinterpret_cast和dynamic_cast,以免造成意料之外的错误。
   2. C++标准是个好东西,它胜过任何技术手册,一旦对语言特性有困惑,查标准是最好的解决方法。


  • 大小: 131.7 KB
分享到:
评论

相关推荐

    C++ 多种数据类型转换

    显式类型转换是程序员明确指定的转换,通常使用C-style强制类型转换 `(type)` 或 C++-style 类型转换函数 `static_cast`, `dynamic_cast`, `reinterpret_cast` 和 `const_cast`。下面我们将一一介绍: 1. **C-style...

    对象转换 c++入门知识点

    5. C风格类型转换(C-style Casts) C++还支持C风格的类型转换,如`(type)`、`(type*)`、`(type&)`和`(type*) const`。尽管它们在C++中仍可用,但推荐使用更安全的`static_cast`、`dynamic_cast`、`const_cast`和`...

    CStrTest.zip

    C++标准库提供了一些工具来操作这些字符串,但它们与C语言中的处理方式有所不同,可能会引发一些特定的问题和挑战。 在描述中没有给出具体信息,我们只能基于文件名进行推测。"CStrTest"可能是一个测试项目,用于...

    Cplusplus:学习c ++过程中的一些小转型

    - **C-style类型转换**: `(type)expression`,直接将表达式转换为目标类型,这种转换可能丢失精度或引发未定义行为。 - **构造函数转换**:类可以通过自定义构造函数实现类型转换,例如定义一个从int到MyClass的...

    ACCP5.0 内部测试

    - 强制类型转换需要注意可能发生的精度损失或溢出问题。 - 使用 `Convert` 类或 `Parse` 方法也可以进行类型转换,但需要注意异常处理,例如 `Convert.ToInt32("123")` 可能引发 `FormatException`。 #### 5. CSS...

    C++ 程序设计陷阱(中文版)

    8. **类型转换**:C++提供了多种类型转换方式,如隐式转换、显式转换和C-style转换。书中强调了类型转换的风险,并提倡使用安全的转换方式,如`static_cast`, `dynamic_cast`, `const_cast`和`reinterpret_cast`。 ...

    最常见的20中编译错误

    谨慎使用C-style转换,优先考虑C++的显式类型转换。 19. **友元声明错误**:友元函数或类的声明不正确。友元关系只在声明时建立,确保在正确的地方声明。 20. **内存管理问题**:动态分配的内存未释放,可能导致...

    More Effective(.doc)

    C++提供了多种类型转换方式,包括C-style强制类型转换、const_cast、static_cast、dynamic_cast和reinterpret_cast。书中建议优先使用C++风格的转换,因为它们更安全且可追踪。 3.3 ITEM M3:不要对数组使用多态 ...

    2021-2022计算机二级等级考试试题及答案No.18559.docx

    **知识点说明**:在进行数据类型转换时,需要注意转换的方向和兼容性。 - **j=i**:正确,从 int 到 long 的自动类型提升。 - **j=(long)i**:正确,显式类型转换。 - **i=(int)j**:正确,显式类型转换。 - **i=j**...

    AStyle_3.1.rar

    对于大型项目或多人协作的代码库,使用AStyle定期格式化代码可以极大地提高代码审查的效率,减少因格式问题引发的冲突。 为了使用AStyle,首先需要从压缩包中解压出AStyle程序,通常它是一个可执行文件,如`astyle`...

    2021-2022计算机二级等级考试试题及答案No.13547.docx

    - **异常处理**: 异常处理机制允许程序在遇到错误时优雅地处理问题,而不是突然崩溃。 - **断言处理**: 断言是一种用于调试的目的,在程序中使用assert关键字来验证假设是否正确。如果断言失败,则会引发...

    astyle格式化source insight

    Artistic Style(简称AStyle)是一款开源的源代码格式化、美化工具,支持多种编程语言,包括C, C++, C++/CLI, Objective-C, C# 和 Java。AStyle的强大之处在于它能够自动调整代码格式,遵循不同的编码规范,比如K&R...

    2021-2022计算机二级等级考试试题及答案No.17118.docx

    - **知识点概述**:在数据库查询中,交叉表查询是一种特殊类型的查询,它可以将行转换为列,或将列转换为行。 - **详细解释**:题目描述中的说法是正确的,使用向导创建交叉表查询的数据源必须来自同一个表或查询。 ...

    ExtAspNet v2.2.1 (2009-4-1) 值得一看

    -Button控件将不再自动拥有display:inline属性,如果希望两个按钮在一行显示,请为第一个按钮设置CssStyle="float:left;"属性。 -修正了弹出菜单的位置在Firefox下不正确的BUG(feedback:eroach)。 -为TriggerBox...

    规模:Swift中的单位转换器

    特征单元强类型单位除法可能会引发错误对相同类型进行运算,结果是两者中较小的单位let length = 5 . kilometer + 7 . meter // 5007 meterlet weight = 10.0 . kilogram * 5 . gram // 50000 gram 转换为相同类型的...

    2021-2022计算机二级等级考试试题及答案No.18442.docx

    22. Java语言中数值数据类型的自动转换顺序是B.byte→short→int→long→float→double。 以上是针对题目涉及知识点的详细解释,涵盖了计算机存储、数据库、操作系统、编程语言、网络协议等方面的基础知识。这些...

    2016信息工程Web开发技术复习概要Word版.docx

    隐式转换自动进行,显式转换需要通过类型转换语法(如`(type)`)手动指定。 #### 11. 类访问修饰符 - C#中类的访问修饰符包括`public`(公有)、`private`(私有)、`protected`(受保护)、`internal`(内部)和`...

    2021-2022计算机二级等级考试试题及答案No.3359.docx

    `ctod("10/01/85")`将字符串转换为日期;而`{^1985-10-1}+24`在VB中不会得到日期类型的结果,因为24代表小时,加到日期上会导致非日期类型的数据。 3. **微型计算机架构**:微型计算机的运算器、控制器及内存储器...

    delphi函数大全

    - **应用场景**:用于解决三角学问题,如计算角度、坐标转换等。 4. **`cos`函数**: - **功能**:计算给定角度的余弦值。 - **应用场景**:广泛应用于图形学、物理学等领域中的数学计算。 5. **`dec`函数**: ...

    2021-2022计算机二级等级考试试题及答案No.10073.docx

    概念设计关注于实体及其之间的关系,而逻辑设计则是将概念模型转换为具体的DBMS支持的数据模型。因此,正确答案是A,概念设计和逻辑设计。 ### 4. 文件创建 - **知识点**:在Windows等操作系统中,创建文件不仅仅...

Global site tag (gtag.js) - Google Analytics