gpt4 book ai didi

c - 严格的别名规则指定不正确吗?

转载 作者:行者123 更新时间:2023-12-02 00:56:50 26 4
gpt4 key购买 nike

作为previously established,形式为并集

union some_union {
type_a member_a;
type_b member_b;
...
};

具有n个成员的对象在重叠存储中包含n +1个对象:一个对象用于联合本身,每个对象对应一个对象。显然,即使读取的工会成员不是最后一个写入的工会成员,您也可以按任意顺序自由地对其进行读写。绝对不会违反严格的别名规则,因为访问存储所使用的左值具有正确的有效类型。

这是脚注95的 further supported,它解释了类型调整是联合的预期用途。

严格别名规则启用的优化的一个典型示例是此函数:
int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (*i);
}

编译器可能会将其优化为类似
int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (1);
}

因为它可以安全地假定对o​​jit_code的写入不会影响 *f的值。

但是,当我们将两个指针传递给同一联合的成员时会发生什么?考虑以下示例,假设一个典型平台,其中 *i是IEEE 754单精度浮点数, float是32位二进制补码整数:
int breaking_example(void)
{
union {
int i;
float f;
} fi;

return (strict_aliasing_example(&fi.i, &fi.f));
}

如先前所建立的, intfi.i指的是重叠的存储区域。以任何顺序读取和写入它们是无条件合法的(只有在联合已初始化后才可以写入)。我认为,先前讨论的所有主要编译器执行的优化都会产生错误的代码,因为不同类型的两个指针合法指向同一位置。

我不敢相信我对严格的别名规则的解释是正确的。由于前面提到的极端情况,严格的混叠设计所针对的最佳优化似乎是不可能的。

请告诉我为什么我错了。

研究期间出现了一个 related question

在添加您自己的答案之前,请阅读所有现有答案及其评论,以确保您的答案添加了新的论点。

最佳答案

从您的示例开始:

int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (*i);
}

首先让我们承认,在没有任何联合的情况下,如果 if都指向同一个对象,这将违反严格的别名规则;假设对象没有有效类型,则 *i = 1将有效类型设置为 int*f = 1.0然后将其设置为 float,最后的 return (*i)然后通过 float类型的左值访问 int有效类型的对象,这显然是不允许的。

问题是,如果 if都指向同一联盟的成员,这是否仍将构成严格混叠违规。在工会成员上,通过“。”进行访问。成员访问运算符,规范指出(6.5.2.3):

A postfix expression followed by the . operator and an identifier designates a member of a structure or union object. The value is that of the named member (95) and is an lvalue if the first expression is an lvalue.



上面提到的脚注95说:

If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation.



显然,这旨在允许通过联合进行类型修饰,但应注意:(1)脚注是非规范性的,也就是说,脚注不应禁止行为,而应澄清脚注的某些部分的意图。 (2)编译器供应商认为通过联合进行类型修剪的这种允许仅被视为通过联合成员访问操作符申请访问-因为否则严格的别名是毫无意义的,因为任何潜在的别名访问也可能是同一联合的成员。

您的示例通过指向不存在的或至少非 Activity 的联合成员的指针进行存储,从而实现了严格的别名冲突(因为它使用不合适的类型的左值访问了 Activity 的成员)或使用了不表示对象(因为与非 Activity 成员相对应的对象不存在)-可以用任何一种方式争论,并且标准也不明确,但是两种解释都意味着您的示例具有未定义的行为。

(我可能会补充说,我看不到脚注允许通过联合进行类型修饰的方式如何描述规范中固有的行为-也就是说,这似乎违反了不禁止行为的ISO规则;规范中似乎没有其他内容以便允许通过联合进行类型修饰。此外,阅读规范文本有点麻烦,因为要求这种形式的对等形式要求必须通过联合类型立即进行访问)。

规范的另一部分经常引起困惑,但是,在6.5.2.3中也是如此:

One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible.



