C 如果分配了某个区域,则访问超出其末端的阵列是否属于未定义行为?

C 如果分配了某个区域,则访问超出其末端的阵列是否属于未定义行为?,c,undefined-behavior,C,Undefined Behavior,可能重复: 在C语言中,通常在数组结束后访问数组是未定义的行为。例如: int foo[1]; foo[5] = 1; //Undefined behavior 如果我知道数组结束后的内存区域已分配(使用malloc或在堆栈上),这仍然是未定义的行为吗?以下是一个例子: #include <stdio.h> #include <stdlib.h> typedef struct { int len; int data[1]; } MyStruct; int

可能重复:

在C语言中,通常在数组结束后访问数组是未定义的行为。例如:

int foo[1];
foo[5] = 1; //Undefined behavior
如果我知道数组结束后的内存区域已分配(使用malloc或在堆栈上),这仍然是未定义的行为吗?以下是一个例子:

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
  int len;
  int data[1];
} MyStruct;

int main(void)
{
  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  foo->data[5] = 1;
}
#包括
#包括
类型定义结构
{
内伦;
int数据[1];
}我的结构;
内部主(空)
{
MyStruct*foo=malloc(sizeof(MyStruct)+sizeof(int)*10);
foo->data[5]=1;
}

我在很多地方看到过这种模式被用来制作一个可变长度的结构,它在实践中似乎很有效。这是技术上未定义的行为吗?

你所描述的被亲切地称为。目前还不清楚它是否完全正常,但它曾经被广泛使用


最近(C99),它已经开始被“灵活数组成员”所取代,在这里你可以放置
int data[]字段(如果它是结构中的最后一个字段)。6.5.6加法运算符下的:

语义学 8-[…]如果指针操作数指向数组对象的某个元素,且数组足够大,则结果指向与原始元素的元素偏移量,从而结果数组元素和原始数组元素的下标之差等于整数表达式。[…]如果结果指向数组对象的最后一个元素后一个元素,则不应将其用作计算的一元
*
运算符的操作数

如果内存由
malloc
分配,则:

7.22.3内存管理功能 1-[…]如果分配成功,则返回的指针经过适当对齐,以便可以将其分配给指向具有基本对齐要求的任何类型对象的指针,然后用于访问所分配空间中的此类对象或此类对象数组(直到显式释放空间)。已分配对象的生存期从分配到解除分配

然而,这并不支持在没有适当强制转换的情况下使用这种内存,因此对于上面定义的
MyStruct
,只能使用对象的声明成员。这就是添加灵活数组成员(6.7.2.1:18)的原因

还请注意,附录J.2未定义行为调用了数组访问:

1-在以下情况下,行为未定义:[……]
-将指针加减到数组对象和 integer类型生成的结果不指向或刚好超出同一数组 对象。
-将指针加减到数组对象和 整数类型生成一个指向数组对象之外的结果,并用作 计算的一元
*
运算符的操作数。
-数组下标超出范围,即使对象显然可以通过 给定下标(如左值表达式
a[1][7]
给定声明
int
[4][5])

因此,正如您所注意到的,这将是未定义的行为:

  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  foo->data[5] = 1;
但是,您可以执行以下操作:

  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  ((int *) foo)[(offsetof(MyStruct, data) / sizeof(int)) + 5] = 1;
C++在这方面比较宽松3.9.2化合物类型[基本化合物]具有:

3-[…]如果类型为
T
的对象位于地址
A
,则称值为地址
A
的类型为
cv T*
的指针指向该对象,而不管该值是如何获得的


考虑到C对指针的更积极的优化机会,例如使用
restrict
限定符,这是有意义的。

C99基本原理文档在第6.7.2.1节中讨论了这一点

C99的一个新特性:有一种常见的习惯用法称为“struct hack”,用于创建包含可变大小数组的结构:

这一构想的有效性一直受到质疑。在对一个缺陷报告的响应中,委员会决定这是未定义的行为,因为数组
p->items
只包含一个项目,而不管空间是否存在。另一种结构是 建议:使数组大小大于最大可能的大小(例如,使用
int items[int_MAX];
),但由于其他原因,此方法也未定义

委员会认为,尽管没有办法在C89中实现“结构黑客”,但它仍然是一个有用的工具。因此,引入了“柔性阵列成员”的新特性。除了空括号和删除
malloc
调用中的“-1”之外, 这与struct hack的使用方式相同,但现在是显式有效的代码

struct hack是一种未定义的行为,不仅是C规范本身(我相信其他答案中也有引用)提供了支持,而且委员会甚至记录了它的意见

因此答案是是,根据标准文档,它是未定义的行为,但根据事实上的C标准,它定义良好。我想大多数编译器作者都非常熟悉这种黑客。从GCC的
树vrp.c

   /* Accesses after the end of arrays of size 0 (gcc
      extension) and 1 are likely intentional ("struct
      hack").  */

我认为你很有可能在编译器测试套件中找到结构黑客。

我可以看出,如果你有一个像
{double;char[1];}
,non这样的结构,这将是一个问题。你知道有没有任何环境(编译器、平台、运行库)不明确支持这一点,甚至破坏了它?@KerrekSB我不确定我是否理解为什么会有问题?@KerrekSB,C99或C11中不允许使用零大小的阵列。允许的是cnicutar描述的:灵活的数组成员。它们不是0号大小,而是非特定大小。@delnan:concer