【问题标题】:Is converting an integer to a pointer always well defined?将整数转换为指针总是定义明确的吗?
【发布时间】:2020-05-25 13:43:55
【问题描述】:

这是有效的 C++ 吗?

int main() {
    int *p;
    p = reinterpret_cast<int*>(42);
}

假设我从不取消引用 p

查找 C++ 标准,我们有

C++17 §6.9.2/3 [basic.compound]

3每个指针类型的值都是以下之一:

  • 指向对象或函数的指针(该指针被称为指向对象或函数),或
  • 超过对象末尾的指针 ([expr.add]),或
  • 该类型的空指针值 ([conv.ptr]),或
  • 一个无效的指针值。

一个指针类型的值,它是一个指向或超过一个指针结尾的指针 object 表示内存中第一个字节的地址 ([intro.memory]) 对象或内存中的第一个字节占用 结束后对象分别占用的存储空间。 [ 注意:超过对象末尾的指针 ([expr.add]) 不是 被认为指向对象类型的不相关对象 可能位于该地址。指针值在以下情况下变为无效 它表示的存储达到其存储期限的结束;看 [基本.stc]。 — 尾注] 用于指针算术 ([expr.add]) 和比较 ([expr.rel], [expr.eq]),指针过去 考虑 n 个元素的数组 x 的最后一个元素的结尾 等效于指向 x 的假设数组元素 n 的指针 并且不是数组元素的类型 T 的对象被认为是 属于具有一个 T 类型元素的数组。

p = reinterpret_cast&lt;int*&gt;(42); 不适合可能的值列表。并且:

C++17 §8.2.10/5 [expr.reinterpret.cast]

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

C++ 标准似乎没有更多地说明整数到指针的转换。查看 C17 标准:

C17 §6.3.2.3/5(强调我的)

整数可以转换为任何指针类型。除了作为 先前指定,结果是实现定义的,可能不是 正确对齐,可能不指向被引用的实体 类型,并且可能是陷阱表示.68)

C17 §6.2.6.1/5

某些对象表示不需要表示 对象类型。如果一个对象的存储值有这样一个 表示并由不具有的左值表达式读取 字符类型,行为未定义。如果这样的表示是 由修改对象的全部或任何部分的副作用产生 通过没有字符类型的左值表达式, 行为未定义。50) 这种表示称为陷阱 表示。

对我来说,似乎任何不适合 [basic.compound] 列表的值都是陷阱表示,因此 p = reinterpret_cast&lt;int*&gt;(42); 是 UB。我对么?还有什么东西使p = reinterpret_cast&lt;int*&gt;(42); 未定义吗?

【问题讨论】:

  • 第一个引用是关于指针value,而不是你如何获得它,所以我认为它在这里无关紧要。 reinterpret_cast&lt;int*&gt;(42) (可能)是一个无效的指针值,它适合您第一个引用的第 4 个项目符号。另外,“整数类型或枚举类型的值可以显式转换为指针。” - 这怎么不能回答你的问题?
  • 我读过 C++17 §6.9.2/3 ..“无效的指针值”作为指针值可能采用的四种可能和允许的形式之一。因此,“无效的指针值”(例如指向无效对象)仍然是定义的行为。 UB 来自定义指针操作含义的那些部分(例如算术、解引用)。在“无效指针值”上还有一个定义的行为,即“任何指针值都可以转换为整数类型”,无论指针值是否为“无效指针值”。
  • @Ayxan 如果指针只有在它们指向的对象达到其存储期限结束时才会变为无效,int *p 将是 UB,所以我不认为您采用该引用的方式是对。
  • 顺便说一句,42 可能是 int* 的未对齐值。

标签: c++ pointers casting language-lawyer reinterpret-cast


【解决方案1】:

显示的示例是有效的c++。在某些平台上,这是您访问“硬件资源”的方式(如果它无效,您会在标准文本中发现错误/错误)。

另请参阅this answer 以获得更好的解释。


更新: reinterpret_cast的第一句话就像你自己引用的那样:

