C中严格的别名规则

时间:2019-02-17 09:09:01

标签: c language-lawyer strict-aliasing

我正在尝试理解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;
}

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

1 个答案:

答案 0 :(得分: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;
}

如果编译器知道各种对象A[n]不与各种对象B[n]重叠,则它可以通过在一条指令中执行多个B[n]的加载来优化此代码(例如作为AVX或其他单指令多数据[SIMD]指令),并在一条指令中存储多个A[n]。 (这可能需要其他代码来处理循环片段和对齐问题。此处不涉及我们。)如果某些A[n]->x可能以与B[n]->x相同的对象引用不同的值的n的值,则编译器可能不使用此类多元素加载和存储,因为它可能会更改程序的可观察行为。例如,如果情况是内存包含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 astruct b是不兼容的,即使它们的布局相同。当类型XY不兼容时,它告诉我们X不是Y。类型系统的一个重要目的是区分对象类型。

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

  • A[n]struct a的左值。
  • 由于A[n].的左操作数,因此不会转换为值。
  • A[n].x指定x的成员A[n]的左值。
  • 右操作数的值替换A[n].x中的值。

因此,直接访问存储值的对象仅在成员int的{​​{1}}中。左值A[n].x出现在表达式中,但不是直接用于存储值的左值。 A[n]处的有效内存类型是什么?

如果我们仅将此存储器解释为&A[n],那么对对象访问的唯一限制是所有int都是B[n].x,所有int都是{ {1}},因此A[n].x的某些或全部可以访问与int的某些或全部相同的内存,并且不允许编译器进行上述优化。

这不能满足类型系统区分A[n].xB[n].x的目的,因此不能正确解释。为了实现预期的优化,必须使struct a存储的内存包含struct b对象,并且A[n].x访问的内存包含struct a对象。

因此,“…通过左值存储”必须包含这样的表达式,其中左值用于派生结构的成员,但本身并不是用于访问的最终左值。