size_t struct 非数组成员的转换崩溃

size_t cast of struct non-array member crashes

为什么在下面的代码中...

#include <iostream>

struct Foo
{
  int a;
  char b;
  char c[1];
};

struct Bar
{
  int a;
  char b;
  char c;
};

int main( int argc, char* argv[] )
{
  std::cout << "Why does this work? " << (size_t) (((struct Foo*) 0)->c) << std::endl;
  std::cout << "Why does this crash? " << (size_t) (((struct Bar*) 0)->c) << std::endl;
  return 0;
}

...第二个 size_t 操作是否会导致 SIGSEGV 而第一个操作不会?这个程序的输出是:

Why does this work? 5
Segmentation fault (core dumped)

有人能解释一下这行代码的实际作用吗?

(size_t) (((struct Foo*) 0)->c)

我以前没见过这种语法,但对我来说它看起来像是 c 的转换 - 这是工作案例中的一个数组(我认为它退化为指针) - 到size_t。所以我认为代码将指针(即地址)投射到 size_t...这似乎是无害但无意义的操作...但实际上,在工作情况下,代码不会 return 一个无意义的值,但相当可靠 returns 似乎是 c 的偏移量。为什么会这样?

(size_t) (((struct Bar*) 0)->c)

首先,数字 0,相当于 NULL,被转换为 Foo*。唯一允许对空指针做的事情是将它与 NULL.

进行比较

但是,指针被 ->c 非法访问,产生了 未定义的行为 。在这种情况下,程序可能会因 SIGSEGV 而崩溃,也可能不会。

代码是废话。也许是这个意思:

(size_t) &(((struct Bar*) 0)->c)

这也是非法的,但恰好是实现offsetof宏的老式方法。

碰巧,当 c 是一个数组时,数组到指针 的转换有效地插入了一个隐式的 & 运算符,所以你得到结果相当于 offsetof(Foo, c).

如果结构指针(struct Foo*) 0 的地址为0x0000,c 是其数组的地址,则数组的地址((struct Foo*) 0)->c 为0x0005。毕竟,数组的名称代表它的地址。

正如 Griffiths 指出的那样,如果 Bar 的临时指针是 0x0000,并且 c 是其字符变量之一的地址,它会尝试从 0x0005 获取 ((struct Bar*) 0)->c 的值,显然从未分配过,导致段错误。

看起来确实只是在检查偏移量。如果要对其进行指针数学运算,最好实际分配一个结构实例;或使用 offsetof(Foo, c) 正如 potaswatter 指出的那样。