为什么 this 指针地址与析构函数中预期的不同 (c++)

发布于 2024-10-06 07:37:59 字数 2824 浏览 10 评论 0原文

我在基类析构函数中遇到了一个关于 this 指针的奇怪问题。

问题描述:

我有3个类:A1A2A3

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

乖乖兔^ω^ 2024-10-13 07:37:59

你使用 C 类型转换会害死你。
在多重继承的情况下,它特别容易崩溃。

您需要使用dynamic_cast<>推翻类层次结构。虽然您可以使用 static_cast<>向上移动(正如我所做的那样),但有时我认为使用dynamic_cast<>更清楚向两个方向移动。

避免 C++ 代码中的 C 风格转换

C++ 有 4 种不同类型的转换,旨在替代 C 风格转换。您正在使用reinterpret_cast<>的等效项并且你的使用方式不正确(任何优秀的 C++ 开发人员在看到reinterpret_cast<> 时都会在这里犹豫一下)。

~A3(){
    if(this != (A3*)(A2*)&getPrimaryInstance())
        getPrimaryInstance().unregInst(this);
}

Should be:

~A3()
{
   if (this != dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance())))
   {    getPrimaryInstance().unregInst(this);
   }
}

A2 对象的布局:(大概是这样的)。

     Offset   Data
A2   0x00   |------------------
     0x10   * A3 Stuff
            *------------------
     0x20   * A1 Stuff
            *------------------
     0x30   * A2 Stuff

在 getPrimaryInstance() 中,

 // Lets assume:
 std::cout << primary; // has an address of 0xFF00

返回的引用将指向对象的 A1 部分:

std::cout << &getPrimaryInstancce();
// Should now print out 0xFF20

如果您使用 C 风格的强制转换,它不会检查任何内容,只会更改类型:

std::cout << (A2*)&getPrimaryInstancce();
// Should now print out 0xFF20
std::cout << (A3*)(A2*)&getPrimaryInstancce();
// Should now print out 0xFF20

尽管如果您使用 C++ 强制转换,它应该正确补偿:

std::cout << static_cast<A2*>(&getPrimaryInstance());
// Should now print out 0xFF00
std::cout << dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance()));
// Should now print out 0xFF10

当然是实际值都非常依赖于编译器,并且取决于实现布局。以上只是可能发生的情况的一个示例。

尽管如前所述,调用dynamic_cast<>可能并不安全。当前正在被销毁的对象上。

那么如何

更改 regInst() 以便它为注册的第一个对象返回 true 呢? getPrimaryInstance() 将始终由第一个要创建的对象创建,因此它始终是第一个注册自身的对象。

将此结果存储在本地成员变量中,并且仅当您不是第一个时才取消注册:

A3()
{
    m_IamFirst = getPrimaryInstance().regInst(this);
}

~A3()
{
    if (!m_IamFirst)
    {
         getPrimaryInstance().unregInst(this);
    }
}

问题:

为什么 this 指针没有指向它应该指向的地方(?)?

确实如此。只需使用 C-Cast 拧紧指针即可。

为什么像上面那样向继承添加 virtual 可以解决这个问题(尽管它仍然指向 &getPrimaryInstance() 之外的其他地方)?

因为它改变了内存中的布局,使得 C-Cast 不再搞乱你的指针。

这是一个错误吗?

否。C-Cast 的错误使用

有人可以在非 MS 环境中尝试一下吗?

它会做类似的事情。

最重要的是:这安全吗?

不。虚拟成员的布局由实现定义。你只是碰巧很幸运。

解决方案:停止使用 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).

~A3(){
    if(this != (A3*)(A2*)&getPrimaryInstance())
        getPrimaryInstance().unregInst(this);
}

Should be:

~A3()
{
   if (this != dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance())))
   {    getPrimaryInstance().unregInst(this);
   }
}

The layout of an A2 object: (probably something like this).

     Offset   Data
