代码之家  ›  专栏  ›  技术社区  ›  HostileFork says dont trust SE

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

  •  5
  • HostileFork says dont trust SE  · 技术社区  · 6 年前

    有了这些定义:

    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码对吗( “案例1” ):

    foo->header.bits = 1020;
    

    …实际上是不同的 语义上 来自此代码( “案例二” ):

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

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

    • 第一种情况认为分配无法影响bar_类型中的头。它只被视为能够影响其他foo_类型实例中的头。

    • 第二种情况是,通过一个通用的别名指针强制访问,将导致优化器意识到对于任何可能包含 struct My_Header . 它将通过任何指针类型与访问同步。 (例如,如果你有 Foo_Type 它指的是 Bar_Type ,它可以通过头访问并可靠地找出它拥有的内容——假设头位可以告诉您这一点。)

    这依赖于优化器没有得到“智能”并使案例2回到案例1中。

    3 回复  |  直到 6 年前
        1
  •  3
  •   M.M    6 年前

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

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

    6.5P7对象应访问其存储值。 仅通过具有以下内容之一的左值表达式 类型:

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


    注意:问题是在发布以下文本后编辑的。以下文本适用于原始问题,其中空间由 malloc 而不是截至8月23日的当前问题。

    关于代码是否正确。您的代码相当于Q80 effective_type_9.c 在里面 N2013 rev 1571 这是对现有C实现的调查,旨在起草改进的严格混叠规则。

    Q80。将结构写入malloc_d区域后,是否可以通过指向具有相同叶成员类型且偏移量相同的不同结构类型的指针访问其成员?

    绊脚石是代码是否 (*bar).header.bits = 1020; 仅将有效类型设置为 int 比特;或整个 *bar . 因此,是否阅读 (*foo).header.bits 读取一个 int 或者它读整个 *foo ?

    只读 int 不会是一个严格的别名冲突(可以阅读 int 作为 int )但是读一篇 Bar_Struct 作为 Foo_Struct 会违反规定。

    本文的作者考虑写来设置整个 *酒吧 尽管他们没有给出理由,我也没有看到C标准中支持这一立场的任何文本。

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

        2
  •  2
  •   bolov    6 年前

    你有两个结构 My_Header 是一条红鲱鱼,让你的思维复杂化,而不会给你带来任何新的东西。你的问题可以不用任何结构(除了 MyHead报头 当然)。

    foo->header.bits = 1020;
    

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

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

    这里同样如此:通过非常基本的分析,编译器可以准确地知道 alias->bits = 1020; 修改。

    有趣的是:

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

    在这个函数中,指针 p 可以为任何类型的对象(或子对象)创建别名 My_header . 如果你有n个包含 MyHead报头 或者如果你没有。任何类型的对象 MyHead报头 可能在此函数中被修改。

    例如。

    // 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_Type Bar_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.
    }
    
        3
  •  1
  •   supercat    6 年前

    N1570 P5.6P7的编写方式,只有使用字符类型的lvalues或通过调用库函数(如 memcpy . 即使结构或联合具有类型的成员 T ,标准(故意imho)不允许使用看似不相关的类型的lvalue访问集合的存储部分。 T . 目前,gcc和clang似乎使用成员类型的lvalues授予访问结构(而不是联合)的一揽子权限,但n1570 p5.6p7不需要这样做。它对两种聚合及其成员应用相同的规则。由于该标准不授予使用成员类型的不相关lvalue访问结构的一揽子权限,并且授予此类权限会损害有用的优化,因此无法保证gcc和clang将使用成员类型的不相关lvalue继续此行为。

    不幸的是,正如使用union可以证明的那样,gcc和clang在识别不同类型的lvalue之间的关系方面非常差,即使一个lvalue明显地从另一个lvalue派生出来。给出如下信息:

    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;
    

    标准中没有任何内容可以区分以下函数对的“混叠”行为:

    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 s1 , struct s2 , union U union U[100] 即使 int 未被列为访问其中任何内容的允许类型。

    尽管第一个表单调用ub似乎很荒谬,但如果将对标准中明确列出的访问模式以外的访问模式的支持视为一个实现质量问题,那么这就不成问题。根据已发表的基本原理,标准思想编译器的作者将尝试生成高质量的实现,因此没有必要禁止“一致”的实现质量如此之低而无用。一个实现可以是“一致的”,而不能够处理 test1a() test2b() 如果他们要访问成员 v2.x A的 联合U 但是,只有在这样的意义上,一个实现才可能是“一致的”,而不能正确地处理除某些特定的人为的和无用的程序之外的任何东西。

    不幸的是,尽管我认为该标准的作者可能已经期望质量实现能够处理类似 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)相同的存储,即使两者都将访问相同数组的相同元素。