【问题标题】:Could reinterpret_cast turn an invalid pointer value into a valid one?reinterpret_cast 可以将无效的指针值转换为有效的值吗?
【发布时间】:2019-05-25 18:49:43
【问题描述】:

考虑这个联合:

union A{
  int a;
  struct{
    int b;
    } c;
  };

ca 不是layout-compatibles 类型,因此无法通过a 读取b 的值:

A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)

试用 1

对于下面的情况,我认为从 C++17 开始,我也得到了一个未定义的行为:

A x;
x.a=10;
auto p = &x.a; //(1)
x.c.b=12;      //(2)
*p+*p;         //(3) UB

让我们考虑[basic.type]/3

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

  • 指向对象或函数的指针(据说该指针指向对象或函数),或
  • 一个指针一个对象([expr.add]),或
  • 该类型的 空指针值 ([conv.ptr]),或
  • 指针值无效

我们将这 4 个指针值类别称为指针值类型

指针的值可能会从上述类型转换为另一种类型,但标准并没有明确说明这一点。 如果我错了,请随时纠正我。所以我假设在 (1) 处,p 的值是一个 指向 值的指针。然后在 (2) a 生命结束,p 的值变成一个无效的指针值。所以在 (3) 中我得到了 UB,因为我尝试在对象 (a) 的生命周期之外访问它的值。

试用 2

现在考虑这个奇怪的代码:

A x;
x.a=10;
auto p = &x.a;                 //(1)
x.c.b=12;                      //(2)
p = reinterpret_cast<int*>(p); //(2')
*p+*p;                         //(3) UB?

reinterpret_cast&lt;int*&gt;(p) 能否将指针值类型从 invalid pointer value 更改为 pointer to 值。

reinterpret_cast&lt;int*&gt;(p)被定义为等价于static_cast&lt;int*&gt;(static_cast&lt;void*&gt;(p)),那么让我们考虑如何定义static_castvoid*int*[expr.static.cast]/13

“指向cv1 void的指针”类型的纯右值可以转换为“指向cv2 T的指针”类型的纯右值,其中T是一个对象类型,cv2是相同的cv-qualification,或比 cv1 更高的 cv 资格。如果原始指针值表示内存中一个字节的地址A,并且A不满足T的对齐要求,则结果指针值未指定。否则,如果原始指针值指向对象a,并且存在T(忽略cv-qualification)类型为pointer-interconvertible 的对象b a,结果是一个指向b的指针。否则,指针值不会因转换而改变。

所以在我们的例子中,原始指针ed指向对象a。所以我想reinterpret_cast 将无济于事,因为a 不在其生命周期内。 我的阅读是否严格?这段代码能很好定义吗?

【问题讨论】:

  • 结束被指针的生命周期并不会使指针值无效。
  • @Evg RetiredNinja 链接到您无权查看的现已删除的问题。
  • @Oliv 可能我没有完全理解你的要求,但从我的直觉来看,永远不可能通过使用强制转换(任何类型)来修复已经无效的东西。您将进入未定义行为的领域。
  • @Oliv: int *p = new int; delete p;int *p; { int a; p = &amp;a; }。在这两个示例中,p 最终都具有无效的指针值。但是更改活动联合成员不会使指向先前活动和现在非活动成员的指针无效,因为存储仍然存在。

标签: c++ pointers language-lawyer c++17 undefined-behavior


【解决方案1】:

那么在(2)中,一个生命结束,p的值变成了一个无效的指针值。

不正确。 只有指针在指向具有ended its storage duration 的内存时才会失效。

在这种情况下,指针变为指向其生命周期之外的对象的指针。它指向的对象已经消失,但指针并不是规范所指的“无效”。 [basic.life] 花了相当多的时间来解释在对象的生命周期之外你能做什么和不能做什么。

reinterpret_cast 无法将指向其生命周期之外的对象的指针转换为指向其生命周期内的不同对象的指针。

【讨论】:

  • reinterpret_cast 无法将指向其生命周期之外的对象的指针转换为指向其生命周期内的不同对象的指针 所以您认为“存在 /b> 一个对象”这里timsong-cpp.github.io/cppwp/n4659/expr.static.cast#13 表示一个对象应在其生命周期内?
  • @NicolBolas: "An object is created ... when implicitly changing the active member of a union" 那么“用相同地址的新对象替换”规则不会启动,使现有指针已经能够引用新对象吗?
  • 那么*p+*p在这两种情况下都是UB吗?这就是你说的。
  • 如果对象生命周期结束时指针值不是invalid,那么它是一个指向对象basic.type/3.1的指针。但是没有对象。我认为标准中有一个漏洞。指针值也可以表示存储地址。这是具有 (pointer to,pointer past the end,null pointer value,invalid pointer value i>)
  • 我刚刚发现这一段让我满意:basic.stc/4。所以我们可以推断,在存储时长结束之前,指针值并不是无效的……
