c - 类型转换为指向可变长度数组类型的指针是否有效?
问题描述
类型转换为指向可变长度数组类型的指针是否有效?
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 并在两者之间发表评论:
访问其存储值的对象的有效类型是对象的声明类型(如果有)。
从 malloc 返回的这个“堆块”没有声明类型。
如果通过具有非字符类型类型的左值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该类型的后续访问储值。
这意味着如果我们在堆块中的给定位置存储某些东西,该地址将获得有效类型并被视为对象(int
在这种情况下为单个)。编译器并不真正知道整个块是否应该被视为数组、结构或其他东西。
对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。
这意味着如果我们在写入之前读取访问此数据,它将被视为用于读取的类型的变量。(虽然堆块没有被初始化malloc
,所以在写之前读没有多大意义。)
假设 32 位int
然后 malloc(sizeof(int[n][m]));
分配10*20*4
= 800 字节的块。malloc 预留的这个块还没有有效的类型。编译器尚未跟踪存储在该数据中的类型。您在数据上有一个特定的指针类型点这一事实不会改变这一点,只要数据没有被取消引用,它还没有有效的类型。
[5][5]
通常,除非您执行取消引用并写入1
该块中的位置,否则堆块不会获得类型。那个位置现在可以说是有实效了int
。
但是,在你这样做之前,你会这样做*(int (*)[a][b])q
。的类型在q
这里无关紧要 - 你将它转换并说这是一个指向数组的指针。然后你取消引用它并得到一个数组 - 这是一个左值(它将衰减为指向数组第一个元素的指针)。从那时起,至少在这个表达式中,编译器可以假设它正在处理一个数组——不管该数组是否恰好存储在没有有效类型的内存中。如果这个数组是一个 VLA 并且没有足够大的维度来... [5][5] = 1;
访问,你最终会得到普通的旧数组越界访问,这是未定义的行为。
具体来说,UB 根据 6.5.6/8 加法运算符:
如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。
arr[i]
100% 等同于*(arr+i)
,该+
运算符设置上面引用的指针算术规则 - 您不允许使用指针算术访问数组或对象越界 - 这是未定义的行为。这意味着编译器可以生成行为不正确的代码,无论假定地址是否有可用内存。
推荐阅读
- spring - 为什么在使用带有redis的spring cloud api gateway ratelimit时响应标头中的X-RateLimit-Remaining -1?
- blockchain - 就数据互操作性和来源而言,托管私有区块链服务(如 Azure 区块链服务)的真正目的是什么
- sql - 在 Laravel Eloquent 中获取最新的重复数据
- apache-zookeeper - 是否可以在单台机器上运行多个具有不同端口的 Zookeeper 实例?
- flutter - 颤振:堆栈内的警报对话框
- json - 如何根据以数组形式给出的键使用python过滤json字符串?
- java - 在spring boot中显示jsp页面时,它与jsp头文件一起显示
- python - 如何在 Windows 上使用 python 与 AMD GPU 交互
- c++ - C++ - 返回调用对象的引用
- python - 加载自定义指标失败