5

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

考虑以下程序:

#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说指针算术 withvoid *是一个错误。)

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

答案是否取决于实现是否具有宽松或严格的指针安全性([basic.stc.dynamic.safety])?

4

3 回答 3

8

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

如果A不是普通的旧数据,则以上是 UB(C++17 之前)和有条件支持(C++17 之后)。

您可能想要替换char*and int*auto*但这是一种风格。

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

旁白:我没有看到标准中的offsetof保证constexpr。;)

在任何情况下,替换:

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++ 的方式来做。

于 2017-09-08T20:22:34.690 回答
4

在您的特定示例中它是安全的,但这只是因为您的 struct 是标准 layout,您可以使用std::is_standard_layout<>.

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

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

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

编辑

这是我关心的问题:在 3.7.4.3 [basic.stc.dynamic.safety] 中,它说指针仅在(条件)时安全派生,并且如果我们有严格的指针安全性,那么如果指针不是来自这样的地方,则指针无效. 在 5.7 指针算术中,它说我可以在数组中进行通常的算术运算,但我没有看到任何东西告诉我结构偏移算术是可以的。我试图弄清楚这是否与我认为的方式无关,或者结构偏移算术在假设的“严格”实现中是否不合适,或者我读错了 5.7 (n4296)

当您进行指针算术运算时,您是在 的数组上执行它,该数组char的大小至少为sizeof(A),所以这很好。

然后,当您重新转换为第二个成员时,您将被 (2.4) 覆盖:

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

于 2017-09-08T20:22:43.100 回答
2

你应该检查你的假设。

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

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

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

于 2017-09-08T20:24:53.467 回答