【问题标题】:Strict Aliasing Rule and Type Aliasing in C++C++ 中严格的别名规则和类型别名
【发布时间】:2019-01-29 18:12:42
【问题描述】:

我试图在违反严格的别名规则时掌握未定义的行为。为了理解它,我已经阅读了很多关于 SO 的文章。但是,仍然存在一个问题:我真的不明白两种类型何时非法别名。 cpp-reference 状态:

类型别名

每当尝试通过 AliasedType 类型的 glvalue 读取或修改 DynamicType 类型的对象的存储值时,除非满足以下条件之一,否则该行为是未定义的:

  • AliasedType 和 DynamicType 相似。
  • AliasedType 是 DynamicType 的(可能是 cv 限定的)有符号或无符号变体。
  • AliasedType 是 std::byte、(C++17 起)char 或 unsigned char:这允许将任何对象的对象表示检查为字节数组。

我还发现了一个nice example on SO,我清楚地看到了这个问题:

int foo( float *f, int *i ) { 
    *i = 1;               
    *f = 0.f;            

   return *i;
}

int main() {
    int x = 0;

    std::cout << x << "\n";   // Expect 0
    x = foo(reinterpret_cast<float*>(&x), &x);
    std::cout << x << "\n";   // Expect 0?
}

intfloat 是不相似的类型,这个程序可能会造成严重破坏。我没有看到和理解的是以下修改:

struct A
{
    int a;
};

struct B
{
    int b;
};

A foo( A *a, B *b ) { 
    a->a = 1;               
    b->b = 0;            

    return *a;
}

int main() {
    A a;
    a.a = 0;


    std::cout << a.a << "\n";   // Expect 0
    a = foo(&a, reinterpret_cast<B*>(&a));
    std::cout << a.a << "\n";   // Expect 0?
}

AB 是相似的类型,一切都很好,或者它们是非法别名,我有未定义的行为。如果它是合法的,这是因为 AB 是聚合的(如果是,我需要改变什么才能使其成为未定义行为)?

任何提示和帮助将不胜感激。

EDIT 关于重复的问题

我知道this 的帖子,但我没有看到他们在哪里阐明了哪些类型是相似的。至少没有达到我能理解的程度。因此,如果您不关闭此问题,那就太好了。

【问题讨论】:

  • 这是非法的。 AB 是不相关的类型。
  • "...A 和 B 兼容类型...": 否
  • @RichardCritten 我不明白您为什么将问题标记为重复,因为当两种类型兼容时,另一个并没有真正尝试明确,这是这个问题的主要问题。我是否再次无法理解重要的事情?
  • C 类型参考文档 "Compatible types" en.cppreference.com/w/c/language/type ; C++ 文档没有 en.cppreference.com/w/cpp/language/type 。所以 Compatible TypesC++ 无关,重复的链接解释了其他所有内容。在什么是对象以及如何操作/引用它的这一领域中,CC++ 之间存在显着差异。混合使用 2 个标准中的术语可能会导致混淆。

标签: c++ language-lawyer strict-aliasing type-punning


【解决方案1】:

不,这是不合法的,你有未定义的行为:

8.2.1 值类别[basic.lval]

11 如果程序试图访问对象的存储值 通过以下类型之一以外的glvalue 行为未定义:63

(11.1) — 对象的动态类型,

(11.2) — 对象动态类型的 cv 限定版本,

(11.3) — 一种类似于(如 7.5 中定义的)动态类型的类型 对象,

(11.4) — 有符号或无符号类型对应于 对象的动态类型,

(11.5) — 有符号或无符号类型,对应于 对象的动态类型的 cv 限定版本,

(11.6) — 聚合或联合类型,包括以下之一 其元素或非静态数据成员中的上述类型 (包括,递归地,一个元素或非静态数据成员 子聚合或包含联合),

(11.7) — 一种(可能是 cv 限定的)基类类型 对象的动态类型,

(11.8) — char、unsigned char 或 std::byte 类型


63) 此列表的目的是指定在哪些情况下, 对象可能有别名,也可能没有别名。

【讨论】:

    【解决方案2】:

    在表达式b-&gt;b = a; 中,未定义的行为不是由于赋值,而是由于类成员访问表达式b-&gt;b。如果这个表达式不是 UB,你的代码就不是 UB。

    [expr.ref]/1 中指定类成员访问构成访问对象b(在-> 的左侧):

    后缀表达式后跟一个点。或箭头 ->,可选地后跟关键字模板 ([temp.names]),然后是 id 表达式,是后缀表达式。 评估点或箭头之前的后缀表达式;[67]该评估的结果与 id-expression 一起确定整个后缀表达式的结果。

    [67] 如果对类成员访问表达式求值,即使结果对于确定整个后缀表达式的值不是必需的,例如如果 id-expression 表示静态成员,也会发生子表达式求值。

    加粗我的

    所以b-&gt;b 使用B 类型的表达式读取对象a 的值,您引用的规则在这里适用。

    【讨论】:

    • 给定一个像intPtr1 = &amp;someVolatileStructArray[i++].someIntMember; 这样的语句,标准如何描述子表达式someVolatileStructArray[i++] 的作用?由于语句 i 递增,但不执行 volatile 访问,我认为很明显代码对该子表达式做了 something,但没有 evaluate 它。
    • @supercat 如果someVolatileStructArray[i++] 我在标准中找不到任何明确的内容。我的推理是 [exp.ref] 中的规则没有说明访问虚拟基成员或虚拟函数的成员,或者在这种情况下具有 volatile。所以为了使语言一致,我想在 [exp.ref]/1 中“评估”也意味着访问。
    【解决方案3】:

    关于类似的类型,reinterpret_cast 部分有一些有用的解释和示例:

    非正式地,如果忽略顶级 cv-qualification,两种类型是相似的:

    • 它们是同一类型;或
    • 它们都是指针,指向的类型相似;或
    • 它们都是指向同一个类的成员的指针,所指向的成员的类型相似;或
    • 它们都是相同大小的数组或都是未知边界的数组,并且数组元素类型相似。

    例如:

    • const int * volatile *int * * const 相似;
    • const int (* volatile S::* const)[20]int (* const S::* volatile)[20] 相似;
    • int (* const *)(int *)int (* volatile *)(int *) 相似;
    • int (S::*)() constint (S::*)() 不相似;
    • int (*)(int *)int (*)(const int *) 不相似;
    • const int (*)(int *)int (*)(int *) 不相似;
    • int (*)(int * const)int (*)(int *) 相似(它们是同一类型);
    • std::pair&lt;int, int&gt;std::pair&lt;const int, int&gt; 不相似。

    此规则启用基于类型的别名分析,其中编译器假定通过一种类型的 glvalue 读取的值不会被写入不同类型的 glvalue 所修改(受上述例外情况的影响)。

    请注意,作为非标准语言扩展,许多 C++ 编译器放宽了此规则,以允许通过联合的非活动成员进行错误类型访问(这种访问在 C 中不是未定义的

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-31
      • 2015-10-15
      • 2013-03-11
      • 2013-10-01
      • 2011-02-15
      • 2017-02-25
      • 2018-12-14
      • 1970-01-01
      相关资源
      最近更新 更多