【发布时间】:2022-01-08 02:15:02
【问题描述】:
这个问题可能有点争议。 我在块范围内有以下代码:
int *a = malloc(3 * sizeof(int));
if (!a) { ... error handling ... }
a[0] = 0;
a[1] = 1;
a[2] = 2;
我认为这段代码调用 UB 是因为指针算术超出了界限。
原因是a的对象指针的有效类型永远不会
设置为int[3],而仅设置为int。因此,对索引处对象的任何访问
C 标准没有定义除 0 以外的值。
原因如下:
线a = malloc(...)。
如果分配成功,则a 指向一个足以存储 3 个ints 的区域。
a[0] = ... 等价于*a = ...,即int 的左值。它将第一个sizeof(int) 字节的有效类型设置为int,如规则6.5p6 中所示。
...对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。
现在指针a 指向int 类型的对象,不是 int[3]。
a[1] = ... 等价于*(a + 1) =。表达式a + 1 指向可通过*a 访问的int 对象末尾之后的一个元素。
该指针本身对比较有效,但访问未定义,原因是:
规则6.5.6p7:
...指向不是数组元素的对象的指针与指向长度为 1 的数组的第一个元素的指针的行为相同,该数组的元素类型为对象的类型。
和规则6.5.6p8:
...如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元 * 运算符的操作数。
类似的问题与a[2] = ... 相关,但这里甚至隐藏在a[2] 中的a + 2 也会调用UB。
只要满足对齐要求和严格的别名规则,如果标准允许对内存有效区域进行任意指针运算,问题就可以解决。或者任何相同类型的连续对象的集合都可以被视为一个数组。但是,我找不到这样的东西。
如果我对标准的解释是正确的,那么一些 C 代码(全部?)将是未定义的。 因此,当我希望自己错时,这是极少数情况之一。
我是吗?
【问题讨论】:
-
你说得对,
a没有指向int[3]类型的对象。一个原因是指向int[3]的指针将具有int (*)[3]类型,这与a的类型非常不同。相反,它说a + i(对于任何有效索引i,包括0)指向int。 -
7.22.3 内存管理函数 "....然后用来访问空间中的这样一个对象或这样一个对象的数组分配......”可能是相关的。 malloc 的用法在 C 中无处不在,你想多了。
-
有效类型和严格的别名规则被完全破坏了,这就是一个例子。然而,关于指针运算只允许在数组中的规则同样被打破,无论何时应用于未知(有效)类型的数据块。每当对例如微控制器中的硬件寄存器映射进行指针运算时,都会遇到相同的问题。 C 标准通常不承认可以在地址空间中放置 C 编译器未放置的东西。
-
@Mat,是的,我想太多了,但是 language-lawyer 标签正是为了想太多。
7.22.3的措辞看似相关,但与其他更明确的规则相矛盾。 -
@Mat 相反,提出有效类型规则的人都“没有考虑”这一点。它们不涉及数组/聚合类型,也不涉及类型限定符。整个 6.5 §6-§7 可以替换为“这里的实现可以随意在两行之间拼凑事物,以一种未记录的方式”。所有这些最终归结为实施质量。
标签: arrays c language-lawyer dynamic-memory-allocation undefined-behavior