24

在 C 中,如果我们只访问位于分配内存中的元素,那么将内存分配给指向数组的指针是否“合法”?还是这会调用未定义的行为?

int (*foo)[ 10 ];                  //Pointer to array of 10 ints
foo = malloc( sizeof( int ) * 5 ); //Under-allocation! 
                                   //Only enough memory for 5 ints
//Now we only ever access (*foo)[ 0 - 4 ]

如果这本身不是未定义的行为,那么访问另一个不相关的对象,其内存地址恰好落在数组未分配部分的地址空间内,是否会导致严格混叠违规?

4

2 回答 2

11

这是未定义的行为

foo应该指向类型的对象(或对象数组中的第一个)int[10]。这被认为是数组类型的对象,在C 标准的第 6.2.5p20 节中定义

数组类型描述了一 组连续分配的 具有特定成员对象类型的非空对象,称为元素类型。只要指定了数组类型,元素类型就应该是完整的。数组类型的特征在于它们的元素类型和数组中的元素数量。数组类型据说是从它的元素类型派生的,如果它的元素类型是 T ,那么这个数组类型有时被称为“T 的数组”。从元素类型构造数组类型称为''数组类型推导''</p>

我用粗体突出显示的部分是重要的部分。因此Anint[10]是一组连续分配的 10 个类型的对象int

您没有分配足够的空间,因此*foo具有类型的表达式会int[10]访问该类型的对象,但这样做会读取已分配内存段的末尾。

于 2021-10-31T15:36:08.410 回答
7

正如@dbush 在他的回答中描述的那样,数组被定义为元素类型(C17 6.2.5/20)的连续分配的非空对象集。那么,显然malloc( sizeof( int ) * 5 )没有为int[10].

但是我发现很难正式支持该答案的最后一部分,声称尺寸差异(例如)(*foo)[4]具有未定义的行为。这个结论似乎是合理的,但标准实际上是在哪里说的呢?

这里的主要问题之一是(动态)分配的对象没有声明的类型,只有在某些情况下,有效类型取决于它们的访问方式和访问方式。(C17 6.5/6 和脚注 88)。我们确实知道,成功时会malloc(n)返回一个指向大小对象的指针n(C17 7.22.3.4/2),但是我们如何将未定义的行为专门归因于与描述大小大于的对象的有效类型的对象的关联n

我最终决定将这些点连接起来的最佳方法如下。假设它o是一个大小为 的已分配对象nT是一个具有 的完整类型sizeof(T) > n,并且o是通过类型为 的左值读取或写入的T。然后第 6.5/6 段将有效类型归因T于 object o,但由于o的大小不足,我们必须得出结论,它的表示构成了类型的陷阱表示T(C17 3.19.4)。然后第 6.2.6.1/5 段重申了“陷阱表示”的定义,并将我们带到了我们想要去的地方:

某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式并且由不具有字符类型的左值表达式读取,则行为未定义。如果这种表示是由通过不具有字符类型的左值表达式修改对象的全部或任何部分的副作用产生的,则行为未定义。这种表示称为陷阱表示。

(强调补充。)

于 2021-10-31T18:49:08.340 回答