当前位置: 首页 > 知识库问答 >
问题:

通过其他结构成员的偏移指针访问结构成员是否合法?

闾丘高峰
2023-03-14

在这两个示例中,通过偏移其他成员的指针来访问结构成员是否会导致未定义/未指定/实现定义的行为?

struct {
  int a;
  int b;
} foo1 = {0, 0};

(&foo1.a)[1] = 1;
printf("%d", foo1.b);


struct {
  int arr[1];
  int b;
} foo2 = {{0}, 0};

foo2.arr[1] = 1;
printf("%d", foo2.b);

C11§6.7.2.1第14段似乎表明,这应该是实施定义:

结构或联合对象的每个非位字段成员都以适合其类型的实现定义方式对齐。

后来又说:

结构对象中可能有未命名的填充,但在其开头没有。

但是,如下所示的代码似乎相当常见:

union {
  int arr[2];
  struct {
    int a;
    int b;
  };
} foo3 = {{0, 0}};

foo3.arr[1] = 1;
printf("%d", foo3.b);

(&foo3.a)[1] = 2; // appears to be illegal despite foo3.arr == &foo3.a
printf("%d", foo3.b);

该标准似乎保证 foo3.arr

因此,我思考第一个例子的理由也必须是合法的:

    < li>foo3.arr保证与< code >相同

我遇到了一些外部资源,表明< code>foo3.arr[1]和< code >(

union {
  struct {
    int x;
    int arr[];
  };
  struct {
    int y;
    int a;
    int b;
  };
} foo4;

原始应用程序正在考虑从一个结构字段到另一个结构字段的缓冲区溢出是否严格来说是由标准定义的:

struct {
  char buffer[8];
  char overflow[8];
} buf;
strcpy(buf.buffer, "Hello world!");
println(buf.overflow);

我希望它能在几乎任何现实世界的编译器上输出"rld!",但这种行为是由标准保证的,还是未定义或实现定义的行为?

共有2个答案

鞠修雅
2023-03-14

根据C11草案N1570 6.5p7,尝试使用字符类型、结构或联合类型或包含结构或联合类型的左值以外的任何东西访问结构或联合对象的存储值,即使行为将由标准的其他部分完全描述,也会调用UB。本节不包含允许使用非字符成员类型(或任何非字符数字类型)的左值来访问结构或联合的存储值的规定。

然而,根据已发布的Salit文件,该标准的作者认识到,在标准没有施加要求的情况下,不同的实现提供了不同的行为保证,并认为这种“流行的扩展”是一件好事和有用的东西。他们判断,市场比委员会更能回答何时以及如何支持这种延期的问题。虽然标准允许一个迟钝的编译器忽略一些结构.array[i]可能影响某些结构的存储值的可能性似乎很奇怪,但该标准的作者认识到,任何作者不是故意迟钝的编译器都会支持这样的结构,无论标准是否要求,任何试图从迟钝设计的编译器那里强制任何有用的行为都是徒劳的。

因此,编译器对与结构或联合有关的任何内容的支持程度是实现质量问题。专注于与多种程序兼容的编译器编写者将支持多种构造。那些专注于最大限度地提高代码性能的代码只需要那些结构,没有这些结构,语言将完全无用,它们将支持更窄的集合。然而,该标准缺乏关于此类问题的指导。

PS——配置为与MSVC风格的volatile语义兼容的编译器会将该限定符解释为,指示对指针的访问可能会产生副作用,这些副作用会与地址已被占用且不受限制保护的对象交互,无论是否有任何其他理由预期这种可能性。当以“不寻常”的方式访问存储时,使用这样的限定符可能会使人类读者更清楚地看到代码在做一些“奇怪”的事情,因为它将确保与使用这种语义的任何编译器兼容,即使这样的编译器不会以其他方式识别这种访问模式。不幸的是,一些编译器编写者拒绝在优化级别0之外的任何地方支持这种语义,除非程序要求使用非标准语法。

万俟炯
2023-03-14

简介:该标准在这方面是不够的,并且有几十年的关于这个主题的争论历史和严格的别名,没有令人信服的解决方案或建议来修复。

这个答案反映了我的观点,而不是对标准的任何强加。

首先:人们普遍认为第一个代码示例中的代码是未定义的行为,因为通过直接指针算法访问数组边界之外。

规则是C11 6.5.6/8。它说从一个指针开始的索引必须保持在“数组对象”内(或者一个超过末尾的对象)。它没有说明是哪个数组对象,但是一般认为在< code>int *p =的情况下

相关链接:一、二。

第二:大家普遍认为你的Union两个例子都是正确的。标准明确规定可以读取联合的任何成员;无论相关内存位置的内容是什么,都被解释为正在读取的联合成员的类型。

您认为< code>union正确意味着第一个代码也应该正确,但事实并非如此。问题不在于指定读取的内存位置;问题是我们如何得到指定内存位置的表达式。

即使我们知道

人们普遍认为,您可以通过以不违反6.5.6/8规则的其他方式计算其地址来访问int,例如:

((int *)((char *)&foo + offsetof(foo, b))[0]

或者

((int *)((uintptr_t)&foo.a + sizeof(int)))[0]

相关链接:一、二

对于< code>((int *)是否

也许相关的是N2090 - 指针出处提案。这并不能直接解决这个问题,也没有建议废除6.5.6/8。

 类似资料:
  • 我试图理解在将值存储到结构或联合的成员中时,类型双关是如何工作的。 标准N1570指定 当值存储在结构或联合类型的对象(包括成员对象)中时,与任何填充字节相对应的对象表示的字节采用未指定的值。 所以我把它解释为如果我们有一个对象要存储到一个成员中,这样对象的大小等于,与填充相关的字节将具有未指定的值(即使我们定义了原始对象中的字节)。这是一个例子: 在我的机器。 打印的行为是否为这样的程序定义得很

  • 访问结构成员或类成员时,使用成员访问运算符(member access operator),包括圆点运算符(.)和箭头运算符(—>)。圆点运算符通过对象的变量名或对象的引用访问结构和类成员。例如,要打印 timeObject 结构的 hour 成员,用下列语句: cout << timeobject.hour; 要打印timeRef引用的结构的hour成员,用下列语句: cout << timeR

  • 反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。 反射值对象的成员访问方法 方  法 备  注 Field(i int) Value 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机 NumField() int 返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机 FieldByNa

  • 我正在学习如何在C中使用并写了以下示例: 问:是否保证在所有情况下指向一个结构的指针都是指向它的第一个元素的完全相同的指针? 在这种特殊的情况下,它能像我预期的那样工作,但我不确定它是否能得到保证。编译器可以在开始时插入一些填充吗? 我唯一能找到的关于结构类型布局的是N1570的类型: 结构类型描述了一组按顺序分配的非空成员对象(在某些情况下,还包括一个不完整的数组),每个对象都有一个可选的指定名

  • 我有以下代码: 最后一行执行对< code>x.b的间接访问。 这段代码是根据任何C标准定义的吗? 我知道: 已定义,但仅定义了实现。

  • 问题内容: 我已经开始使用dirent.h库,并且遇到了“ struct dirent”构造函数的一个非常有用的成员,该结构在我的书中构造了dirent * p-> d_name。但是不幸的是,它没有声明该结构的任何其他成员; 我想知道这个结构的成员还有什么用? 问候 问题答案: 该结构是指目录条目。 http://www.gnu.org/software/libc/manual/html_nod