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

iBu*_*Bug 21 c struct access-violation language-lawyer

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

struct S{
    int a;
};

#include <stdlib.h>

int main(){
    struct S *p;
    p = malloc(sizeof(struct S) + 1000);
    // This line:
    *(&(p->a) + 1) = 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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);
//                ^^^^^^^
Run Code Online (Sandbox Code Playgroud)

Ant*_*ala 24

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

该成员p->a是类型的对象int.C11 6.5.6p7

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

从而

&(p->a)
Run Code Online (Sandbox Code Playgroud)

是一个指向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 */ 
Run Code Online (Sandbox Code Playgroud)

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

(加粗强调我的)

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

如果你想寻址连续的structs,你总是可以把指针指向第一个成员并将其作为指针转换为struct并向前推进:

*(int *)((S *)&(p->a) + 1) = 0;
Run Code Online (Sandbox Code Playgroud)

  • @PSkocik UB不是特定于平台的;-)它可能在某些平台上工作并不会改变它是UB; 它超出了标准可以保证的范围. (8认同)
  • @PSkocik UB不依赖.行为的某些实现按预期工作并不意味着它由标准很好地定义.编译器可以自由决定是否按照UB的预期进行操作. (3认同)
  • @SimonRichter是的,它不符合**任何**C标准,大小为0的数组是一个**约束错误**,所以它肯定是未定义的行为和兼容的编译器**一定不能**编译它没有抱怨. (2认同)
  • ...只有当`n`为零时,优化器才会看到这个表达式是有效的(给定`struct`的定义),因此它可以优化代码以仅服务于该情况.结果是当调用例程并将`n`设置为1时,生成的代码就像`n`为零一样. (2认同)

cma*_*ter 8

这是未定义的行为,因为您正在访问不是数组(int a内部struct S)作为数组的内容,并且超出范围.

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

#include <stdlib.h>

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.
}
Run Code Online (Sandbox Code Playgroud)

  • @iBug某事物在分配的内存区域内并不意味着,访问它是定义的行为.有大量未定义的行为只能访问分配的内存(严格的别名规则等). (5认同)