【问题标题】:Passing pointers to arrays of unrelated, but compatible, types without copying?将指针传递给不相关但兼容的类型数组而不复制?
【发布时间】:2021-06-13 02:56:25
【问题描述】:

(免责声明:在这一点上,这主要是学术兴趣。)

想象一下我有这样一个外部接口,也就是我不控制它的代码:

// Provided externally: Cannot (easily) change this:

// fill buffer with n floats:
void data_source_external(float* pDataOut, size_t n);

// send n data words from pDataIn:
void data_sink_external(const uint32_t* pDataIn, size_t n);

是否可以在标准 C++ 中在这两个接口之间“移动”/“流式传输”数据而无需复制?

也就是说,有什么方法可以使以下内容成为非 UB,而无需在两个正确类型的缓冲区之间复制数据?

int main()
{
  constexpr size_t n = 64;
  float fbuffer[n];
  data_source_external(fbuffer, n);

  // These hold and can be checked statically:
  static_assert(sizeof(float) == sizeof(uint32_t), "same size");
  static_assert(alignof(float) == alignof(uint32_t), "same alignment");
  static_assert(std::numeric_limits<float>::is_iec559 == true, "IEEE 754");
  
  // This is clearly UB. Any way to make this work without copying the data?
  const uint32_t* buffer_alias = static_cast<uint32_t*>(static_cast<void*>(fbuffer));

  // **Note**: 
  // + reinterpret_cast would also be UB.

  data_sink_external(buffer_alias, n);
  // ...

据我所知,以下将是定义的行为,至少在严格别名方面:

...
uint32_t ibuffer[n];
std::memcpy(ibuffer, fbuffer, n * sizeof(uint32_t));
data_sink_external(ibuffer, n);

但鉴于ibufferfbuffer 具有完全相同的位,这似乎很疯狂。

或者我们会expect optimizing compilers to optimize 甚至这个副本吗? (在一个现已删除的类似评论的答案中,用户发布了一个godbolt link,这似乎表明,至少乍一看,clang 11 确实能够优化 memcpy。)

【问题讨论】:

  • 简短回答:不。C++ 不允许将一种类型视为另一种类型。有几个例外,但这里都不适用。
  • 如果只有 data_sink_external 接受了普通的旧“字符”,那么至少会有机会......
  • This 可能工作,只要在编译时知道n。虽然我觉得奇怪的是 GCC 能够优化数组之间的转换但不能内联函数,但另一方面 Clang 无法优化转换,但是一旦它被内联到 main 中就能够做到这一点.
  • 使用std::bit_caststd::array 两个编译器都更喜欢它(demo here

标签: c++ arrays language-lawyer strict-aliasing


【解决方案1】:

我没有测试也不能发表评论(因为没有足够的声誉)。但是 reinterpret_cast 在这种情况下可能会有所帮助。

Documentation

基本上它告诉编译器,嘿把这个指针当作是强制转换中的指定类型。

【讨论】:

  • 为此目的使用reinterpret_cast 违反了严格的别名规则。它可能看起来在大多数测试中都有效,但它是未定义的行为。阅读您链接的网站的“类型别名”部分。
  • reinterpret_cast 相比,在这里将static_cast 设置为无效然后设置为不同的类型没有区别。问题不是“如何”,而是标准是否允许。注意 language-lawyer 标签。
  • @FrançoisAndrieux 在使用reinterpret_cast之后,首先可能违反的不是严格的别名规则,而是timsong-cpp.github.io/cppwp/n4861/expr.add#6.sentence-1
  • @LanguageLawyer:C 和 C++ 标准都没有做出合理的努力来枚举所有或几乎所有常见实现一致处理的所有极端情况,并且应该继续如此。早期标准的作者并不打算将具有相同大小和表示形式的 PODS 在比规定更多的情况下视为兼容,这似乎令人难以置信。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
相关资源
最近更新 更多