我正在尝试理解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.a
和test_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 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 a
和struct b
是不相容的,即使它们的布局相同。当类型X
和Y
不相容时,它告诉我们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 b
和A[n].x
,因此它不能是正确的解释。要启用预期的优化,必须是struct a
存储的内存包含B[n].x
对象,struct b
访问的内存包含qazxswpoi对象。
因此,“通过左值存储”必须包括表达式,其中左值用于派生结构成员,但本身不是用于访问的最终左值。