【发布时间】:2019-11-07 10:51:54
【问题描述】:
这个问题是由 clang 和 gcc 对 nullptr 检查指针值的不公平处理引起的。对于this,它们都发出警告,但对于通过在对象上使用address-of 运算符获取的指针,它们保持安静。
我很确定这样的指针应该总是有效的,因为我们遇到了错误,因为现代编译器从快乐的 90 年代实际触发的地方删除了对 c++ 代码的此类检查。
为什么编译器在一般情况下保持安静,这让我很困惑。 if 是否有可能触发,或者这只是两个主要编译器的设计决定? 在我开始编写补丁或调试编译器开发人员之前,我想确定我'我没有遗漏什么。
#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);
}
它们生成两个版本的g,if 在g(A& 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 /O2 和 icc -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 &?如果是,A* ptr = &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