A2   0x00   |------------------
     0x10   * A3 Stuff
            *------------------
     0x20   * A1 Stuff
            *------------------
     0x30   * A2 Stuff

In getPrimaryInstance()

 // Lets assume:
 std::cout << primary; // has an address of 0xFF00

The reference returned will point at the A1 part of the object:

std::cout << &getPrimaryInstancce();
// Should now print out 0xFF20

If you use C style casts it does not check anything just changes the type:

std::cout << (A2*)&getPrimaryInstancce();
// Should now print out 0xFF20
std::cout << (A3*)(A2*)&getPrimaryInstancce();
// Should now print out 0xFF20

Though if you use a C++ cast it should compensate correctly:

std::cout << static_cast<A2*>(&getPrimaryInstance());
// Should now print out 0xFF00
std::cout << dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance()));
// Should now print out 0xFF10

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:

A3()
{
    m_IamFirst = getPrimaryInstance().regInst(this);
}

~A3()
{
    if (!m_IamFirst)
    {
         getPrimaryInstance().unregInst(this);
    }
}

Questions:

Why does the this-pointer not point to the where it should(?)?

It does. Just using C-Cast screws up the pointers.

Why does adding virtual to the inheritance like above solve it (despite this still pointing somewhere else than &getPrimaryInstance())?

Because it changes the layout in memory in such a way that the C-Cast is no longer screwing up your pointer.

Is this a bug?

No. Incorrect usage of C-Cast

Can someone try it in a non-MS environment?

It will do somthing similar.

And most importantly: Is this safe??

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.

那一片橙海, 2024-10-13 07:37:59
class A2 : private A3, public A1 {...} // <-- old version
class A2 : private A3, virtual public A1 {...} //new, with virtual!  

为什么像上面那样向继承添加 virtual 可以解决这个问题(尽管它仍然指向 &getPrimaryInstance() 之外的其他地方)?

造成这种差异的原因是虚拟继承会影响基类构造函数和基类析构函数的调用顺序。

this 指针的数值不同的原因是完整对象 A2 Primary; 的不同“基类子对象”可以而且必须具有不同的地址。在调用任何析构函数之前,您可以使用 dynamic_castA1*A2* 之间切换。当您确定A3对象确实是A2的私有基础部分时,您可以使用C风格的强制转换来获取A3*A2*

但是,一旦析构函数 ~A2 的主体完成(~A3 析构函数内就是这种情况),来自 A1 的 dynamic_cast *A2* 将失败,从 A3*A2* 的 C 风格转换将产生未定义的行为,因为不再是任何 A2 对象。

因此,除非您更改 A2 Primary; 的存储/访问方式,否则可能无法执行您正在尝试的操作。

class A2 : private A3, public A1 {...} // <-- old version
class A2 : private A3, virtual public A1 {...} //new, with virtual!  

Why does adding virtual to the inheritance like above solve it (despite this still pointing somewhere else than &getPrimaryInstance())?

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 object A2 primary; can and must have different addresses. Before any destructors are called, you can use dynamic_cast to get between A1* and A2*. When you're certain an A3 object is really the private base part of an A2, you can use a C-style cast to get from A3* to A2*.

But once the body of the destructor ~A2 has completed, which is the case within the ~A3 destructor, a dynamic_cast from A1* to A2* will fail and the C-style cast from A3* to A2* will produce undefined behavior, since there is no longer any A2 object.

So there's probably no way to do what you're trying unless you change how A2 primary; is stored/accessed.

百思不得你姐 2024-10-13 07:37:59

仅当您具有菱形继承结构时,虚拟基类才应发挥作用,即您是共享公共基类的多个继承类。您是否在实际代码中显示了 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?

月亮邮递员 2024-10-13 07:37:59

问题可能是,当为 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

高跟鞋的旋律 2024-10-13 07:37:59

OP @AnorZaken 评论道:

