通过指针访问是否会更改严格的别名语义?

有了这些定义:

struct My_Header { uintptr_t bits; } struct Foo_Type { struct My_Header header; int x; } struct Foo_Type *foo = ...; struct Bar_Type { struct My_Header header; float x; } struct Bar_Type *bar = ...; 

说这个C代码( “案例一” )是否正确:

 foo->header.bits = 1020; 

…实际上与此代码在语义上不同( “case two” ):

 struct My_Header *alias = &foo->header; alias->bits = 1020; 

我的理解是它们应该是不同的:

  • 情况一考虑分配不能影响Bar_Type中的标题。 它只被视为能够影响其他Foo_Type实例中的标头。

  • 情况二,通过强制通过通用别名指针进行访问,将导致优化器实现所有可能包含struct My_Header类型的所有投注。 它将通过任何指针类型与访问同步。 (例如,如果你有一个指向实际上是Bar_Type ,它可以通过标题访问并可靠地找出它有哪些 – 假设标题位可以告诉你的东西。)

这依赖于优化器不会变得“聪明”并将案例二重新组合成案例一。

代码bar->header.bits = 1020;struct My_Header *alias = &bar->header; alias->bits = 1020;完全相同struct My_Header *alias = &bar->header; alias->bits = 1020; struct My_Header *alias = &bar->header; alias->bits = 1020;

严格别名规则是通过左值 对对象访问来定义的:

6.5p7对象的存储值只能由具有以下类型之一的左值表达式访问:

唯一重要的是左值的类型,以及左值指定的对象的有效类型。 不是你是否将左值的推导的一些中间阶段存储在指针变量中。


注意:自从发布以下文本以来,该问题已被编辑。 以下文本适用于malloc分配空间的原始问题,而不是截至8月23日的当前问题。

关于代码是否正确。 您的代码相当于N2013 rev 1571中的 Q80 effective_type_9.c ,它是对现有C实现的调查,着眼于起草改进的严格别名规则。

Q80。 将结构写入malloc’d区域后,是否可以通过指向不同结构类型的指针访问其成员,该结构类型在同一偏移处具有相同的叶成员类型?

绊脚石是代码(*bar).header.bits = 1020; 设置只有int位的有效类型; 或整个*bar 。 因此,读取(*foo).header.bits读取一个int ,还是读取整个*foo

只读取一个int不会是严格的别名违规(可以将int作为int读取); 但是将一个Bar_Struct作为Foo_Struct读取将是违规行为。

本文的作者考虑使用write来设置整个*bar的有效类型,尽管它们没有给出它的理由,我没有看到C标准中的任何文本支持该位置。

在我看来,目前对于您的代码是否正确没有明确的答案。

你有两个包含My_Header结构的事实是一个红色的鲱鱼,使你的想法变得复杂,而不My_Header表带来任何新东西。 您的问题可以在没有任何结构( My_Header ofcourse)之外的情况下进行陈述和澄清。

 foo->header.bits = 1020; 

编译器清楚地知道要修改哪个对象。

 struct My_Header *alias = &foo->header; alias->bits = 1020; 

同样在这里也是如此:通过非常基本的分析,编译器确切地知道alias->bits = 1020;哪个对象alias->bits = 1020; 修改。

有趣的部分来到这里:

 void foo(struct My_Header* p) { p->bits = 1020; } 

在此函数中,指针p可以对My_header类型的任何对象(或子对象)进行My_header 。 如果您有N个结构包含My_header成员或者没有结构,那么这并不重要。 可以在此函数中修改My_Header类型的任何对象。

例如

 // global: struct My_header* global_p; void foo(struct My_Header* p) { p->bits = 1020; global_p->bits = 15; return p->bits; // the compiler can't just return 1020 here because it doesn't know // if `p` and `global_p` both alias the same object or not. } 

为了说服你Foo_TypeBar_Type是红色鲱鱼并且无关紧要看这个分析与前一个案例相同的例子,它既不涉及Foo_Type也不涉及Bar_type

 // global: struct My_header* gloabl_p; void foo(struct Foo_Type* foo) { foo->header.bits = 1020; global_p->bits = 15; return foo->header.bits; // the compiler can't just return 1020 here because it doesn't know // if `foo.header` and `global_p` both alias the same object or not. } 

编写N1570 p5.6p7的方式,只有在使用字符类型的左值执行访问或通过调用memcpy等库函数时,才会定义访问结构或联合的各个成员的代码的行为。 即使结构或联合具有类型T的成员,标准(故意恕我直言)也不会使用看似不相关的类型为T lvalue来授予访问聚合存储的该部分的一揽子权限。 目前,gcc和clang似乎使用成员类型的左值来授予访问结构但不是联合的一揽子权限,但是N1570 p5.6p7不需要这样做。 它对两种聚合及其成员应用相同的规则。 因为标准不授予使用不相关的成员类型左值访问结构的一揽子权限,并且授予此类权限会损害有用的优化,所以无法保证gcc和clang将继续使用与成员类型的无关左值的此行为。

不幸的是,正如使用联合可以certificate的那样,gcc和clang在识别不同类型的左值之间的关系方面非常差,即使一个左值很明显地来自另一个左值。 给出类似的东西:

 struct s1 {short x; short y[3]; long z; }; struct s2 {short x; char y[6]; }; union U { struct s1 v1; struct s2 v2; } unionArr[100]; int i; 

标准中的任何内容都不会区分以下function对的“别名”行为:

 int test1(int i) { return unionArr[i].v1.x; } int test2a(int j) { unionArr[j].v2.x = 1; } int test2a(int i) { struct s1 *p = &unionArr[i].v1; return p->x; } int test2b(int j) { struct s2 *p = &unionArr[j].v2; p->x = 1; } 

它们都使用int类型的左值来访问与struct s1struct s2union Uunion U[100]类型的对象相关联的存储,即使int未被列为允许访问任何这些类型的允许类型。

虽然即使第一种forms会调用UB看起来也很荒谬,但如果人们认识到对标准中明确列出的作为执行质量问题明确列出的访问模式的支持,这应该不是问题。 根据公布的理论基础,标准的作者认为编译器编写者会尝试生成高质量的实现,因此没有必要禁止“符合”实现,使其具有如此低的质量而无用。 一个实现可能是“符合”而不能在它们访问union U成员v2.x的情况下处理test1a()test2b() ,但只是在某个意义上实现可能“符合”而无法执行正确处理除了某些特定的人为和无用程序之外的任何东西。

不幸的是,尽管我认为标准的作者可能已经预期质量实现能够处理像test2a() / test2b()以及test1a() / test1b() ,但gcc和clang都不能支持它们的模式可靠(*)。 别名规则的既定目的是避免强制编译器在没有证据的情况下允许别名,并且别名的可能性是“可疑的”[怀疑]。 我没有看到任何证据表明他们打算质量编译器无法识别采用unionArr[i].v1的地址并使用它的代码可能会访问与使用unionArr[i]其他代码相同的存储(其中当然,它与unionArr[i].v2明显相关。 然而,gcc和clang的作者似乎认为某些东西可能是一种高质量的实现,而不必考虑这些事情。

(*)给出例如

 int test(int i, int j) { if (test2a(i)) test2b(j); return test2a(i); } 

gcc和clang都不会认识到如果i == j,test2b(j)将访问与test2a(i)相同的存储,即使两者都访问相同数组的相同元素。