【问题标题】:Can pointer taken from reference ever be null in well-defined c++?在定义明确的 c++ 中,从引用中获取的指针可以为空吗?
【发布时间】:2019-11-07 10:51:54
【问题描述】:

这个问题是由 clang 和 gcc 对 nullptr 检查指针值的不公平处理引起的。对于this,它们都发出警告,但对于通过在对象上使用address-of 运算符获取的指针,它们保持安静。

我很确定这样的指针应该总是有效的,因为我们遇到了错误,因为现代编译器从快乐的 90 年代实际触发的地方删除了对 c++ 代码的此类检查。

为什么编译器在一般情况下保持安静,这让我很困惑。 if 是否有可能触发,或者这只是两个主要编译器的设计决定? 在我开始编写补丁或调试编译器开发人员之前,我想确定我'我没有遗漏什么。

Toy example:

#include <iostream>
class A {
    void f(){
        if(!this) {
            std::cout << "This can't trigger, and compilers warn about it.";
        }
    }
};

void f(A& a){
    A* ptr = &a;
    if(ptr == nullptr) {
        std::cout << "Can this trigger? Because gcc and clang are silent.";
    }
}

尽管这个问题看起来很愚蠢,但我觉得它很实用。如果确实使用了臭代码,则此优化会产生致命的结果,因此警告将是非常有用的诊断。

补充案例。 clang 和 gcc 都知道 check 有不断的评估,因为即使是干净的代码:

void g(A* a){
    A* ptr = a;
    if(ptr == nullptr) {
        std::cout << "Gee, can this trigger? Be cause gcc and clang are silent.";
    }
}

void g(A& a) {
    g(&a);
}

它们生成两个版本的gifg(A&amp; a) 中省略,因此两者都能够确定并假设不可为空性以供参考。 gcc 生成很好的可读程序集:

f(A&):
        ret
.LC0:
        .string "Can this trigger? Be cause gcc and clang are silent."
g(A*):
        test    rdi, rdi
        je      .L5
        ret
.L5:
        mov     edx, 52
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        jmp     std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
g(A&):
        ret

据我了解,装配 msvc /O2icc -fast 将检查留在原处。

编辑:我在A::f() 中错过了!,已修复。

【问题讨论】:

  • 简直是Is null reference possible?的骗子,不是吗?
  • 很可能不值得为此烦恼。没有办法让if(!this) 通过。另一方面,if(ptr == nullptr) 没有这种保证。是的A* ptr = a; if(ptr == nullptr) 永远不会失败,但谁知道A* ptr = a; ... foo(ptr); ... if(ptr == nullptr)。编译器正在抓住低调的果实,而不是进行完整的静态分析。
  • A 是否超载operator &amp;?如果是,A* ptr = &amp;a; ptr 可以是 nullptr。您需要A* ptr = std::addressof(a),但这是一个函数调用,通常函数可以返回nullptr。对于这些情况,静态分析并不比检查this == nullptr 简单。
  • @BaummitAugen 是的,标准报价确实回答了它,但我发现 eeroika 的回答也很有用。我不介意将此标记为对另一个的欺骗。我发誓我使用搜索并没有找到它=S.
  • @NathanOliver 我已经看到使用 gcc 4.9.4 编译的代码通过并有意义。并不是说它是好代码,但它确实有效,并且编写它的人依赖于检查。所以就是这样。有指向对象的指针向量,其中一些被删除并且指针值归零,然后在容器上循环称为成员函数,以if(!this) return;开头。类似的东西。我什至不确定这是否算作 UB,因为在较新的编译器删除检查之前,没有人取消引用无效指针。除非写if(this)或者依赖结果被定义为UB...

标签: c++ pointers reference language-lawyer null-pointer


【解决方案1】:

在定义良好的 c++ 中,从引用中获取的指针是否可以为空?

没有。此答案中的标准引号:Is null reference possible?

虽然,在特定情况下,使用类类型的重载 operator&amp; 获取指针可以返回任何内容,包括 null。

if 是否有可能触发?

不在A::f::f 中。可以在g(A*) 中触发,但在从g(A&amp;) 调用时则不能。

警告将是一个非常有用的诊断。

如您所见,GCC 和 Clang 都不够聪明,无法在这种情况下检测到错误,但它们确实检测到了相同错误的更简单版本:

海合会

warning: the compiler can assume that the address of 'a' will never be NULL [-Waddress]
     if(&a == nullptr) {
        ~~~^~~~~~~~~~
warning: nonnull argument 'a' compared to NULL [-Wnonnull-compare]
     if(&a == nullptr) {
     ^~

叮当

warning: reference cannot be bound to dereferenced null pointer in well-defined C++ code; comparison may be assumed to always evaluate to false [-Wtautological-undefined-compare]
   if(&a == nullptr) {

【讨论】:

  • 好吧,除了this,我无法让它发出任何诊断信息,但我没想到&amp;a == nullptr。我猜你的答案+相关问题的答案的标准引用解决了它。我承认,我认为在相同的上下文中进行验证与this 一样容易。至少在没有人触摸指针时。我想不是,因为编译器需要记住指针在整个范围内都是非空的。使用 this 则不必。
  • @luk32 这在技术上是可行的,甚至很容易证明您的示例中的错误。编写编译器来检测所有可证明的情况是不可能的,即使是简单的情况也需要进行检测。
  • 当然,我同意。我只是认为这与已经实施的诊断相同。这是错误的。我也知道两个编译器都能够证明这一点,因为他们使用这个假设生成了程序集,但他们在优化阶段证明了这一点。更容易接受诊断没有发出,因为它基于优化级别,甚至必须由优化器发出,这可能被认为是不必要的复杂架构等。
猜你喜欢
  • 2021-03-15
  • 1970-01-01
  • 2021-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-27
相关资源
最近更新 更多