安全地检查`this`是否为空

时间:2016-11-24 09:54:18

标签: c++ undefined-behavior

首先,我知道在空指针上调用方法是未定义的行为。我也知道,因为这不应该发生,编译器可以(并且确实)假设this总是非空。

但在实际代码中,您有时会意外地执行此操作。通常,它没有任何不良影响,除了当然this在方法中为空,并且事情可能会崩溃。

作为一个调试辅助工具,并且本着早期崩溃的精神,我将assert(this != 0)置于一个方法中,这个方法之前我偶然会调用空指针。它似乎有效,但clang抱怨道:

warning: 'this' pointer cannot be null in well-defined C++ code; comparison may be
      assumed to always evaluate to true [-Wtautological-undefined-compare]
    assert (this ! = 0);
            ^~~~     ~

我想知道检测this为空的最佳(最不正确)方法是什么。可以优化一个简单的比较。

  • 我可以在this上做一些指针算术来试图欺骗编译器,或强制它将指针视为整数。
  • 我可以使用memcmp
  • 也许有特定于编译器的扩展名称“不要优化此表达式”?

另外一个问题是,在继承的情况下,“null”这个指针实际上可能类似0x00000004,所以处理这种情况也会很好。我对Clang,MSVC或GCC的解决方案感兴趣。

6 个答案:

答案 0 :(得分:6)

在gcc中,您可以使用-fsanitize=null进行构建。 Clang也应该有这个选项。

来自man gcc

       -fsanitize=null
           This option enables pointer checking.  Particularly, the application built with this option turned on will issue an error message when it tries to dereference a NULL pointer, or if a
           reference (possibly an rvalue reference) is bound to a NULL pointer, or if a method is invoked on an object pointed by a NULL pointer.

这是一个测试程序:

[ ~]$ cat 40783056.cpp
struct A {
  void f() {}
};

int main() {
  A* a = nullptr;
  a->f();
}
[ ~]$ g++ -fsanitize=null 40783056.cpp
[ ~]$ 
[ ~]$ ./a.out 
40783056.cpp:7:7: runtime error: member call on null pointer of type 'struct A'

答案 1 :(得分:1)

你应该在调用成员函数之前检查指针,而不是在它之后。

Class *object = ...;
...;
if (object) {
  object->...
}

检查this是否为成员函数中的nullptr只能引入调用开销,并且始终应该是调用者的职责,以确保this永远不会是nullptr或悬空或野指针。 不检查rhs是否与赋值运算符中的lhs相同的原因相同。 (但是应该确保自我赋值正常工作,实现这样的运算符可能很棘手且容易出错)

this为nullptr时尝试访问成员函数或变量通常会立即使程序崩溃,并且取消引用nullptr实际上是一个ub。断言(this!= nullptr)当这= = nullptr时也会立即终止你的程序,但提供的信息比崩溃还多,这也依赖于ub。

第一种ub检查方法需要更少的努力,并提供更少的信息。前ub检查方法需要更多努力(在任何地方添加断言),但确实提供了更多信息。结果是权衡利弊?

如果可以重现崩溃,我更喜欢第一个(没有断言),可以添加一些日志并快速(O(lgn))找出哪个行崩溃了程序,以及{{1}是this。并确定哪个呼叫存在问题。

如果不是这样,那么nullptr可能会提供更多信息,但“解除引用assert”在这种情况下会有很大帮助吗?我对此表示怀疑。

因此,在我看来,正确的方法是建立一个良好的日志记录系统,并使重现错误变得更加简单。

答案 2 :(得分:1)

首先,正如对这个问题的评论所指出的那样:尝试使用内置于编译器中的消毒剂。

要回答您的问题:您可以将指针转换为Source: local data frame [10 x 3] Groups: A [3] A B LABEL <dbl> <fctr> <chr> 1 1 1A 1-2 2 1 1B 2-3 3 1 1C 3-4 4 2 2A 1-2 5 2 2B 2-3 6 3 3A 1-2 7 3 3B 2-3 8 3 3C 3-4 9 3 3D 4-5 10 3 3E 5-6 并比较该值。请注意,此转换是实施定义,并且无法保证按预期工作。

以下代码使用clang 3.9优化任何内容,但使用gcc 7.0删除空指针检查。 uintptr_t被任意选择。

0x10

demo

答案 3 :(得分:0)

也许如果你尝试assert(this != nullptr)但我认为这也会抱怨。您可能想要尝试并类型转换this,然后检查该值:

int* ptr = static_cast<int*>(this);

然后查看assert

答案 4 :(得分:0)

根据您的编译器,它可能会积极强制执行关于 thisnullptr 作为 未定义 行为的 C++ 规则。

但是,优化发布的应用程序/代码仍然需要能够优雅地处理和报告可能发生这种情况的场景;在某些情况下,它实际上可能是优化性能的有意设计的一部分(JIT 引擎 - 例如我正在研究的那些)。

在这种情况下,您需要通过明确告知编译器您的意图来与编译器携手合作。在 this 上进行任何类型的转换并尝试与 0nullptr 的任何变体进行比较将不能保证可靠地工作.通常,编译器会在优化构建中抱怨或将其优化为无效。

相反,您必须从 nullptr 成员函数 内联优化中提升您的 this 测试。为此,您需要提供一个静态(非成员)函数,该函数内联以执行您需要的测试。

以下代码满足该要求。

[[gnu::noinline]] // Required for testing `this` == nullptr;else c++/clang optimizes away
static bool af_isnullptr(void* self) { return self == nullptr; }

在您的 classstruct 中,您可以按如下方式使用它:

class Example {
  void member() {
    if(af_isnullptr(this)) {
      // handle `this == nullptr` case
    }
  }
};

另见:

答案 5 :(得分:-2)

clang是正确的,警告你 - 如果条件this == 0的计算结果为true,那么你的程序已经向南移动并暴露出未定义的行为。所以当你已经知道你的程序不能表现出来时,你期待定义的行为(正确执行assert()),这有点奇怪。

听起来就像是挂在你试图在你上方切割的绳子上并再次将两端结合在一起......

如果您想确保(this == 0)不会意外调用您的方法,只需将其设为virtual,它就会强制转储核心。