【问题标题】:Valid and portable use of 'reinterpret_cast'?'reinterpret_cast' 的有效和可移植使用?
【发布时间】:2021-07-23 19:20:22
【问题描述】:

我们在工作中玩一些代码高尔夫。目的是保留to_upper 的签名并将所有参数返回给上层。我的一位同事提出了这个 ~~ugly~~ 精彩的代码:

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>

std::string operator+(std::string_view& a, int const& b) {
  std::string res;

  for (auto c : a) {
    res += (c - b);
  }
  return (res);
}

struct Toto {
  std::string data;
};

struct Result {
  std::string a;
  std::string b;
};

std::unique_ptr<Toto> to_upper(std::string_view input_a,
                               std::string_view input_b) {
  auto* res = new Result;
  res->a = (input_a + 32);
  res->b = (input_b + 32);
  auto* void_res = reinterpret_cast<void*>(res);
  auto* toto_res = reinterpret_cast<Toto*>(void_res);

  return std::unique_ptr<Toto>(toto_res);
}

int main() {
  std::unique_ptr<Toto> unique_toto_res = to_upper("pizza", "ananas");

  auto* toto_res = unique_toto_res.release();
  auto* res = reinterpret_cast<Result*>(toto_res);

  std::cout << res->a << std::endl;
  std::cout << res->b << std::endl;
  return 0;
}

这种使用reinterpret_cast 在可移植性和UB 方面是否合适? 我们认为这没关系,因为我们只是在类型上欺骗编译器,但也许我们遗漏了一些东西。

【问题讨论】:

  • 我不明白你为什么需要这些演员表,从 Toto 继承 Result 并使用 dynamic_cast 应该可以解决所有问题而不必担心 UB。
  • 避免使用幻数:'A' - 'a'
  • @sklott 这样的解决方案不要求Toto base 是一个多态类吗? (解决安全问题。)
  • 顺便说一句,a-z 不保证是连续的(EBCDIC 是一个反例)。所以'A' - 'a'不能保证等于'Z' - 'z'
  • 首先,reinterpret_cast 往返于void* 基本上没有意义——use static_cast instead

标签: c++ undefined-behavior reinterpret-cast


【解决方案1】:
std::string operator+(std::string_view& a, int const& b)

这可能不是完全不允许的,但在全局命名空间中为标准类定义运算符重载只是要求违反 ODR。如果您使用任何库并且如果其他人都认为这很好,那么其他人也可能会定义该重载。所以,这是个坏主意。

auto* void_res = reinterpret_cast<void*>(res);

这完全没有必要。通过将转换直接重新解释为 Toto*,您会得到完全相同的结果。

有效(且可移植)

假设小写和大写相距 32 并不是一个适用于所有字符编码的假设。对于超出a...z 范围的字符,该函数也无法正常工作。


现在关于主要问题。 reinterpret_cast 指向另一个本身的指针(或引用)永远不会有 UB。这完全取决于您如何使用生成的指针(或引用)。

这个例子有点不稳定,而唯一指针拥有重新解释的指针,因为如果抛出异常,那么它会尝试删除它,这会导致 UB。但是我认为不能抛出异常,所以应该没问题。否则,您只需重新解释强制转换,标准明确定义了在中间类型的对齐要求不比原始类型更严格的情况下(适用于本示例)。 p>

程序确实泄漏了内存。

【讨论】:

  • 一个operator + 减去每个元素的值...
【解决方案2】:

这里唯一的问题是你有内存泄漏。调用 release 后,您永远不会删除指针。

您可以使用reinterpret_cast 将对象转换为不相关的类型。您只是不允许访问该不相关的类型。从Result*Toto* 再回到Result* 是可以的,您只能通过Result* 访问Result 对象。

T*U* 然后返回到 T* 时,TU 都需要是对象类型,U 不能有比 T 更严格的对齐方式。在这种情况下,ResultToto 具有相同的对齐方式,所以你没问题。这在[expr.reinterpret.cast]/7中有详细说明

【讨论】:

  • 你确定吗? T* &lt;-&gt; void* 可以,但我怀疑 T* &lt;-&gt; U*。对齐可能有问题(一般情况下)。
  • @Jarod42 答案已更新以说明这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-14
  • 2022-08-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多