...这是我最初尝试解决的问题之一:我希望 getPrimaryInstance() 直接返回 A2 引用,但我不能! A3还没看到A2的定义!由于 getPrimaryInstance() 是在 A3 的基类中声明的(上面未提及),因此您会得到: error C2555: 'A3::getPrimaryInstance': overriding virtual function return type different and is not covariant from 'A3Base::getPrimaryInstance' 简单地:即使我声明 A2 的存在,我也不知道有什么方法可以在声明 A2 之前告诉编译器 A2 以 A1 作为基数。 :( 如果我能解决这个问题那就太好了!

所以听起来你有类似的东西:

class A3Base {
public:
  virtual A1& getPrimaryInstance();
};

并且由于 class A2 不能在 class A3 之前定义,我只想跳过协变返回类型。如果您需要一种从 A3 获取 A2& 引用的方法,请将其添加为不同方法。

// A3.hpp
class A2;

class A3 : public A3Base {
public:
  virtual A1& getPrimaryInstance();
  A2& getPrimaryInstanceAsA2();
};

// A3.cpp
#include "A3.hpp"
#include "A2.hpp"

A1& A3::getPrimaryInstance() {
    return getPrimaryInstanceAsA2(); // no cast needed for "upward" public conversion
}

OP @AnorZaken commented:

...This was one of the original problems I tried to tackle: I would like getPrimaryInstance() to return an A2 reference directly, but I can't! A3 hasn't seen the definition of A2! Since getPrimaryInstance() is declared in the base-class of A3 (not mentioned above) you get: error C2555: 'A3::getPrimaryInstance': overriding virtual function return type differs and is not covariant from 'A3Base::getPrimaryInstance' Simply: even if I declare the existence of A2 I don't know of any way to tell the compiler that A2 has A1 as base before I declare A2. :( If I could solve this it would be the great!

So it sounds like you have something like:

class A3Base {
public:
  virtual A1& getPrimaryInstance();
};

And since class A2 cannot be defined before class A3, I would just skip the covariant return type. If you need a way to get the A2& reference from an A3, add that as a different method.

// A3.hpp
class A2;

class A3 : public A3Base {
public:
  virtual A1& getPrimaryInstance();
  A2& getPrimaryInstanceAsA2();
};

// A3.cpp
#include "A3.hpp"
#include "A2.hpp"

A1& A3::getPrimaryInstance() {
    return getPrimaryInstanceAsA2(); // no cast needed for "upward" public conversion
}
蓝海似她心 2024-10-13 07:37:59

当名为primary的静态A2实例在程序终止时被销毁时,A3析构函数将被调用,但在~A3内部我尝试访问与我破坏的情况相同。 =>运行时访问冲突!

当名为primary的静态A2实例被销毁时,指向primary的指针将指向到内存中的“随机”位置。因此,您尝试访问随机内存位置,并且会遇到运行时违规。这一切都与调用析构函数和在析构函数中进行调用的顺序有关。

尝试这样的事情:

delete a3;
delete a2-primary;

而不是

delete a2-primary;
delete a3;

我也认为你可能会发现这个

希望我能帮助你。

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!

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:

delete a3;
delete a2-primary;

instead of

delete a2-primary;
delete a3;

I also think you might find this typecasting tutorial helpful.

Hope I could help you.

殤城〤 2024-10-13 07:37:59

您应该问两个更大的问题:

  1. 为什么我需要使用私有继承?
  2. 为什么非抽象基类需要使用多重继承?

在大多数情况下,第一个问题的答案是你不这样做。向另一个类声明一个包含数据成员的类通常可以以更干净且更易于维护的代码库来处理相同的情况。

在大多数情况下,第二个问题的答案也是你不这样做。这是该语言的一个功能,您使用该功能需要您自担风险。

我建议您阅读迈耶斯的书籍并重新评估您的设计模式。

There are 2 much bigger questions you should be asking:

  1. Why do I need to use private inheritance?
  2. Why do I need to use multiple inheritance for non-abstract base classes?

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.

乖不如嘢 2024-10-13 07:37:59

简短的回答。发生这种情况是因为调用了不正确的析构函数。
如需长答案,请检查这个
并查看 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文