编译器在调试模式下是否假设"this"不是nullptr?

5

我在想是否在每个成员函数中加入assert( this != nullptr );是一个好主意。我认为编译器可以决定完全忽略此断言,因为假定this不能为空指针,所以该断言始终为真并可在编译时解决。

但是如果编译器没有做出这种假设,则此断言非常有用,可以早期捕获问题。

编译器会做出这种假设吗?


结构体foo { int bar; void baz() { bar = 1; } } *crash = 0; crash->baz(); 嘿,这时的“this”是NULL。 - Jonathan Potter
4
@JonathanPotter 是你吗?我说 crash->baz() 是未定义行为,因此之后的任何结果都有可能发生。 - Luchian Grigore
@JonathanPotter:嘿,瞧,this已经被转化成了一块美味的奶酪。 - Lightness Races in Orbit
4
如果 "this" 为 null,你的程序已经出现了错误 - Kaz Dragon
2
@KazDragon 但是发现你的程序已经出了问题,这些断言的存在就是为了这个目的,不是吗? - qdii
4个回答

9
不,编译器通常不会假设这一点。甚至有些商业代码中都包含这些检查,有些不仅是断言,还有逻辑检查,例如:if (!this) { doSomeWork(); }
虽然你无法遇到 thisNULL 的情况而不陷入未定义的行为,但如果你对实现细节非常了解,那么这是一个可以进行的检查;而且,你是正确的,它确实可以帮助调试。
不过我不会把它放在每个地方,或者任何地方。如果 this 确实是 NULL,则在访问某些成员时您可能会稍后出现崩溃。如果您没有访问任何成员,请考虑将该方法标记为static。它也会不必要地使代码膨胀。

为什么你不把它放到任何地方? - qdii
2
这有点过头了 - 你最终会因为空指针引用而崩溃,所以断言失败并没有真正添加任何有用的东西。 - Paul R
@qdii 我看不出有什么好处。 - Luchian Grigore
谢谢大家,这很有道理。不过我对 if (!this) doSomeWork(); 有点惊讶,因为这是明显的未定义行为,你为什么会假设你的函数会被执行呢?下一个版本的编译器可能会忽略它。 - qdii
@qdii 这是一些低级的东西,编译器的行为是已知的并且基于此。虽然我同意这不太好。 - Luchian Grigore

3
编译器通常只在开启优化时对某些内容做预设。因此,如果你没有开启优化编译 (assert会生效,只要你没有定义NDEBUG,这与优化无关),那么assert会生效。
使用普通方法可以捕获问题,但请注意,对于虚函数,必须先解引用this指针才能调用函数,在这个检查被调用之前程序就已经崩溃了。对于大多数非虚函数,如果函数访问任何成员(如果不访问任何成员,则它本身就不应该是实例成员函数),问题将不会被忽略,因此是否添加assert是一个值得考虑的问题。

不,这并不总是被解引用以进行调用!在非虚拟调用的情况下,不需要对指针进行解引用。 - Jean-Baptiste Yunès
删除我的评论,因为你的编辑现在它没有意义了 :) - qdii

2

除了已经提到的内容,对于nullptr来测试this是没有意义的。即使指针确实是nullptrthis也不总是为零。以下是一个例子:

#include <iostream>

struct A
{
    void foo() { std::cout << this << std::endl; }
    int a;
};

struct B
{
    void boo() { std::cout << this << std::endl; }
    int b;
};

struct C: public A, public B
{
};

int main()
{
    C *c = 0;
    c->foo(); // this == 0
    c->boo(); // this == 4
    return 0;
}

如果它只能有时检测到错误,我仍然会使用它。 - qdii

-2

不必在虚拟的静态成员函数上断言它。

对于静态成员,this 被禁止使用,所以没问题。

对于非虚拟的非静态成员,由于调用被编译器静态确定,因此像 ptr->f() 这样的调用可以被转换为类似 f(ptr) 的东西,因此你可以有一个空的 this!奇怪但是可能的。所以你可以断言它。

对于虚拟成员,由于调用是动态计算的,这是不可能发生的。为了找到函数,需要通过指针解引用(通过指针查看虚函数表),所以机器将在那个时候崩溃(解引用空指针总是会崩溃),在你有任何机会进行有效调用之前。

尝试一下这个:

#include <iostream>
using namespace std;

struct foo {
  int bar;
  void baz() { bar = 1; }
  void barf() { int i = 5; cout << "barf" << this << endl;}
  virtual void barf2() { int i = 5; cout << "barf2" <<this <<  endl;}
};
int main() {
        struct foo * crash = 0;
        crash->barf(); // do not crash
        crash->barf2(); // crash
        //crash->baz(); // crash
}

因为“在非虚拟的、非静态的成员中,由于调用在编译器中是静态确定的,所以像ptr->f()这样的调用可以被翻译成类似于f(ptr)的东西,因此你可以有一个空的this!”但这是不正确的。它不是有效的代码,会引起未定义行为。(当答案为“为什么这个答案是错误的?”时,评论是多余的。) - Luchian Grigore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接