为什么 this 指针地址与析构函数中预期的不同 (c++)
我在基类析构函数中遇到了一个关于 this 指针的奇怪问题。
问题描述:
我有3个类:A1、A2、A3
A2 公开继承自A1< /strong> 并从 A3 私有继承
class A2:private A3, public A1 {...}
A3 有一个函数 getPrimaryInstance() ...返回一个 A1 -类型引用 一个 A2 实例:
A1& A3::getPrimaryInstance()const{
static A2 primary;
return primary;
}
A3 构造函数如下所示:(
A3(){
getPrimaryInstance().regInst(this);
}
其中 regInst(...) 是 A1 中定义的函数 存储指向所有 A3 实例的指针)
类似的 A3 析构函数:
~A3(){
getPrimaryInstance().unregInst(this);
}
^这里就是问题发生的地方!
当名为primary的静态A2-实例在程序终止时被销毁时,A3-析构函数将被调用,但在~A3<内部/strong> 我尝试访问我破坏的同一实例上的函数。 => 运行时访问冲突!
所以我认为可以用一个简单的 if 语句来修复,如下所示:(
~A3(){
if(this != (A3*)(A2*)&getPrimaryInstance()) //Original verison
//if (this != dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance())))
//Not working. Problem with seeing definitions, see comments below this post
getPrimaryInstance().unregInst(this);
}
双重强制转换的原因是继承:)
A1 A3
。 \ /
。 A2
(但这并不重要,可能只是(int)-casted或其他什么)
更糟糕的是它仍然崩溃。 使用调试器单步执行代码会发现,当我的 A2 primary 实例被销毁时,析构函数中的 this 指针和我得到的地址由于某种原因,调用 getPrimaryInstance() 根本不匹配!我无法理解为什么 this 指针指向的地址总是与它(据我有限的知识)应有的地址不同。 :(
在析构函数中执行此操作:
int test = (int)this - (int)&getPrimaryInstance();
还向我表明差异不是恒定的(我简要地有一个理论,即存在一些恒定的偏移量),所以它就像是两个完全不同的对象,而它应该是相同的。:(
I' m 在 VC++ Express 中进行编码 (2008)。 经过一番谷歌搜索后,我发现了以下 MS 文章:
修复:“this”指针不正确基类的析构函数
这与我遇到的问题不同(据说早在 C++.Net 2003 中就已修复)。但无论如何,症状看起来很相似而且他们确实提供了一个简单的解决方法,所以我决定尝试一下:
删除了 not-working-if 语句,并在 A2 的第二个继承前面添加了 virtual,如下所示:
class A2:private A3, public A1 {...} // <-- old version
class A2:private A3, virtual public A1 {...} //new, with virtual!
并且成功了! this 指针看起来仍然是错误的,但不再给出访问冲突。
所以我最大的问题是为什么?
为什么this指针没有指向它应该指向的位置(?)?
为什么像上面那样将virtual添加到继承中可以解决这个问题(尽管this仍然指向&getPrimaryInstance()之外的其他地方)?
这是一个错误吗?有人可以在非 MS 环境中尝试一下吗?
最重要的是:这安全吗?当然它不会再抱怨了,但我仍然担心它没有做它应该做的事情。 :S
如果有人有这方面的知识或经验并且可以帮助我理解它,我将非常感激,因为我仍在学习 C++,这完全超出了我目前的知识范围。
I have a weird problem with a this-pointer in a base-class destructor.
Problem description:
I have 3 classes: A1, A2, A3
A2 inherits publicly from A1 and inherits privately from A3
class A2:private A3, public A1 {...}
A3 has a function getPrimaryInstance() ...that returns an A1-type reference to
an A2 instance:
A1& A3::getPrimaryInstance()const{
static A2 primary;
return primary;
}
And the A3 constructor looks like this:
A3(){
getPrimaryInstance().regInst(this);
}
(Where regInst(...) is a function defined in A1 that stores pointers to all A3 instances)
Similarly the A3 destructor:
~A3(){
getPrimaryInstance().unregInst(this);
}
^Here is where the problem occurs!
When the static A2-instance named primary gets destroyed at program termination the A3-destructor will be called, but inside ~A3 I try to access a function on the same instance that I a destroying.
=> Access violation at runtime!
So I thought it would be possible to fix with a simple if-statement like so:
~A3(){
if(this != (A3*)(A2*)&getPrimaryInstance()) //Original verison
//if (this != dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance())))
//Not working. Problem with seeing definitions, see comments below this post
getPrimaryInstance().unregInst(this);
}
(Reason for double cast is the inheritance:)
A1 A3
. \ /
. A2
(But it's not important, could have just (int)-casted or whatever)
The kicker is that it still crashes.
Stepping through the code with the debugger reveals that when my A2 primary-instance gets destroyd the this-pointer in the destructor and the address I get from calling getPrimaryInstance() doesn't match at all for some reason! I can't understand why the address that the this-pointer points to is always different from what it (to my limited knowledge) should be. :(
Doing this in the destructor:
int test = (int)this - (int)&getPrimaryInstance();
Also showed me that the difference is not constant (I briefly had a theory that there be some constant offset) so it's like it's two completely different objects when it should be the same one. :(
I'm coding in VC++ Express (2008).
And after googling a little I found the following MS-article:
FIX: The "this" Pointer Is Incorrect in Destructor of Base Class
It's not the same problem as I have (and it was also supposedly fixed way back in C++.Net 2003). But regardless the symptoms seemed much alike and they did present a simple workaround so I decided to try it out:
Removed the not-working-if-statement and added in virtual in front of second inheritance to A2, like so:
class A2:private A3, public A1 {...} // <-- old version
class A2:private A3, virtual public A1 {...} //new, with virtual!
AND IT WORKED! The this-pointer is still seemingly wrong but no longer gives an access-violation.
So my big question is why?
Why does the this-pointer not point to the where it should(?)?
Why does adding virtual to the inheritance like above solve it (despite this still pointing somewhere else than &getPrimaryInstance())?
Is this a bug? Can someone try it in a non-MS environment?
And most importantly: Is this safe?? Sure it doesn't complain anymore, but I'm still worried that it's not doing what it should. :S
If anyone has knowledge of or experience from this and could help me understand it I would be very thankful since I'm still learning C++ and this is entirely outside of my current knowledge.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
你使用 C 类型转换会害死你。
在多重继承的情况下,它特别容易崩溃。
您需要使用dynamic_cast<>推翻类层次结构。虽然您可以使用 static_cast<>向上移动(正如我所做的那样),但有时我认为使用dynamic_cast<>更清楚向两个方向移动。
避免 C++ 代码中的 C 风格转换
C++ 有 4 种不同类型的转换,旨在替代 C 风格转换。您正在使用reinterpret_cast<>的等效项并且你的使用方式不正确(任何优秀的 C++ 开发人员在看到reinterpret_cast<> 时都会在这里犹豫一下)。
A2 对象的布局:(大概是这样的)。
在 getPrimaryInstance() 中,
返回的引用将指向对象的 A1 部分:
如果您使用 C 风格的强制转换,它不会检查任何内容,只会更改类型:
尽管如果您使用 C++ 强制转换,它应该正确补偿:
当然是实际值都非常依赖于编译器,并且取决于实现布局。以上只是可能发生的情况的一个示例。
尽管如前所述,调用dynamic_cast<>可能并不安全。当前正在被销毁的对象上。
那么如何
更改 regInst() 以便它为注册的第一个对象返回 true 呢? getPrimaryInstance() 将始终由第一个要创建的对象创建,因此它始终是第一个注册自身的对象。
将此结果存储在本地成员变量中,并且仅当您不是第一个时才取消注册:
问题:
确实如此。只需使用 C-Cast 拧紧指针即可。
因为它改变了内存中的布局,使得 C-Cast 不再搞乱你的指针。
否。C-Cast 的错误使用
它会做类似的事情。
不。虚拟成员的布局由实现定义。你只是碰巧很幸运。
解决方案:停止使用 C 风格的强制转换。使用适当的 C++ 转换。
You use of C casts is killing you.
It is especially liable to break in situations with multiple inheritance.
You need to use dynamic_cast<> to cast down a class hierarchy. Though you can use static_cast<> to move up (as I have done) but sometimes I think it is clearer to use dynamic_cast<> to move in both directions.
Avoid C style casts in C++ code
C++ has 4 different types of cast designed to replace the C style cast. You are using the equivalent of the reinterpret_cast<> and you are using incorrectly (Any good C++ developer on seeing a reinterpret_cast<> is going to go hold on a second here).
The layout of an A2 object: (probably something like this).
In getPrimaryInstance()
The reference returned will point at the A1 part of the object:
If you use C style casts it does not check anything just changes the type:
Though if you use a C++ cast it should compensate correctly:
Of course the actual values are all very compiler dependent and will depend on the implementation layout. The above is just an example of what could happen.
Though as pointed out it is probably not safe calling dynamic_cast<> on an object that is currently in the processes of being destroyed.
So how about
Change regInst() so that it returns true for the first object registered. The getPrimaryInstance() will always by the first object to be created so it will allways be the first to register itself.
store this result in a local member variable and only unregister if you are not the first:
Questions:
It does. Just using C-Cast screws up the pointers.
Because it changes the layout in memory in such a way that the C-Cast is no longer screwing up your pointer.
No. Incorrect usage of C-Cast
It will do somthing similar.
No. It is implementation defined how virtual members are layed out. You just happen to get lucky.
Solution: Stop using C style casts. Use the appropriate C++ cast.
造成这种差异的原因是虚拟继承会影响基类构造函数和基类析构函数的调用顺序。
this
指针的数值不同的原因是完整对象A2 Primary;
的不同“基类子对象”可以而且必须具有不同的地址。在调用任何析构函数之前,您可以使用dynamic_cast
在A1*
和A2*
之间切换。当您确定A3
对象确实是A2
的私有基础部分时,您可以使用C风格的强制转换来获取A3*
到A2*
。但是,一旦析构函数
~A2
的主体完成(~A3
析构函数内就是这种情况),来自A1 的
到dynamic_cast
*A2*
将失败,从A3*
到A2*
的 C 风格转换将产生未定义的行为,因为不再是任何A2
对象。因此,除非您更改
A2 Primary;
的存储/访问方式,否则可能无法执行您正在尝试的操作。The reason this makes a difference is that
virtual
inheritance affects the order in which base class constructors and base class destructors are called.The reason the numeric values of your
this
pointers aren't the same is that different "base class subobjects" of your complete objectA2 primary;
can and must have different addresses. Before any destructors are called, you can usedynamic_cast
to get betweenA1*
andA2*
. When you're certain anA3
object is really the private base part of anA2
, you can use a C-style cast to get fromA3*
toA2*
.But once the body of the destructor
~A2
has completed, which is the case within the~A3
destructor, adynamic_cast
fromA1*
toA2*
will fail and the C-style cast fromA3*
toA2*
will produce undefined behavior, since there is no longer anyA2
object.So there's probably no way to do what you're trying unless you change how
A2 primary;
is stored/accessed.仅当您具有菱形继承结构时,虚拟基类才应发挥作用,即您是共享公共基类的多个继承类。您是否在实际代码中显示了 A1、A2 和 A3 的整个实际继承树?
The virtual base class should only come into play if you have a diamond-inheritance structure, i.e. you're multiply inheriting classes sharing a common base class. Are you showing the whole actual inheritance tree of A1, A2 and A3 in your actual code?
问题可能是,当为 A2 对象调用 A3::~A3() 时,A2 对象已经被销毁,因为在 A2 销毁结束时调用了 ~A3() 。您无法再次调用 getPrimary,因为它已经被破坏。
注意:这适用于静态变量primary的情况
The problem may be that when A3::~A3() is called for an A2 object ,the A2 object is already destructed since ~A3() is called at the end of the destruction of A2. You cannot call getPrimary again since it's destructed already.
NB: this applies to the case of the static variable primary
OP @AnorZaken 评论道:
所以听起来你有类似的东西:
并且由于
class A2
不能在class A3
之前定义,我只想跳过协变返回类型。如果您需要一种从A3
获取A2&
引用的方法,请将其添加为不同方法。OP @AnorZaken commented:
So it sounds like you have something like:
And since
class A2
cannot be defined beforeclass A3
, I would just skip the covariant return type. If you need a way to get theA2&
reference from anA3
, add that as a different method.当名为primary的静态A2实例被销毁时,指向primary的指针将指向到内存中的“随机”位置。因此,您尝试访问随机内存位置,并且会遇到运行时违规。这一切都与调用析构函数和在析构函数中进行调用的顺序有关。
尝试这样的事情:
而不是
我也认为你可能会发现这个
希望我能帮助你。
When static A2-instance named primary is destroyed the pointer to primary will point to "random" place in the memory. Therefore you are trying to access a random memory location and you get the runtime violation. It all has to do with in what order you call the destructors and make the call in the destructor.
try something like this:
instead of
I also think you might find this typecasting tutorial helpful.
Hope I could help you.
您应该问两个更大的问题:
在大多数情况下,第一个问题的答案是你不这样做。向另一个类声明一个包含数据成员的类通常可以以更干净且更易于维护的代码库来处理相同的情况。
在大多数情况下,第二个问题的答案也是你不这样做。这是该语言的一个功能,您使用该功能需要您自担风险。
我建议您阅读迈耶斯的书籍并重新评估您的设计模式。
There are 2 much bigger questions you should be asking:
In most cases, the answer to #1 is you do not. Declaring a class that contains a data member to the other class usually handles this same situation in a much cleaner and easier to maintain code base.
In most cases, the answer to #2 is also you do not. It is a feature of the language that you use at your own peril.
I recommend you read the Meyers books and reevaluate your design pattern.
简短的回答。发生这种情况是因为调用了不正确的析构函数。
如需长答案,请检查此和这个
并查看 Scott Meyer 的《Effective C++》。它涵盖了此类问题。
Short answer. It happens, because incorrect destructor is calling.
For long answer check this and this
And check Scott Meyer`s Effective C++. It covers such questions.