C中的严格别名规则

问题描述 投票:7回答:1

我正在尝试理解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类型。

c language-lawyer strict-aliasing
1个回答
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中,即A[n].x成员。左值A[n]出现在表达式中,但它不是直接用于存储值的左值。 &A[n]记忆的有效类型是什么?

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

这不符合类型系统的目的,以区分struct bA[n].x,因此它不能是正确的解释。要启用预期的优化,必须是struct a存储的内存包含B[n].x对象,struct b访问的内存包含qazxswpoi对象。

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

© www.soinside.com 2019 - 2024. All rights reserved.