【问题标题】:Safety of reinterpret_cast on pointer to template aggregate typereinterpret_cast 在指向模板聚合类型的指针上的安全性
【发布时间】:2015-01-31 18:14:10
【问题描述】:

我想知道我对reinterpret_cast 的以下使用是否是未定义的行为。

给定一个模板聚合,例如...

template<typename T>
struct Container
{
  Container(T* p) : ptr(p) { }
  ...
  T* ptr;
};

... 以及类似 ...

的类型层次结构
struct A { };
struct B : A { };

鉴于B 是A 的动态类型,以下转换是否安全...

Container<B>* b = new Container<B>( new B() );
Container<A>* a = reinterpret_cast<Container<A>*>(b);

...就我现在可以安全地使用a-&gt;ptr 及其(可能是虚拟的)成员而言?

我使用它的代码编译和执行都很好(Clang,OS X),但我担心我已经放置了一个定时炸弹。我猜Container&lt;T&gt; 的每个实例都共享相同的布局和大小,所以这应该不是问题,对吧?

看看 cppreference.com 关于reinterpret_cast 的说法,似乎有一个合法用途的声明涵盖了我正在尝试做的事情......

类型别名 当指向 T1 类型对象的指针或引用被 reinterpret_cast(或 C 风格强制转换)到指向不同类型 T2 对象的指针或引用时,强制转换总是成功,但结果指针或引用只能在两个 T1和 T2 是标准布局类型,并且以下情况之一为真:

...

T2 是聚合类型或联合类型,它将上述类型之一作为元素或非静态成员(递归地包括子聚合的元素和所包含联合的非静态数据成员):这使得它可以安全地从结构的第一个成员和联合的元素转换到包含它的结构/联合。

我很感激我在这件事上似乎走错了路。这不是我关心的。我只想知道我所做的是否安全/合法。提前感谢您的帮助。

【问题讨论】:

标签: c++ casting


【解决方案1】:

似乎有一个合法用途的声明涵盖了我正在尝试做的事情......

这不是那个例外所说或意味着的。那个例外说给定

struct S { int i; } s;

您可以使用*reinterpret_cast&lt;int *&gt;(&amp;s) 访问s.i。

您正在尝试做的事情没有类似的例外。您正在尝试做的事情在 C++ 中根本无效。甚至以下内容也是无效的:

struct S { int i; };
struct T { int i; };
int f(S s) { return ((T &) s).i; }

并且编译器会根据您不编写这样的代码的假设进行优化。

对于使用当前编译器在运行时失败的实际示例:

#include <cstdlib>
struct S { int i; };
struct T { int i; };
void f(S *s, T *t) { int i = s->i; t->i++; if (s->i == i) std::abort(); }

在这里,GCC 优化了检查s-&gt;i == i(GCC 4.9.2,在命令行选项中使用-O2),并无条件调用std::abort(),因为编译器知道s 和t不可能指向同一个内存区域。即使您可能会尝试将其称为

int main() { S s = { 0 }; f(&s, reinterpret_cast<T *>(&s)); }

【讨论】:

  • 好东西。 s-&gt;i == i 的证明可以证明这一点。我的测试是用-O0 进行的,我想这是令人担忧的部分。谢谢。
  • 为什么会这样? #include struct S { int i[1]; };结构 T { int i[1]; };无效 f(S *s, T *t) { int i = s->i[0]; t->i[0]++; if (s->i[0] == i) std::abort(); } int main() { S s = { 0 }; f(&s, reinterpret_cast(&s)); }
  • @nachum 通过“工作”,你的意思是它不会为你中止?在我在答案中指定的-O2 级别,它显示了自 4.9 以来在gcc.godbolt.org 上的所有 GCC 版本中无条件调用中止。
  • 啊,MacOS High Sierra 上的 clang(Apple LLVM 版本 9.0.0 (clang-900.0.39.2))没有中止。但它中止了您的原始代码。
【解决方案2】:

根据标准,类型别名是否合法,您可能还有其他问题。

我猜Container&lt;T&gt; 的每个实例都共享相同的布局和 大小应该没问题吧?

实际上,并非Container&lt;T&gt; 的每个实例都共享相同的布局!正如this question 中所述,模板成员仅在使用时才会创建,因此如果每种类型使用不同的成员,您的Container&lt;A&gt; 和Container&lt;B&gt; 可能会有不同的内存布局。

【讨论】:

  • 是的,如果我将Container 专门用于某些类型,那将是正确的——我明白了——但在这种情况下,任何类型T 都只有ptr
  • 这不仅仅是你的专长。成员(成员函数和成员变量)可能在不同的实例中被忽略。在我链接到的另一个问题中,他们使用了实例化 std::list&lt;T&gt; 的示例,其类型无法排序。只要从未调用过std::list&lt;T&gt;::sort,该类就会编译。此外,假设您使用 Container&lt;B&gt; 的 3 个成员变量,但只使用 Container&lt;A&gt; 的两个成员变量。它们的内存布局可能不兼容。此外,如果您在每个类上使用不同的方法,则方法地址可能不会对齐,您...
  • ..当您在一个类上调用成员函数时,可能最终会跳入错误的函数或函数的中间。 (不太确定成员函数的情况,但我对成员变量的情况持肯定态度)。
  • 我明白你的推理。我想我更关心ptr 的使用,而不是实际的Container 类型本身。绝对正确,使用Container 会有风险,但在这种情况下,它只能用作ptr 的包装器。感谢您的意见。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-19
  • 1970-01-01
  • 1970-01-01
  • 2021-07-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多