检查悬空指针的值是安全的还是未定义行为?

11

我们只能取消引用一个有效的指针,只能检查悬空内置指针指向的地址。我们无法访问其值(指向的对象的地址中的值)。

int* ptr = nullptr;
if(ptr) // != 0x00000000
   std::cout << *ptr << '\n';

ptr = new int(1000);

if(ptr) // != 0x00000000
   std::cout << *ptr << '\n';

delete ptr; // still pointing at the address of that dynamic object but that object has been destroyed.

if(ptr) // succeeds or undefined behavior?
   std::cout << *ptr << '\n'; // of course UB here

我明白了,但是我关心的只是检查指针值是否安全或者会导致未定义行为?if(ptr)。因为假设我没有访问该地址上的值,就像这样:std::cout << *ptr


5
检查一个指针是否为nullptr无法确定所指向的对象是否已被销毁。(显然,如果指针是nullptr,那么就不存在所指向的对象,但反过来则不一定成立。) - Nathan Pierson
@fabian:非常感谢!是我自己的问题,我忘记了指针运算符。 - Itachi Uchiwa
1
自C++14开始,这是实现定义的行为,在C++14之前是未定义的:https://dev59.com/dFcP5IYBdhLWcg3wkK3U - UnholySheep
3个回答

4

检查悬空指针的值是否安全或undefined行为?

从C++14开始,这不是未定义行为,但“安全”取决于您的期望。对于此类检查,没有关于结果的保证。它可能为true或false。基于if(ptr)假定指针有效通常不安全。


看起来是未定义行为:https://dev59.com/dFcP5IYBdhLWcg3wkK3U - chux - Reinstate Monica
@chux-ReinstateMonica 已编辑。 - eerorika

4
只有在非空指针实际指向有效对象时,解引用该指针才是安全的。不幸的是,在C/C++中没有办法测试这种情况 1

1:除非您手动跟踪您的有效对象的地址,因此可以在自己的跟踪数据中搜索所指地址。

测试指针是否等于null并不是未定义行为。 但是,根据 [basic.stc.general] ,显然在一个内存块被销毁/回收后,任何指向该内存块中任何部分的指针值的使用都是实现定义的行为:

当达到存储区域的持续时间的结尾时,表示该存储区域中任何部分地址的所有指针的值都变为无效指针值。

对无效指针值进行间接寻址和将无效指针值传递给取消分配函数具有未定义的行为。

使用无效指针值的任何其他方法都具有实现定义的行为。

因此,有人可能会认为,这意味着持有已销毁对象地址的指针甚至可能无法与其他指针或甚至nullptr进行比较,因为地址无效。只有编译器才能决定这是否合法。

1
不是完全定义良好,而是实现定义。请参见对其他答案的评论。 - 463035818_is_not_a_number
1
检查指针是否等于null是一个明确定义的操作。你对这个指针做什么是另一回事。 - Remy Lebeau
1
@RemyLebeau 标准的措辞是“除了取消引用之外,对无效指针值的任何其他使用都具有实现定义的行为。”,是否有一个单独的部分涉及nullptr比较与此相矛盾? - user4442671
@Frank if (ptr) 本质上只是 if (ptr != 0) 或者 if (ptr != nullptr) 的简写。在 [conv.ptr] 中标准规定:"空指针常量可以转换为指针类型;结果是该类型的空指针值... 两个相同类型的空指针值应当相等。" 我理解这句话的意思是,如果 ptr 有一个空值,则比较保证为真,否则如果它没有空值,则比较保证为假。你在看哪个章节? - Remy Lebeau
2
@RemyLebeau [new.delete.single] 规定了 delete 会使指针“无效”,而 [basic.stc.general] 规定了使用无效指针的行为是实现定义的(这仍然是明确定义的,只是不具备可移植性安全性)。 - user4442671

0

对于 int* ptr;,表达式 ptr 总是安全的。程序的行为仍然被定义。但是

  • 指针的具体值是实现定义的。[省略号,强调我的]

[6.8.2.3.4] 指向对象或超过对象末尾的指针类型的值表示对象占用的内存中第一个字节(6.7.1)的地址或对象存储结束后内存中第一个字节的地址,分别。 [...] 指针类型的值表示是实现定义的。 布局兼容类型的指针应具有相同的值表示和对齐要求(6.7.6)。 [注:指向超对齐类型(6.7.6)的指针没有特殊表示,但它们的有效值范围受到扩展对齐要求的限制。— 结束语]

  • nullptr保证等于(==)0和NULL
  • 在布尔上下文中,nullptr评估为false,所有其他值,无论它们是否表示有效对象的地址,都评估为true
  • delete不修改指针。编辑:或许它确实会,在评论中查看讨论。
  • 指针具有与整数相同的初始化规则,这意味着局部变量、非静态成员未被初始化。int* ptr; assert(ptr==nullptr);通常不成立。但全局指针被初始化为零,并因此在开始时评估为false。

总之,尽管程序是安全的,但很容易使其最好是不确定性的。


2
据我所知,“delete不会修改指针”这个说法未必正确 - Nathan Pierson
1
答案中有 int ptr;,但代码是 int *ptr;。这是有意为之的吗? - chux - Reinstate Monica
1
@NathanPierson 但是它如何修改指针?据我所知,签名是 void operator delete(void* ptr) - 复制指针,因此它无法修改变量本身,对吗? - Quimby
@chux-ReinstateMonica 不是这样的。谢谢,以后如果我的回答有任何错别字,请随意更正 :) - Quimby
2
@Quimby,我认为这与调用的delete表达式和operator delete函数之间的区别有关。如果编译器允许按照标准来做,那么这可能是一个相当学术的观点,但在实践中,它们并不选择这样做。 - Nathan Pierson
@NathanPierson 你可能是对的,7.6.2.8标准将后者称为释放函数。因此存在差异,尽管该章节没有说明delete之后的值。可惜了。 - Quimby

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