【解决方案2】:

标准中的对象概念相当抽象,与直觉有所不同。一个对象可能在其生命周期内或不在其生命周期内,不在其生命周期内的对象可以具有相同的地址,这就是联合工作的原因:活动成员的the definition 是“在其生命周期内的成员”。

指向不在其生命周期内的对象的指针仍然是指向对象的指针。 reinterpret_cast 仅在指针类型之间进行转换,而不是在其有效性之间进行转换。转换为 non-pointer-interconvertible types 得到的 UB 是由于 strict-aliasing rule 而不是由于指针的有效性。

在您的所有试验中,包括您的 follow up question,您正在以 aren't allowed 的方式使用不在其生命周期内的对象,即访问它,因此是 UB。

【讨论】:

  • 所以您认为 BenVoigt 在其对另一个问题的回答中的论点不成立?
  • 第二段我不同意,没有违反strict-aliasing rule(这是新版本去掉了一个核心语言问题)。
  • 指向不在其生命周期内的对象的指针仍然是指向对象的指针。我没有找到相关的文档。这是我的观点。我唯一发现的是指针不是无效指针:basic.stc/4
  • “指向不在其生命周期内的对象的指针仍然是指向对象的指针”......它有一个指向对象类型的指针,我同意不会改变。但它的价值仍然是“指向”价值的指针吗?如果您觉得我刚刚写的内容很奇怪,请仔细阅读上述问题中链接的 [basic.type]/3。
  • @Oliv 我提到的严格别名规则违规是为了澄清,通过它来转换指针通常是 UB,而不是通过指针有效性。 “存储的持续时间结束” 不是 “生命周期的结束”,只有当存储本身消失时,您才会得到它,即自动变量超出范围或删除动态内容。
【解决方案3】:

迄今为止,C 和 C++ 标准的每个版本对于如何处理工会成员的地址都是模棱两可或矛盾的。 C 标准的作者不想要求编译器对函数可能被以下结构调用的可能性做出悲观的考虑:

someFunction(&myUnion.member1, &myUnion.member2);

如果函数会导致myUnion 的一个成员的值在通过另一个访问之间发生更改。如果代码无法执行以下操作,那么获取工会成员地址的能力将毫无用处:

someFunction1(&myUnion.member1);
someFunction2(&myUnion.member2);
someFunction3(&myUnion.member1);

该标准的作者预计,用于各种目的的质量实现将“以环境的文档化方式”处理未定义行为的构造,这样才能最好地服务于这些目的,因此认为为此类构造提供支持实施质量问题比试图制定必须支持模式的精确规则更简单。在不知道调用上下文的情况下为第二个示例中的被调用函数生成代码的编译器将无法交错由两个函数执行的访问,并且在处理上述代码时内联扩展它们的高质量编译器将不会有问题注意到当每个指针派生自myUnion 时。

C89 标准的作者认为没有必要为指向联合成员的指针的行为定义精确的规则,因为他们认为编译器编写者希望产生高质量的实现会促使他们明智地处理适当的情况,即使没有这样的规则.不幸的是,一些编译器作者懒得处理像上面第二个例子这样的情况,而且后来的 C 和 C++ 标准的作者们没有认识到高质量的编译器没有任何理由不能处理这种情况,而是向后弯腰想出奇怪的扭曲、模棱两可和矛盾的规则来证明这种编译器行为是合理的。

因此,只有在结果指针将用于访问单个存储字节的情况下,地址操作符才应被视为有意义地适用于联合成员,或者直接使用字符类型,或者传递给类似的函数memcpy 以这种方式定义。除非或直到对标准进行重大修改,或者在附录中描述实现可以提供超出标准要求的可选保证的方式,否则最好假装联合成员 - 像位域 - 不存在的左值没有地址。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-04
    • 2021-09-09
    • 2013-06-29
    • 1970-01-01
    • 2016-01-28
    • 2023-03-07
    • 2013-01-10
    • 2017-01-19
    相关资源
    最近更新 更多