类型转换为指向可变长度数组类型的指针是否有效?

Is it valid to typecast to a pointer to a variable length array type?

类型转换为指向可变长度数组类型的指针是否有效?

int main() {
    int n = 10;
    int m = 20;
    int (*p)[n][m] = malloc(sizeof(int[n][m]));
    void *q = p;
    int a = n;
    int b = m;
    (*(int (*)[a][b])q)[5][5] = 1;
    printf("%zd %d\n", sizeof(*p), (*p)[5][5]);
    return 0;
}

打印 800 1.

int a = 1;

仍然打印 800 1.

int a = 1;
int b = 1;

现在打印 800 0.

明确定义的行为到什么程度?

这是未定义的行为,因为您越界访问数组。

可能值得指出的是,C 类型系统在这里有点混乱。用 malloc 分配的内存在被访问之前不会被赋予类型。我将引用 C17 6.5/6 并在中间留下评论:

The effective type of an object for an access to its stored value is the declared type of the object, if any.

从 malloc 返回的这个“堆块”没有声明的类型。

If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value.

意味着如果我们在堆块中的给定位置存储某些内容,该地址将获得有效类型并被视为对象(在本例中为单个 int)。编译器并不真正知道整个块是否被视为数组、结构或其他东西。

For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

这意味着如果我们在写入数据之前读取访问该数据,它将被视为用于读取的类型的变量。 (虽然堆块没有被 malloc 初始化,所以先读再写没有多大意义。)

假设 32 位 int 然后 malloc(sizeof(int[n][m])); 分配一块 10*20*4 = 800 字节。 malloc 预留的这个块还没有有效的类型。编译器还没有跟踪存储在该数据中的类型。事实上,您在数据上有一个特定的指针类型并不会改变这一点,只要数据没有取消引用,它就还没有有效的类型。

通常,堆块不会获得类型,直到您执行 [5][5] 取消引用并将 1 写入该块中的某个位置。该位置现在可以说具有有效类型 int.

但是,在你这样做之前,你先做*(int (*)[a][b])qq 的类型在这里是无关紧要的——你投射它并说这是一个指向数组的指针。然后你取消引用它并得到一个数组 - 这是一个 lvalue (它将衰减为指向数组第一个元素的指针)。从那时起,至少在这个表达式中,编译器可以假定它正在处理一个数组——不管该数组是否恰好存储在没有有效类型的内存中。如果这个数组是一个 VLA 并且没有足够大的维度来进行 ... [5][5] = 1; 访问,你最终会得到普通的旧数组越界访问,这是未定义的行为。

具体来说,根据 6.5.6/8 加法运算符的 UB:

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

arr[i] 100% 等同于 *(arr+i)+ 运算符设置了上面引用的指针算法的规则 - 不允许使用指针算法访问数组或对象越界 - 这是未定义的行为。这意味着编译器可能会生成行为不正确的代码,而不管假定地址处是否有可用内存。