保证多维数组 T arr[M][N]
与具有相同元素总数的单维数组 T arr[M * N]
具有相同的内存布局。这是因为数组是连续的(6.2.5p20),并且因为 sizeof array / sizeof array[0]
保证返回数组中元素的数量(6.5.3.4p7)。
然而,将指向类型的指针强制转换为指向类型数组的指针,或者反过来,并不意味着这样做是安全的。首先,
对齐是一个问题;虽然具有基本对齐方式的类型的数组也必须具有基本对齐方式(根据6.2.8p2),但不能保证对齐方式相同。因为数组包含基本类型的对象,所以数组类型的对齐方式必须至少与基本对象类型的对齐方式一样严格,但它可以更严格(我从未见过这种情况)。然而,对于
分配的内存来说,这与相关性不大,因为
malloc
保证返回适当分配给任何基本对齐方式的指针(7.22.3p1)。这意味着您不能将指向自动或静态内存的指针安全地强制转换为数组指针,尽管反过来是允许的:
int a[100];
void f() {
int b[100];
static int c[100];
int *d = malloc(sizeof int[100]);
int (*p)[10] = (int (*)[10]) a;
int (*q)[10] = (int (*)[10]) b;
int (*r)[10] = (int (*)[10]) c;
int (*s)[10] = (int (*)[10]) d;
}
int A[10][10];
void g() {
int B[10][10];
static int C[10][10];
int (*D)[10] = (int (*)[10]) malloc(sizeof int[10][10]);
int *p = (int *) A;
int *q = (int *) B;
int *r = (int *) C;
int *s = (int *) D;
}
接下来,不能保证在数组类型和非数组类型之间进行强制转换实际上会导致指针指向
正确的位置,因为强制转换规则(6.3.2.3p7)不包括此用法。尽管这种情况发生的可能性非常小,但通过
char *
进行的转换确实具有保证的语义。当从指向数组类型的指针到指向基本类型的指针时,最好只是间接引用该指针:
void f(int (*p)[10]) {
int *q = *p;
assert((int (*)[10]) q == p);
assert((int (*)[10]) (char *) q == p);
}
数组下标的语义是什么?众所周知,[]
操作只是加法和间接寻址的语法糖,因此其语义与+
运算符相同;正如6.5.6p8所描述的那样,指针操作数必须指向足够大的数组成员,以使结果落在数组内或刚好超出末尾。这对于两个方向的强制转换都是一个问题;当转换为数组类型的指针时,加法是无效的,因为该位置不存在多维数组;而当转换为基础类型的指针时,该位置上的数组仅具有内部数组绑定的大小:
int a[100];
((int (*)[10]) a) + 3;
int b[10][10];
(*b) + 3;
(*b) + 23;
这是我们开始看到实际的常见实现问题,而不仅仅是理论。因为优化器有权假定未定义的行为不会发生,所以通过基对象指针访问多维数组可以假定不会别名任何在第一个内部数组之外的元素:
int a[10][10];
void f(int n) {
for (int i = 0; i < n; ++i)
(*a)[i] = 2 * a[2][3];
}
优化器可以假设对于
a[2][3]
的访问不会与
(*a)[i]
产生别名,并将其提升到循环外部:
int a[10][10];
void f_optimised(int n) {
int intermediate_result = 2 * a[2][3];
for (int i = 0; i < n; ++i)
(*a)[i] = intermediate_result;
}
如果使用n = 50
调用f
,这当然会产生意想不到的结果。
最后值得问一下,这是否适用于分配的内存。7.22.3p1指定malloc
返回的指针“可以分配给具有基本对齐要求的任何类型对象的指针,然后用于访问在分配的空间中分配的此类对象或此类对象的数组”;没有关于将返回的指针进一步转换为另一个对象类型的内容,因此结论是分配的内存类型由返回的void
指针转换为的第一个指针类型固定;如果你将其转换为double *
,那么就不能进一步转换为double (*)[n]
,如果你将其转换为double (*)[n]
,则只能使用double *
来访问前n
个元素。
因此,我认为如果您想要绝对安全,即使基础类型相同,也不应该在指针和数组类型之间进行转换。布局相同这一事实除了通过char
指针进行的memcpy
和其他访问之外是无关紧要的。
-Werror
编译。 - Fred Foo