C ++中的结构偏移和指针安全性

时间:2017-09-08 20:09:01

标签: c++ c++11 pointers language-lawyer offsetof

这个问题是关于使用带有结构偏移的指针算法派生的指针。

考虑以下计划:

#include <cstddef>
#include <iostream>
#include <new>

struct A {
  float a;
  double b;
  int c;
};

static constexpr auto off_c = offsetof(A, c);

int main() {
  A * a = new A{0.0f, 0.0, 5};
  char * a_storage = reinterpret_cast<char *>(a);
  int * c = reinterpret_cast<int *>(a_storage + off_c));

  std::cout << *c << std::endl;

  delete a;
}

该程序似乎可以工作,并使用默认设置和C ++ 11标准在我测试的编译器上给出预期结果。

(一个密切相关的计划,我们使用void *代替char *static_cast代替reinterpret_cast, 并未被普遍接受。 gcc 5.4发出有关使用void指针的指针算法的警告,clang 6.0表示该指针 使用void *进行算术是一个错误。)

根据C ++标准,该程序是否具有明确定义的行为?

答案取决于实施是否已放宽或严格指针安全([basic.stc.dynamic.safety])?

3 个答案:

答案 0 :(得分:8)

您的代码中没有根本错误。

如果A不是普通旧数据,则上面是UB(在C ++ 17之前)并且有条件地支持(在C ++ 17之后)。

您可能希望将char*int*替换为auto*,但这是一种风格。

请注意,指向成员的指针以类型安全的方式完成相同的操作。大多数编译器实现了一个指向成员的指针...作为类型中成员的偏移量。然而,它们确实在任何地方工作,甚至在非吊舱结构上也是如此。

除此之外:我没有看到标准中offsetofconstexpr的保证。 ;)

无论如何,请替换:

static constexpr auto off_c = offsetof(A, c);

static constexpr auto off_c = &A::c;

  auto* a_storage = static_cast<char *>(a);
  auto* c = reinterpret_cast<int *>(a_storage + off_c));

  auto* c = &(a->*off_c);

用C ++方式做。

答案 1 :(得分:4)

在您的具体示例中这是安全的,但仅因为您的结构是标准布局,您可以使用std::is_standard_layout<>仔细检查。

尝试将其应用于结构,例如:

struct A {
  float a;
  double b;
  int c;
  std::string str;
};

即使字符串超出了相关结构的一部分,也是非法的。

修改

  

下面是关于abt的内容:在3.7.4.3 [basic.stc.dynamic.safety]中它表示只有在(条件)时才能安全地导出指针,并且如果我们有严格的指针安全性,那么指针无效,如果它不是来自这样的地方。在5.7指针算术中,它说我可以在数组中进行通常的算术,但我没有看到任何告诉我结构偏移算术是可以的。我试图弄清楚这是否与我认为的方式无关,或者假设的“严格”推算中结构偏移算术是否正常,或者我是否读错了5.7(n4296)

当您执行指针算术时,您在char的数组上执行它,其大小至少为sizeof(A),所以没关系。

然后,当你回到第二个成员时,你将被(2.4)所涵盖:

  

- 安全派生指针值的明确定义的指针转换(4.10,5.4)的结果;

答案 2 :(得分:1)

你应该检查你的假设。

假设#1)offsetof以字节为单位给出正确的偏移量。只有当类被认为是“标准布局”时才有保证,它有许多限制,例如没有虚拟方法,避免多重继承等。在这种情况下,应该没问题,但一般情况下你不能一定不会。

假设#2)char与字节大小相同。在C中,这是定义,所以你是安全的。

假设#3)offsetof给出了指向类的指针的正确偏移,而不是从数据的开头。这与#1基本相同,但vtable肯定是个问题。同样,只适用于标准布局。