动态数组的惯用初始化是否会调用未定义的行为?

人气:940 发布:2022-10-16 标签: arrays c undefined-behavior language-lawyer dynamic-memory-allocation

问题描述

这个问题可能有点争议。 我在块作用域中有以下代码:

int *a = malloc(3 * sizeof(int));
if (!a) { ... error handling ... }
a[0] = 0;
a[1] = 1;
a[2] = 2;

我认为这段代码调用ub是因为指针算法超出界限。 原因是a对象指针的有效类型永远不是 设置为int[3],但仅设置为int。因此,对索引处的对象的任何访问 非0不是由C标准定义的。

原因如下:

a = malloc(...)。 如果分配成功,则a将指向足够存储3ints的区域。

a[0] = ...等价于*a = ...,即int的l-值。它将第一个sizeof(int)字节的有效类型设置为int,如6.5p6规则所示。

.对于对没有声明类型的对象的所有其他访问,该对象的有效类型只是用于该访问的左值的类型。

现在指针a指向int类型的对象,Notint[3]

a[1] = ...相当于*(a + 1) =。表达式a + 1指向int对象结束后的一个元素,可通过*a访问。 此指针本身对比较有效,但由于以下原因未定义访问:

规则6.5.6p7:

.指向不是数组元素的对象的指针的行为与指向长度为1的数组的第一个元素的指针相同,该数组的元素类型为对象的类型。

与规则6.5.6p8:

.如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元*运算符的操作数。

类似问题与a[2] = ...相关,但此处即使隐藏在a[2]中也会调用ub。

如果标准允许使用有效内存区的任意指针算法,只要满足对齐要求和严格的别名规则,问题就可以解决。或者可以将相同类型的连续对象的任何集合视为数组。但是,我找不到这样的东西。

如果我对标准的解释是正确的,那么一些C代码(全部?)是不确定的。 因此,这是我希望自己错的罕见情况之一。

我是吗?

推荐答案

仅标准中途定义了术语对象:它说每个对象都是存储区域,但它没有指定存储区域何时是对象。对于标准的大部分内容,可以说每个存储区域同时包含适合其中的所有类型的所有对象;任何修改对象的操作都会修改底层存储,而任何修改底层存储的操作都会修改其中所有对象的存储值。

我认为很明显,标准的作者预计,如果标准规定某个操作调用未定义的行为,但该行为将在没有该声明的情况下定义,则质量实现应该以定义的方式在其客户认为有用的情况下。然而,这些是哪些案件的问题是执行质量问题,超出了标准的管辖范围。因此,如果标准将到目前为止所有实现都以相同的明显有用的方式处理的某些操作描述为未定义的行为,这并不重要,因为任何寻求销售编译器的人都不会将标准未能强制执行此类行为解释为邀请以对其客户不利的方式偏离标准。

因为不同的编译器用于不同的目的,所以标准能够实际定义许多低级编程任务所需的所有行为,同时还允许对高端数字运算有用的所有优化的唯一方法是要么识别进行不同优化的实现类别,要么添加更好的方法来邀请或阻止优化,从而有效地提高性能和/或导致不正确的程序行为。因为每个曾经存在或将会存在的编译器都不会进行一些原本有用的优化,和/或执行不正确地处理某些严格符合C11程序的优化,所以标准是否允许愚蠢的优化的问题应该只与那些想要编写质量较差的编译器或想要竭尽全力与之兼容的人有关。

211