是否可以通过成员地址访问结构的大小,并分配足够的空间?

具体来说,是下面的代码,标记下方的行,好吗?

struct S{ int a; }; #include  int main(){ struct S *p; p = malloc(sizeof(struct S) + 1000); // This line: *(&(p->a) + 1) = 0; } 

人们在这里争论,但没有人给出令人信服的解释或参考。

他们的论点略有不同,但基本相同

 typedef struct _pack{ int64_t c; } pack; int main(){ pack *p; char str[9] = "aaaaaaaa"; // Input size_t len = offsetof(pack, c) + (strlen(str) + 1); p = malloc(len); // This line, with similar intention: strcpy((char*)&(p->c), str); // ^^^^^^^ 

至少从1989年C标准化以来的意图是允许实现检查数组访问的数组边界

成员p->aint类型的对象。 C11 6.5.6p7说

7出于[附加运算符]的目的,指向不是数组元素的对象的指针与指向长度为1的数组的第一个元素的指针的行为相同, 其中对象的类型为其元素类型

从而

 &(p->a) 

是指向int的指针; 但它也好像是一个指向长度为1的数组的第一个元素的指针,其中int作为对象类型。

现在6.5.6p8允许计算&(p->a) + 1 ,这是一个指向刚好超过数组末尾的指针,因此没有未定义的行为。 但是,这种指针的取消引用是无效的。 从附录J.2中拼写出来,在以下情况下行为未定义:

将指针加到或减去数组对象和整数类型会产生一个指向数组对象之外的结果,并用作被计算的一元*运算符的操作数(6.5.6)。

在上面的表达式中,只有一个数组,一个(好像)只有一个元素。 如果取消引用&(p->a) + 1 ,则访问超出边界的数组,并且发生未定义的行为 ,即

行为[…],[C11]标准没有要求

随着说明说 :

可能的未定义行为包括完全忽略不完整结果的情况 ,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的特定文档执行,终止翻译或执行(发布时)一条诊断信息)。

最常见的行为是完全忽略这种情况 ,即表现就好像指针刚好引用了内存位置,并不意味着从标准的角度来看其他类型的行为是不可接受的 – 标准允许每一个可以想象的并且难以想象的结果。


有人声称C11标准文本含糊不清,委员会的意图应该是确实允许这样做,以前它本来就没问题。 这不是真的。 阅读委员会对[1992年12月10日至C89的缺陷报告#017]的答复。

问题16

[…]

响应

对于数组数组,通过将单词object的使用解释为表示由指针的类型和值直接确定特定对象 ,可以理解子条款6.3.6,第47页,第12-40行中的允许指针算法,而不是通过邻接与其相关的其他对象 。 因此,如果表达式超出这些权限,则行为未定义。 例如,以下代码具有未定义的行为:

  int a[4][5]; a[1][7] = 0; /* undefined */ 

一些符合要求的实现可以选择诊断数组边界违规 ,而其他实现可以选择使用明显的扩展语义成功地解释这样的尝试访问。

(加粗强调我的)

没有理由将同一个转移到结构的标量成员,特别是当6.5.6p7表示指向它们的指针应该被认为与指向长度为1的数组的第一个元素的指针相同时对象的类型作为其元素类型

如果要处理连续的struct ,可以始终将指针指向第一个成员并将其转换为指向struct的指针并改为:

 *(int *)((S *)&(p->a) + 1) = 0; 

这是未定义的行为,因为您正在访问不是数组(在struct S int a )作为数组的东西,并且在那里超出界限。

实现您想要的正确方法是使用没有大小的数组作为最后一个struct成员:

 #include  typedef struct S { int foo; //avoid flexible array being the only member int a[]; } S; int main(){ S *p = malloc(sizeof(*p) + 2*sizeof(int)); p->a[0] = 0; p->a[1] = 42; //Perfectly legal. } 

C标准保证
§6.7.2.1/ 15:

[…]指向结构对象的指针(适当转换)指向其初始成员(或者如果该成员是位字段,则指向它所在的单位),反之亦然。 结构对象中可能存在未命名的填充,但不是在其开头。

&(p->a)相当于(int *)p&(p->a) + 1将是第二个结构元素的地址。 在这种情况下,只有一个元素存在,结构中将不会有任何填充,因此这将起作用,但是在有填充的情况下,此代码将中断并导致未定义的行为。