【问题标题】:Is initializing a pointer with an arbitrary, literal, non-zero value defined?是否使用定义的任意、文字、非零值初始化指针?
【发布时间】:2018-05-27 01:37:27
【问题描述】:

考虑:

struct T{};

int main() {
    T* p = (T*)0xDEADBEEF;
}

使用无效指针是实现定义的。取消引用它是未定义的行为。我的问题不在于这些。

我的问题是 p 的初始化是否按原样定义。

如果您认为自己已经掌握了回答此问题所需的所有信息(或者如果您发现这是重复的),则无需进一步阅读。以下是基于我的发现的一些喃喃自语:

C 标准(C++ 标准所基于)说:

6.3.2.3 指针

5 整数可以转换为任何指针类型。除先前规定外, 结果是实现定义的,可能没有正确对齐,可能不指向 引用类型的实体,可能是陷阱表示。

哪些提示可能是实现定义的。

C++ 标准仅定义(据我所知)无效指针值 的任何使用都是实现定义的。脚注特别重要,因为它似乎表明这种值的仅仅 copy 已经是指针的使用。 (或者它是指指向的值?我很困惑)

6.7 保存期限

4  当一个存储区域的持续时间结束时,表示该存储区域任何部分地址的所有指针的值将变为无效指针值 (6.9.2) .通过无效指针值的间接传递以及将无效指针值传递给释放函数具有未定义的行为。对无效指针值的任何其他使用都具有实现定义的行为。37

37 一些实现可能会定义复制无效指针值会导致系统生成的运行时错误

这种符合C标准。问题是我不相信这是一个无效指针值的例子,因为标准清楚地表明这种类型的值是由到达该地址的存储持续时间结束引起的(这显然从未发生过)。

还有许多指针算术实例是未定义行为TM,但显然没有算术或在这里对指针的值进行操作。这只是一个初始化。

【问题讨论】:

  • @RichardCritten 虽然我的问题非常接近那个问题(它实际上是由与 OP 就该问题进行的讨论引起的!),但略有不同:该问题已经确定分配的值/根据标准中的措辞,初始化 from 是一个无效的指针值。在这个问题上使用的 literal 是一个非常重要的细节,不清楚(至少对我而言)是否应该以同样的方式对待它。
  • C 标准段落的等价物是[expr.reinterpret.cast/5]:“整数类型或枚举类型的值可以显式转换为指针。转换为足够大小的整数的指针(如果有的话)这样存在于实现中)并返回相同的指针类型将具有其原始值;指针和整数之间的映射是由实现定义的。”我认为您展示的初始化是实现定义的。
  • C 标准的文本与此完全无关;建议从您的问题中删除它。
  • “以任何方式使用无效指针是实现定义的”也不正确。

标签: c++ pointers language-lawyer


【解决方案1】:

您的 C 风格演员正在执行 reinterpret_cast;像这样转换任意整数值是implementation-defined:

8.5.1.10 重新解释演员表

5 整数类型或枚举类型的值可以显式转换为指针。转换为足够大小的整数(如果实现中存在这样的整数)并返回相同指针类型的指针将具有其原始值;指针和整数之间的映射是由实现定义的。 [ 注意: 除了[basic.stc.dynamic.safety] 中的描述外,这种转换的结果不会是安全派生的指针值。 — 尾注 ]

如果结果是(被实现认为是)无效的指针值,那么它可能又是implementation-defined 当它存储在变量中时会发生什么,但这不太清楚:

6.6.4 存储期限

4 当一个存储区域的持续时间结束时,表示该存储区域任何部分地址的所有指针的值都变为invalid pointer values。 通过无效指针值的间接传递以及将无效指针值传递给释放函数具有未定义的行为。 无效指针值的任何其他使用都具有实现定义的行为。

【讨论】:

  • [basic.stc]/4 涵盖了使用无效指针值。你也可以提到 [basic.stc.dynamic.safety]/4 它涵盖了它的 id 是否存在 strict pointer safety (其中该指针始终无效)或 relaxed 指针安全性它可能无效也可能不无效
  • @M.M:我添加了链接,考虑到我刚刚在这个鼓舞人心的问题中回答了这个问题,起初这似乎是额外的。至于严格的指针安全性,这仅适用于尚未标记为可达的动态存储持续时间的对象。
  • @M.M 我相信指针安全用于 GC 的实现,也就是说,没有。
  • @DavisHerring “(被实施认为是)”为我做了。如果强制转换的结果是实现定义的,那么它的有效性肯定也是如此。我想这就是我所缺少的。非常感谢!
【解决方案2】:

根据我在桌面(UNIX、Linux、Windows)应用程序方面的经验,我认为您可以为指针分配任何值。如果你不取消引用指针,当你将这些值分配给指针时,这些系统不会导致奇怪的行为。

下面的例子展示了我见过的一种机制来处理保存指向磁盘的指针和从磁盘恢复指针。

让我们简化一下 CAD 模型的面、边和顶点之间的连接。

struct Face;
struct Edge;
struct Vertex;

struct Face
{
   std::vector<Edge*> edges;
};

struct Edge
{
   std::vector<Face*> faces;
   Vertex* start;
   Vertex* end;
};

struct Vertex
{
   std::vector<Edge*> edges;
   double x;
   double y;
   double z;
};

你在 XY 平面上有一张脸。

       E3
V4 +--------+ V3
   |        |
E4 |    F   | E2
   |        |
   +--------+
 V1     E1   V2

这样的一张脸可以使用以下格式保存到磁盘。

0 Face 4 $1 $2 $3 $4
1 Edge 1 $0 $5 $6
2 Edge 1 $0 $6 $7 
3 Edge 1 $0 $7 $8
4 Edge 1 $0 $8 $5
5 Vertex 2 $1 $4 0 0 0 
6 Vertex 2 $2 $1 10 0 0 
7 Vertex 2 $3 $2 10 10 0 
8 Vertex 2 $4 $3 0 10 0 

其中第一个数字表示对象数组中的索引,而具有 $ 前缀的字段是指向该索引处的项目的指针。

当从磁盘读取该信息时,有两次将对象恢复到可用状态。在第一遍中,索引被存储在指针的位置。在第二遍中,索引被转换为指针。在第一遍中,数字 [0 - 8] 存储在需要指针的位置。只有在第二遍之后,指针才会指向相应的对象。

长篇大论排序,指针成员变量被分配的值显然不是有效的指针,但机制完美无缺。

这对于其他平台是否会成为问题,我无法评论。我没有经验可以依靠。

【讨论】:

  • 有些处理器在将指针加载到寄存器时进行验证,然后再使用它们访问内存。 (例如:80286 处于受保护的分段模式。)此类处理器仅在将无效指针值加载到寄存器时就会出错。
  • 这不能回答问题
  • @MM,我想是的。 如果您不取消引用指针,这些系统在您将此类值分配给指针时不会导致奇怪的行为。
  • 问题是关于根据标准定义的行为是什么;与任何特定系统或系统类别无关
  • 我投了赞成票。虽然它没有回答本身的问题,但它显示了从实现定义的行为(与未定义的行为相反)获得的一个不错的“奖励”:如果您知道您的目标编译器/架构,它可以是完全可以依靠它。
猜你喜欢
  • 1970-01-01
  • 2018-11-05
  • 2020-02-19
  • 2020-07-18
  • 1970-01-01
  • 2020-11-01
  • 2021-03-22
  • 2014-02-05
  • 2015-08-25
相关资源
最近更新 更多