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

时间:2017-05-17 03:26:02

标签: c++ struct offset size-t

为什么在以下代码中......

#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的一个演员 - 这是一个工作案例中的数组(我认为它退化为一个指针) - 一个{{ 1}}。所以我认为代码将一个指针(即一个地址)转换为size_t ......这似乎是一个无害但无意义的操作......但在实践中,在工作的情况下,代码不会返回一个毫无意义的值,但可靠地返回似乎是size_t的偏移量。为什么会这样?

2 个答案:

答案 0 :(得分:1)

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

首先,数字0(相当于NULL)会投放到Foo*。你唯一允许使用空指针的做法是将它与NULL进行比较。

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

代码是无稽之谈。也许这意味着:

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

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

实际上,当c是一个数组时,数组到指针转换有效地插入一个隐式&运算符,因此您得到的结果等效于{ {1}}。

答案 1 :(得分:1)

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

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

它确实只是检查偏移量。如果要对其进行指针数学运算,最好实际分配结构的实例;或者使用offsetof(Foo,c)作为potatoswatter指出。