结构和结构的第一个成员之间的指针别名

Pointer aliasing between struct and first member of struct

C 中的指针别名通常是未定义的行为(因为严格的别名),但 C11 标准似乎允许别名指向结构的指针和指向结构的第一个成员的指针

C11 6.7.2.1 (15)...A pointer to a structure object... points to its initial member... and vice versa...

那么下面的代码是否包含未定义的行为?

struct Foo {
    int x;
    int y;
};

// does foe return always 100?
int foe() {
    struct Foo foo = { .x = 10, .y = 20 }, *pfoo = &foo;
    int *px = (int*)pfoo; *px = 100;
    return pfoo->x;
}

此代码正确。标准 C 和 C++ 的所有版本都允许这样做,尽管措辞有所不同。

不存在严格的别名问题,因为您通过类型 int 的左值访问类型 int 的对象。当执行访问的左值与存储在内存位置的对象具有不同类型时,可能会应用严格的别名规则。

您引用的文字涵盖了指针转换实际上指向 int 对象。

标准的编写方式,结构或联合类型的左值可用于访问成员类型的对象,但没有规定允许任意 结构或联合成员类型的左值来访问结构或联合类型的对象。因为说代码不能使用结构或联合成员左值(当然会具有该成员的类型)来访问结构或联合当然是荒谬的,所有编译器都支持一些常见的访问模式。但是,由于编译器在不同情况下允许此类访问,因此该标准将对此类访问的所有支持都视为实现质量问题,而不是试图准确指定何时需要此类支持。

与标准的措辞最一致的方法,允许最有用的优化,同时还支持大多数需要执行类型双关或其他技术的代码,就是说,为了 N1570 6.5p7 ,从给定类型的指针或左值明显派生的指针可以在这种派生的上下文中使用,以访问(出于 6.5p7 的目的)可以使用该类型的左值访问的事物。在这种方法下,给定一段代码:

struct foo { int index,len; int *dat; };
void test1(struct foo *p)
{
  int *pp = &foo->len;
  *pp = 4;
}
void test2(struct foo *p, int dat)
{
  if (p->index < p->len)
  {
    p->dat[p->index] = dat;
    p->index++;
  }
}

应该认识到在 test1 中,对 *pp 的访问可能会访问 struct foo 对象 *p,因为 pp 显然是由 p。另一方面,编译器不需要在 test2 范围内适应 struct foo 类型的对象及其成员(例如 p->index)可能通过指针 p->index 进行修改的可能性。 =20=],因为 test2 中的任何内容都不会导致 struct foo 的地址或其任何部分存储在 p->dat.

然而,Clang 和 gcc 选择了不同的方法,表现得好像 6.5p7 允许通过其类型的任意指针访问结构成员,但根本不能通过指针访问联合成员,不包括括号中的数组表达式隐含的指针算术。鉴于 union { uint16_t h[4]; uint32_t w[2];} u; clang 和 gcc 将识别对 u.h[i] 的访问可能与 u.w[j] 交互,但不会识别 *(u.h+i) 可能与 *(u.w+j) 交互,即使标准定义前者带括号的表达式的含义等同于后者的形式。

假设在禁用基于类型的别名时,编译器始终有效地处理所有这些构造。然而,即使在许多常见情况下,该标准也没有强加任何要求,并且 clang 和 gcc 不对标准未强制要求的构造的行为做出任何承诺,即使迄今为止的所有版本都有效地处理了此类构造。因此,我不建议依赖 clang 或 gcc 来有用地处理任何涉及在不同时间以不同类型访问存储的任何事情,除非使用 -fno-strict-aliasing,并且在使用该选项时它们的古怪不是问题,所以我' d 建议简单地使用该选项,除非或直到 clang 和 gcc 采用更好定义的抽象。