使用接近最大基础整数值的哨兵指针值是否安全?

时间:2019-04-10 14:53:48

标签: c pointers

我正在检查一些代码,除了空指针外,还使用了一些特殊的值,例如(T*)-1,如果某些“创建”功能失败,通常将它们用作返回值。

所指向的类型足够大,以至于((T*)-n) + sizeof(T)会溢出,这意味着实际上无法为T类型的实例分配地址,这样可以吗?编译器能否看到类似if (ptr == (T*)-1)的东西,确定不可能并对其进行优化?

1 个答案:

答案 0 :(得分:5)

TL; DR (T*)-1可能会按实际要求工作,但是出于安全性,可移植性和面向未来的考虑,您应该使用空指针作为标记。

  

我正在检查一些代码,除了空指针外,还使用了一些特殊的值,例如(T*)-1,如果某些“创建”功能失败,通常将它们用作返回值。

实际上,某些POSIX接口(例如shmat())的行为类似,返回(void *)-1表示错误。对于他们来说,这等效于许多其他标准函数,它们返回int值-1。它是一个永远不会成为成功调用的有效返回值的值。因此,这必须适用于所有符合POSIX的实现,并且我认为其他POSIX要求也具有共同的效果,即对于void *以外的指针类型也必须保持相同。

更一般地说,C明确允许无限制地将整数转换为指针,但需要注意的是

  

除了[对于空指针常量],结果是实现定义的,   可能未正确对齐,可能未指向   引用的类型,并且可能是陷阱表示。

(C2011,6.3.2.3/5)。那么,这种转换的主要问题是

  • (T*)-1的结果是陷阱表示,在这种情况下,您描述的方案会产生未定义的行为。
  • (T*)-1的结果可能是指向T的有效指针,在这种情况下,将其用作哨兵是不安全的。

据我所知,对于可能会遇到的任何C实现,第一个都不是问题。我认为第二个在实践中也不大可能成为您的问题,但是,如果您针对的是非POSIX系统,那么我对此不太自信。

你继续问,

  

所指向的类型足够大,以至于   ((T *)-n)+ sizeof(T)将溢出,这意味着该地址永远不会   实际上分配给类型T的实例,这样可以吗?可以   编译器看到类似if(ptr ==(T *)-1)的东西,确定是   不可能并对其进行优化?

这是一个有趣的问题。假设(T*)-1不产生陷阱表示,则该规定适用:

  

两个指针比较相等当且仅当都是空指针时,两个指针   是指向同一对象的指针(包括指向对象的指针和   子对象的开头)或函数,两者都是指向一个对象的指针   越过同一数组对象的最后一个元素,或者一个指针指向   一个超过一个数组对象的末尾,另一个是指向该数组的指针。   恰好紧随其后的另一个数组对象的开始   地址空间中的第一个数组对象。

(C2011,6.5.9/6

不幸的是,这有点混乱。

尽管标准对==表达式的指针操作数的 types 设置了约束,但它并不要求它们的值是有效的指针。为避免对此有任何疑问,必须与第6.3.2.3节的内部保持一致,该节指定了涉及空指针(不限于空指针常量)的相等比较的结果。

如果x == y的操作数中至少有一个是无效指针,而不是空指针,例如我们可以假定(T *)-1,则6.5.9 / 6成立,因此该表达式的计算结果应为0。编译器可能会以此为依据来优化测试和分支。

但是,实际上,实现通常在这方面不符合要求。取而代之的是,他们从历史行为中获取线索,也许是通过对6.5.9 / 6中地址空间的短暂引用来证明自己的正当性,或者也许是对对象是什么持开放态度。对于提供地址空间平面视图的实现,这表现为==的评估是根据指针值所对应的地址是否相同,而无论这些地址与任何对象的关系如何。像这样的实现(一定不能可以优化==测试,因为它不能安全地假定它将永远失败。

最重要的是,尽管编译器不太可能优化测试,但是您不能依靠该标准来保证不会这样做。如果您使用空指针作为哨兵,则出于安全考虑,尽管我曾指出不一致,但实际上,根据6.3.2.3,在所有实现中,相同类型 do 的空指针在所有实现中均相等/ 4。

相关问题