C中的严格别名规则

Som*_*ame 7 c strict-aliasing language-lawyer

我正在尝试理解严格的别名规则,如下所述6.5(p6):

如果通过具有非字符类型的左值的值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该值的后续访问的有效类型储值.

并且6.5(p7):

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

- 兼容对象的有效类型的类型

请考虑以下示例:

struct test_internal_struct_t{
    int a;
    int b;
};

struct test_struct_t{
    struct test_internal_struct_t tis;
};

int main(){
    //alocated object, no declared type
    struct test_struct_t *test_struct_ptr = malloc(sizeof(*test_struct_ptr)); 

    //object designated by the lvalue has type int
    test_struct_ptr->tis.a = 1; 

    //object designated by the lvalue has type int
    test_struct_ptr->tis.b = 2; 

    //VIOLATION OF STRICT ALIASING RULE???
    struct test_internal_struct_t tis = test_struct_ptr->tis; 
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

malloc(sizeof(*test_struct_ptr))没有声明的类型,因为它被分配作为脚注87:

87)分配的对象没有声明的类型

对象通过访问test_struct_ptr->tis.atest_struct_ptr->tis.b具有有效类型int.但是对象test_struct_ptr->tis没有有效的类型,因为它被分配了.

问题:是否struct test_internal_struct_t tis = test_struct_ptr->tis;违反了严格的别名?指定的对象test_struct_ptr->tis没有有效类型,但lvalue有类型struct test_internal_struct_t.

Eri*_*hil 5

C 2018 6.5 6 使用短语"存储...通过左值" 定义有效类型,但从不定义该短语:

如果通过具有非字符类型的左值的值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该值的后续访问的有效类型储值.

因此,读者需要解释.考虑以下代码:

struct a { int x; };
struct b { int x; };

void copy(int N, struct a *A, struct b *B)
{
    for (int n = 0; n < N; ++n)
        A[n].x = B[n].x;
}
Run Code Online (Sandbox Code Playgroud)

如果编译器知道各种对象A[n]不与各种对象重叠B[n],那么它可以通过B[n]在一条指令中加载多条指令(例如AVX或其他单指令多数据[SIMD]指令)并存储来优化该代码.几A[n]合一指令.(这可能需要额外的代码来处理循环片段和对齐问题.这些与我们无关.)如果某些A[n]->x可能引用同一个对象作为B[n]->x不同的值n,那么编译器可能不会使用这样的多个-element加载和存储,因为它可能会改变程序的可观察行为.例如,如果情况是内存包含10 int,其值为0到9,并B指向0,同时A指向2:

B   A
0 1 2 3 4 5 6 7 8 9

然后写入的循环,给定N= 4,必须一次复制一个元素,产生:

0 1 0 1 0 1 6 7 8 9

如果编译器将此优化为四元素加载和存储,则可以加载0 1 2 3然后存储0 1 2 3,从而产生:

0 1 0 1 2 3 6 7 8 9

但是,C告诉我们struct a并且struct b不相容,即使它们的布局相同.当类型XY不兼容时,它告诉我们Xa不是a Y.类型系统的一个重要目的是区分对象类型.

现在考虑表达式A[n]->x = B[n]->x.在这:

  • A[n]是一个左右struct a.
  • 由于A[n]是a的左操作数.,因此不会将其转换为值.
  • A[n].x指定,并且对于构件的左值xA[n].
  • 右操作数的值替换了中的值A[n].x.

因此,对存储值的对象的直接访问仅限于int成员A[n].x.左值A[n]出现在表达式中,但它不是直接用于存储值的左值.记忆的有效类型是&A[n]什么?

如果我们解释这个记忆仅仅是一个int,然后在对象上唯一的限制访问是所有B[n].xARE int和所有的A[n].x都是int,所以一些或所有A[n].x可以访问相同的存储器中的部分或全部的B[n].x,编译器不允许进行上述优化.

这不能用于类型系统的区分struct astruct b,因此它不能是正确的解释.为了使预期的优化,它必须是由存储内存A[n].x包含struct a对象,并通过访问内存B[n].x中包含struct b的对象.

因此,"通过左值存储"必须包括表达式,其中左值用于派生结构成员,但本身不是用于访问的最终左值.