整数类型或枚举类型的值可以显式转换为指针。

我建议您此时停止阅读并休息一下。剩下的只是很多细节,包括可能的实现指定的行为等。这并不意味着它是 UB/invalid。

【讨论】:

  • 根据Wikipedia “用 C 语言编写的 16 位实模式 x86 设备的 BIOS 代码可以通过取消引用空指针进行写入来将 IDT 写入机器的物理地址 0。 ".这并没有很好地定义取消引用空指针。
  • @Ayxan 我认为这是一个特例。 AFAIK 即使在 C++ 中也有有效读取地址 0 的解决方法(尽管可能取决于平台)。
  • 对硬件资源的访问是有效的,因为它只是实现定义的行为。
  • 句子“如果它无效,您在标准文本中发现了错误/错误”是错误的,因为 C++ 是由 C++ 标准定义的(当然,它有缺陷,但在这种情况下没有)而不是“某些平台”。对严重的 [语言律师] 问题提供错误答案并不是“节省每个人时间”的有效方法。
  • 该问题使用四个问号(一个在标题中,三个在问题正文中)。其中三个是指UB;剩下的说“有效”,所以我很确定“有效”也指无 UB(无论如何这是常用术语)......
【解决方案2】:

这不是 UB,而是实现定义的,您已经引用了原因(§8.2.10/5 [expr.reinterpret.cast])。如果一个指针具有无效的指针值,并不一定意味着它具有陷阱表示。它可以有一个陷阱表示,编译器必须记录这一点。这里只有一个不安全派生的指针。

注意,我们一直在生成指针值无效的指针:如果一个对象被delete释放,那么所有指向这个对象的指针都有无效的指针值。

使用结果指针也是implementation defined(不是UB):

[...] 如果 glvalue 所指的对象包含无效的指针值([basic.stc.dynamic.deallocation]、[basic.stc.dynamic.safety]),则行为是实现定义的。

【讨论】:

  • 释放或删除的内存按照标准明确属于“无效值”类别:“指针值在其表示的存储达到其存储期限结束时变为无效;”。标准在哪里说“不指向任何地方的指针具有无效值”?它没有这么说。我将我引用的措辞解释为任何不在可能值列表中的值根据定义都是陷阱值。
  • 一个指针只能有这 4 个不同的值。如果不是前 3 个,那么它的指针值无效(在格式良好的程序的情况下)。 “无效指针值”类别也包含陷阱值。例如,在某些平台上可能会发生在delete 之后,您无法访问指针本身,因为它会产生异常。对此有一个脚注:eel.is/c++draft/basic.stc#footnote-32.
  • "如果不是前 3 个,那么它的指针值无效(在格式良好的程序的情况下)。"你能引用标准中的措辞吗?
  • @Ayxan:这不是一个独立的类型。如果是这样,该标准会将其放在该列表中。这是简单的集合论:“无效指针值”集合包含“具有陷阱表示的指针”集合(该脚注也支持此论点)。
  • @Ayxan 因为你在分配指针变量的那一刻就得到了 UB。指针变量不能保存可能来自强制转换的陷阱表示。
【解决方案3】:

陷阱表示

什么:如 [C17 §6.2.6.1/5] 所述,陷阱表示是非值。它是一种位模式,用于填充为给定类型的对象分配的空间,但这种模式与该类型的值不对应。它是一种特殊的模式,可以被识别用于触发实现定义的行为。也就是说,该行为未被标准涵盖,这意味着它属于“未定义行为”的旗帜。该标准规定了何时可以(不是必须)触发陷阱的可能性,但并未尝试限制陷阱的作用。 更多信息,请参阅A: trap representation

与陷阱表示相关的未定义行为很有趣,因为实现必须检查它。未定义行为的更常见情况未定义,因此实现不需要需要检查它们。检查陷阱表示的需要是在有效实现中需要少量陷阱表示的一个很好的理由。