尽管由于没有通用的初始序列,所以这不适用于您的示例,但我已经看到人们认为这是管理类型修剪的通用规则(至少在涉及通用的初始序列时);他们认为,这意味着只要可见完整的联合声明,就应该有可能使用通过指向不同联合成员的两个指针的这种类型的修剪(因为在上面引用的段落中出现了这种含义的词)。但是,我要指出的是,以上段落仍仅适用于通过“。”进行的工会成员访问。运算符(operator)。在这种情况下,协调这种理解的问题在于,无论如何,完整的工会声明必须始终可见,因为否则您将无法引用工会成员。我认为正是这种措辞上的毛刺,加上示例3中类似的措辞不佳(以下内容不是有效的片段(因为并集类型不可见...),而并集可见性并不是真正的决定因素) ,这使某些人相信common-initial-sequence异常旨在全局应用,而不仅仅是通过“。”进行成员访问。运算符,作为严格别名规则的异常(exception);并且得出这个结论之后,读者可能会解释有关类型修剪的脚注也将在全局范围内应用,有些人会这样做:例如,参见有关 this GCC bug的讨论(请注意,该错误已长期处于SUSPENDED状态)。

(顺便说一句,我知道一些编译器没有实现“全局通用初始序列”规则。我没有具体意识到任何实现“全局通用初始序列”规则的编译器,同时也不允许任意类型的修剪,但是委员会对 Defect Report 257的回应表明,他们希望规则是全局性的,但是,我个人认为,仅类型的可见性应该改变不包含代码的语义的想法指代该类型存在严重缺陷,我知道其他人也同意)。

在这一点上,您可能会质疑如何通过成员访问运算符读取非 Activity 的联合成员不会违反严格的别名,如果通过指针这样做会违反严格的别名。这再次是规范有些模糊的 Realm 。关键可能在于确定哪个左值负责访问。例如,如果一个联合对象 u有一个成员 a,并且我通过表达式 u.a读取了它,那么我们可以将其解释为对成员对象的访问( a)或仅对联合对象的访问( u)然后从中提取成员值。在后一种情况下,没有混叠冲突,因为明确允许它通过包含合适成员(6.5¶7)的聚合类型的左值访问对象(即 Activity 成员对象)。确实,6.5.2.3中的成员访问运算符的定义确实支持这种解释,即使有些微弱:该值是命名成员的值-虽然它可能是左值,但不必访问该成员所引用的对象为了获得成员的值而使用了左值,因此避免了严格的别名冲突。但这又有点延伸了。

(在我看来,通常来说,这只是一个不足的规定,只是当对象按照6.5¶7“通过左值表达式访问它的存储值...时;我们当然可以为自己做出一个合理的确定,但是我们必须请谨慎允许通过上述类型的联合进行类型打折,否则将不理会脚注95。尽管经常会产生不必要的麻烦,但有时仍缺少必要的详细说明。

在某些时候,关于联合语义的争论总是引用 DR 236。实际上,您的示例代码表面上与该缺陷报告中的代码非常相似。我会指出:
  • “委员会认为示例2违反了6.5第7段中的别名规则” –这与我上面的推理并不矛盾;
  • “为了不违反规则,示例中的函数f应该写为”-这支持我上面的推理;您必须使用并集对象(和“。”运算符)来更改 Activity 成员类型,否则,您将访问不存在的成员(因为联合一次只能包含一个成员);
  • DR 236中的示例与类型绑定(bind)无关。这是关于是否可以通过指向非 Activity 联盟成员的指针来分配该成员的确定。所讨论的代码与此处所讨论的代码略有不同,因为它在写入第二个成员之后不再尝试再次访问“原始”联合成员。因此,尽管示例代码在结构上相似,但缺陷报告在很大程度上与您的问题无关。
  • 委员会在DR 236中的回应声称“两个程序都调用未定义的行为”。但是,讨论不支持此操作,该讨论仅显示示例2调用了未定义的行为。我相信回应是错误的。
  • 关于c - 严格的别名规则指定不正确吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38798140/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com