“this”的值何时偏移了偏移量?

时间:2013-07-21 13:21:19

标签: c++ pointers this

我想知道assert( this != nullptr );在成员函数中是否是一个好主意,有人指出如果this的值添加了偏移量,它将不起作用。在这种情况下,它不是0,而是40,这使得断言无用。

这是什么时候发生的?

6 个答案:

答案 0 :(得分:8)

多重继承可能会导致偏移,从而跳过对象中额外的v表指针。通用名称为“this pointer adjustor thunking”。

但是你的帮助太多了。空引用是非常常见错误,操作系统已经内置了一个断言。您的程序将因segfault或访问冲突而停止。您从调试器获得的诊断总是足以告诉您对象指针为空,您将看到一个非常低的地址。不仅仅是null,它也适用于MI案例。

答案 1 :(得分:4)

this调整只能在使用多重继承的类中进行。这是一个说明这一点的程序:

#include <iostream>

using namespace std;

struct A {
  int n;
  void af() { cout << "this=" << this << endl; }
};

struct B {
  int m;
  void bf() { cout << "this=" << this << endl; }
};

struct C : A,B {
};

int main(int argc, char** argv) {

  C* c = NULL;
  c->af();
  c->bf();

  return 0;
}

当我运行这个程序时,我得到了这个输出:

this=0
this=0x4

即:您的assert this != nullptr将无法捕获c->bf()的调用,其中c为nullptr,因为this内的B子对象的C A对象被移位四个字节(由于C子对象)。

让我们试着说明0: | n | 4: | m | 对象的布局:

0

左侧的数字是对象开头的偏移量。因此,在偏移A处,我们有n子对象(其数据成员为4)。在偏移B处,我们有m个子对象(其数据成员为this)。

整个对象的this以及A子对象的B都指向偏移量0.但是,当我们想要引用{{ 1}}子对象(当调用B定义的方法时)需要调整this值,使其指向B子对象的开头。因此+4。

答案 2 :(得分:1)

注意这是UB。

多重继承可以引入偏移量,具体取决于实现:

#include <iostream>

struct wup
{
    int i;
    void foo()
    {
        std::cout << (void*)this << std::endl;
    }
};

struct dup
{
    int j;
    void bar()
    {
        std::cout << (void*)this << std::endl;
    }
};

struct s : wup, dup
{
    void foobar()
    {
        foo();
        bar();
    }
};

int main()
{
    s* p = nullptr;
    p->foobar();
}

某些版本的clang ++的输出:

  

0
  为0x4

Live example.


另请注意,正如我在对OP的评论中指出的那样,assert可能不适用于虚函数调用,因为vtable未初始化(如果编译器执行动态调度,即不如果它知道*p的动态类型,则优化。

答案 3 :(得分:0)

以下是可能发生的情况:

struct A {
    void f()
    {
       // this assert will probably not fail
       assert(this!=nullptr);
    }
};

struct B {
    A a1;
    A a2;
};

static void g(B *bp)
{
    bp->a2.f(); // undefined behavior at this point, but many compilers will
                // treat bp as a pointer to address zero and add sizeof(A) to
                // the address and pass it as the this pointer to A::f().

}

int main(int,char**)
{
    g(nullptr); // oops passed null!
}

这通常是C ++的未定义行为,但对于某些编译器,它可能具有 this指针在A::f()内有一些小的非零地址的一致行为。

答案 4 :(得分:0)

编译器通常通过将基础对象按顺序存储在内存中来实现多重继承。如果你有,例如:

struct bar {
  int x;
  int something();
};

struct baz {
  int y;
  int some_other_thing();
};

struct foo : public bar, public baz {};

编译器将在同一地址分配foobarbaz将偏移sizeof(bar)。因此,在某些实现中,nullptr -> some_other_thing()可能导致非空this

This example at Coliru演示(假设您从未定义的行为得到的结果与我做的相同)情况,并显示assert(this != nullptr)未能检测到该情况。 (感谢@DyP,我基本上偷了示例代码)。

答案 5 :(得分:-3)

我认为放置断言并不是一个坏主意,例如至少可以看到下面的例子

class Test{
public:
void DoSomething() {
  std::cout << "Hello";
}
};

int main(int argc , char argv[]) {
Test* nullptr = 0;
 nullptr->DoSomething();
}

以上示例将无错误地运行,如果缺少该断言,则更复杂变得难以调试。

我试图指出null这个指针可以被忽视,并且在复杂的情况下变得难以调试,我遇到了这种情况。