谁:哪些位模式(如果有)构成陷阱表示的决定由实现决定。标准不强制陷阱表示的存在;当提到陷阱表示时,措辞是允许的,如“可能”,而不是要求,如“应该”。陷阱表示是允许的,不是必需的。事实上,N2091 得出的结论是,陷阱表示在实践中基本上没有使用,因此提出了将其从 C 标准中删除的提议。 (如果删除证明不可行,它还提出了一个备用计划:明确指出实现必须记录哪些表示是陷阱表示,因为没有其他方法可以确定给定的位模式是否是陷阱表示。)

原因:理论上,陷阱表示可以用作调试辅助。例如,一个实现可以声明0xDDDD 是指针类型的陷阱表示,然后选择初始化所有未初始化的指向该位模式的指针。读取此位模式可能会触发一个陷阱,提醒程序员使用未初始化的指针。 (如果没有陷阱,崩溃可能要到稍后才会发生,使调试过程复杂化。有时早期检测是关键。)无论如何,陷阱表示需要某种陷阱才能达到目的。如果没有定义陷阱,实现就不会定义陷阱表示。

我的观点是必须指定陷阱表示。它们被故意从给定类型的值集中删除。它们不仅仅是“其他一切”。

指针值

C++17 §6.9.2/3 [basic.compound]

本节定义什么是无效指针值。在列出四种可能性之前,它声明“指针类型的每个值都是以下之一”。这意味着如果你有一个指针值,那么它就是四种可能性之一。前三个是完全指定的(指向对象或函数的指针、结束指针和空指针)。最后一种可能性(无效的指针值)在其他地方没有完全指定,因此它成为列表中包罗万象的“其他所有”条目(它一个“通配符”,借用术语厘米)。因此,本节将“无效指针值”定义为不指向某物、不指向某物结尾且不为空的指针值。如果您的指针值不符合这三个类别之一,则它是无效的。

特别是,如果我们同意reinterpret_cast&lt;int*&gt;(42) 不指向某物,不指向某物的结尾,并且不为空,那么我们必须断定它是一个无效的指针值。 (诚​​然,可以假设强制转换的结果是某些实现中指针的陷阱表示。在这种情况下,是的,它不适合可能的指针值列表,因为它不是指针值,因此它是陷阱表示。但是,这是循环逻辑。此外,基于N2091,很少有实现为指针定义任何陷阱表示,因此该假设可能毫无根据。)

[ 注意:[...] 指针值在其表示的存储达到其存储期限结束时变为无效;见 [basic.stc]。 ——尾注]

我应该首先承认这是一张便条。它在不添加新内容的情况下解释和澄清。人们应该期望在注释中没有定义。

这个注释给出了一个无效指针值的例子。它阐明了指针可以(也许令人惊讶)从“指向对象”更改为“无效指针值”而不更改其值。从形式逻辑的角度来看,这个注释是一个暗示:“if [something] then [invalid pointer]”。将此视为“无效指针”的定义是fallacy;这只是获取无效指针的方法之一的示例。

铸造

C++17 §8.2.10/5 [expr.reinterpret.cast]

整数类型或枚举类型的值可以显式转换为指针。

这明确允许reinterpret_cast&lt;int*&gt;(42)。因此,行为被定义。

为了彻底,应该确保标准中没有任何内容使42“错误数据”达到undefined behavior 由演员阵容产生的程度。 [§8.2.10/5] 的其余部分没有这样做,并且:

C++ 标准似乎没有更多地说明整数到指针的转换。

这是有效的 C++ 吗?

是的。

【讨论】:

  • "int * p = new int; p += 2; 生成一个非空指针,该指针不指向对象,也不指向对象末尾。"不,这个 sn-p 有 UB(将 2 添加到指向单个对象的指针是 UB)。
  • @geza 我想我让我对实际发生的事情的理解模糊了标准所说的内容。 :( 示例仍然可以有效,但需要太多的积累才值得。示例删除,直到我得到更多启发。
猜你喜欢
  • 2015-08-18
  • 1970-01-01
  • 2015-10-03
  • 2019-05-24
  • 2021-02-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多