在关于转换对象的地址空间的 c 样式类型转换过程中实际发生了啥?
Posted
技术标签:
【中文标题】在关于转换对象的地址空间的 c 样式类型转换过程中实际发生了啥?【英文标题】:what actually happens during a c- style typecasting regarding address space of casted objects?在关于转换对象的地址空间的 c 样式类型转换过程中实际发生了什么? 【发布时间】:2011-05-09 07:39:14 【问题描述】:根据 Cline 的 C++ FAQ 一书,以下代码描述了不正确的继承。 请注意,我可以将一种水果的香蕉添加到实际上应该只包含苹果的 bagofApples 中。但是由于继承关系,香蕉被添加到 bagofApples 中。
但问题是这一行实际发生了什么:
return (Apple&) BagOfFruit::remove(); //what does happen here?
c 风格的类型转换在这里做什么? 注意 sizeof(banana) 是 4004,而苹果只有 4。 因此,虽然香蕉对象的删除函数被苹果对象访问,因为苹果函数的地址偏移量将与香蕉的地址偏移量匹配,但是当香蕉被类型转换为苹果对象时,数据成员 ar[1000] 会发生什么情况香蕉? 苹果和这里的引用在内存中的什么位置,铸造的香蕉对象(实际对象)的地址空间会发生什么变化?
Apple& a2 = bagOfApple.remove();
完整的代码如下。
#include "stdafx.h"
#include <iostream>
using namespace std;
class Fruit
public:
virtual void printClassName() const throw() = 0;
virtual ~Fruit() throw();
;
Fruit::~Fruit() throw()
class Apple : public Fruit
public:
virtual void printClassName() const throw();
;
void Apple::printClassName() const throw()
cout << "Apple\n";
class Banana : public Fruit
public:
virtual void printClassName() const throw();
protected:
int ar[1000];
;
void Banana::printClassName() const throw()
cout << "Banana\n";
//The following BagOfFruit class allows insertion and removal of objects of any
//kind-of Fruit.
class Full ;
class Empty ;
class BagOfFruit
public:
BagOfFruit() throw();
unsigned size() const throw();
void insert(Fruit& f) throw(Full);
Fruit& remove() throw(Empty);
protected:
enum maxSize_ = 20 ;
unsigned size_;
Fruit* data_[maxSize_];
;
BagOfFruit::BagOfFruit() throw()
: size_(0)
unsigned BagOfFruit::size() const throw()
return size_;
void BagOfFruit::insert(Fruit& f) throw(Full)
if (size_ == maxSize_) throw Full();
data_[size_++] = &f;
Fruit& BagOfFruit::remove() throw(Empty)
if (size_ == 0) throw Empty();
return *data_[--size_];
void insertFruitIntoBag(BagOfFruit& bag, Fruit& fruit)
bag.insert(fruit);
class BagOfApple : public BagOfFruit
public:
BagOfApple() throw();
void insert(Apple& a) throw(Full);
Apple& remove() throw(Empty);
;
BagOfApple::BagOfApple() throw()
: BagOfFruit()
void BagOfApple::insert(Apple& a) throw(Full)
BagOfFruit::insert(a);
Apple& BagOfApple::remove() throw(Empty)
return (Apple&) BagOfFruit::remove(); //what does happen here?
int _tmain(int argc, _TCHAR* argv[])
BagOfApple bagOfApple;
Banana banana;
insertFruitIntoBag(bagOfApple, banana);
cout << "Removing an Apple from bagOfApple: ";
Apple& a2 = bagOfApple.remove();
a2.printClassName();
return 0;
【问题讨论】:
你到底为什么把它标记为C?这甚至不是远程 C 代码。 【参考方案1】:“c 风格的类型转换在这里做了什么?”:说谎。撒谎 编译器,它最终会得到你。
C 风格转换的含义取决于编译器知道什么
它发生的点。如果编译器已经看到了
Banana
,并且知道它继承自Fruit
,那么它就是
一个static_cast
;如果编译器对这种关系一无所知
两者之间,是reinterpret_cast
。在这两种情况下,使用
结果(假设实际类型为Apple
)未定义
行为。当您显式转换时,编译器假定您知道什么
你正在做——它获取对象的地址,并处理
该地址的内存就好像它是Banana
。如果真的是
Banana
,一切正常;如果不是,你就骗了
编译器,它会回来困扰你。不同的大小
只是一个明显的例子——如果你写的东西超出了
Apple
的结尾,你将覆盖不属于的内存
对象,或可能触发访问冲突。但即使
Apple
和 Banana
具有相同的大小,您处于未定义的行为
土地,几乎任何事情都可能发生。
【讨论】:
【参考方案2】:首先,如果这段代码来自这本书,那么再找另一本书:
我理解它说明了从 BagOfFruit 公开派生 BagOfApples 时的故意错误(这是荒谬的,因为公开派生意味着人们可以继续将 BagOfApples 作为 BagOfFruit 使用,从而将对象插入其中不是苹果)但是 它充斥着其他问题和糟糕的设计: 水果的所有权永远不会被袋子容器占用……它们接受对对象的引用,然后获取它们的地址。通过没有insert()
获取指针,他们错误地暗示对象是按值复制的。此用户界面容易导致应用程序崩溃的错误使用。
Stroustrup 本人曾表示,异常规范已被证明是一个错误,不应使用(如果有人真的感兴趣但自己找不到,我会尝试在网上找到面试记录的链接)。
remove()
方法具有欺骗性,因为它返回值。 Comp Sci 文献通常为此使用术语pop()
。
但问题是这一行实际发生了什么:
return (Apple&) BagOfFruit::remove(); //what does happen here?
好吧,remove()
从袋子/容器中“弹出”一个水果,而 C 风格的转换只是向编译器保证弹出的水果是一个 Apple
。只有通过 BagOfApples 特定接口专门使用该袋子时,这才可能是正确的,但鉴于它公开派生自 BagOfFruit,某些代码完全有可能将其用作 BagOfFruit 并在其中插入一些其他类型的水果。如果正在返回 Apple&
但对象不是 Apple
,并且有人试图对假定的 Apple
进行操作,那么您将有未定义的行为。
实际上,对于大多数实际编译器实现,此代码可能会按预期工作。但是,假设苹果添加了一个“const char*”成员来存储它生长的区域。假设您要打印或比较该区域,但该对象实际上是Banana
:编译器可能会将具有ar[0]
的值的位重新解释为ar[1]
(对于32 位整数和64 位系统)作为@ 987654335@,然后尝试使用该无意义地址处的字符串。这很可能会导致分段错误并使您的程序崩溃。但请记住,即使这听起来不像你的确切用法会咬你,这种行为在技术上是未定义,并且可能更糟。
【讨论】:
所以你能不能给我推荐一本讨论编程语言(C++)真正方面的书,例如不是那些只说公共继承使所有公共成员成为公共、保护到保护和私有到私有的书...但是那些真正解释了为什么会发生这种情况以及它在软件开发中的用途的人。讨论软件开发的实际方面。我从 c++ faqs parashift.com/c++-faq 得到了这本书的参考,这被认为是一个很好的 c++ 常见问题资源。 . 书不好吗?看这本书goo.gl/rI6uH @ashishsony:Marshall Cline 的常见问题解答(在您链接的地址)非常精美且值得阅读 - 任何专业程序员都应该熟悉所有问题并理解他的回答,尽管您可能不同意所有这些(例如,我有时在调用代码时通过指针作为提示它们被接受为非常量 - Marshall 的常见问题解答建议参考)。我以前从未见过他的书中的任何内容......我只能猜测它没有达到在线常见问题解答所享有的批判性反馈水平,因此没有那么完美。 @ashishsony:我必须承认,在过去十年或更长时间里,我对入门/中级 C++ 编程书籍不感兴趣,所以我不是一个好人问。也就是说,我仍然认为 Stroustrup 的 C++ 编程语言 的内容经过了很好的判断,可以帮助已经掌握一般编程概念的专业程序员(例如,不懂 C++ 的优秀 C 程序员)。 最后,有很多个S.O。关于好书的问题 - 值得搜索。【参考方案3】:c 风格的类型转换有什么作用 在这里?
它什么都不做。我的意思是,您只是将引用类型更改为您的具体对象。如果你做了错误的转换,就会出现一些问题(即使编译器没有抱怨它)。 您应该避免在 C++ 中使用 c 样式强制转换。如果要将基对象引用强制转换为派生类引用,则需要在运行时检查类型。看看here。这是一个很好的教程。
所以虽然香蕉对象的remove函数被苹果对象访问了,因为苹果函数的地址偏移量将与香蕉的地址偏移量匹配
没有。
这里的apple&引用它在内存中的什么位置,被转换的香蕉对象(实际对象)的地址空间会发生什么?
什么都没有。在这种情况下,强制转换不会改变任何东西,因为它是对引用类型的强制转换,所以它创建了一个引用。 .他们只是告诉编译器不要抱怨类型。如果你需要 C++ 中的 c 风格转换,你可能做错了什么。
【讨论】:
这个转换不会“改变”任何东西,因为它是一个引用类型的转换,所以它创建了一个引用。像(float)4
这样的强制转换创建了一个值为 4.0 的临时浮点对象,这不仅仅是“告诉编译器不要抱怨类型”,因为它没有与整数 4 相同的位模式。来自派生的强制转换多继承层次结构中指向基类指针的类指针通常会导致不同的地址,这非常重要:-)
顺便说一句:在某些情况下,您可以安全地使用 static_cast
进行转换(因为您以某种方式知道该对象是派生类型),并且您想避免使用dynamic_cast
,因为您的源类型没有虚函数。它不时出现在泛型编程中。但是这个Fruit
类确实有虚函数,所以dynamic_cast
是可用的,而且看不到模板。 dynamic_cast
会在 Banana
从 BagOfApples
中读出时发现问题,尽管保存明智的类不变量(“它充满了苹果”)为时已晚。
@Steve Jessop:是的.. 感谢您为讨论增加价值。以上是关于在关于转换对象的地址空间的 c 样式类型转换过程中实际发生了啥?的主要内容,如果未能解决你的问题,请参考以下文章
转换为枚举类型需要显式转换(static_cast、C 样式转换或函数样式转换)