0

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

这什么时候发生?

4

6 回答 6

8

多重继承会导致偏移,跳过对象中额外的 v-table 指针。通用名称是“这个指针调整器 thunking ”。

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

于 2013-07-21T13:47:07.473 回答
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()where c isnullptr因为对象内thisB子对象的C调用被移动了四个字节(由于A子对象)。

让我们尝试说明一个C对象的布局:

0:  | n |
4:  | m |

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

整个对象的this以及子对象的 都指向偏移量 0。但是,当我们要引用子对象时(调用由 定义的方法时),需要调整该值,使其点在子对象的开头。因此+4。thisABBthisB

于 2013-07-21T13:47:41.587 回答
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

活生生的例子。


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

于 2013-07-21T13:41:41.610 回答
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()

于 2013-07-21T13:28:25.547 回答
0

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

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

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

struct foo : public bar, public baz {};

编译器将分配foobar在同一地址,并将baz偏移sizeof(bar). 因此,在某些实现下,可能会nullptr -> some_other_thing()导致 non-null this

Coliru 的这个示例演示了(假设您从未定义行为中获得的结果与我所做的相同)情况,并显示了assert(this != nullptr)未能检测到该情况。(归功于@DyP,我基本上是从他那里窃取了示例代码)。

于 2013-07-21T13:41:24.433 回答
-3

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

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

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

上面的示例将毫无错误地运行,如果没有该断言,则如果更复杂变得难以调试。

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

于 2013-07-21T13:30:54.133 回答