c++ - memcpy 在尝试 ‘fast’ pimpl 期间未优化

标签 c++ memcpy strict-aliasing

我需要使用一个非常大且复杂的仅 header 类(想想 boost::multiprecision::cpp_bin_float<76>,下面称为 BHP),我想将其隐藏在类似 pimpl 的实现后面,纯粹是为了在较大的项目中减少编译时间(将 Boost 类替换为 std::complex<double> 减少了大约 50% 的编译时间)。

但是,我想避免动态内存分配。因此,这样的事情看起来很自然(暂时忽略可以使用 aligned_storagealignas 避免的对齐问题):

struct Hidden {
  char data[sz];

  Hidden& punned(Hidden const& other);
};

Hidden::punned然后可以在单个翻译单元中定义以转换 dataBHP* ,对其采取行动,不要用 170k LOC 的头文件污染所有其他翻译单元。一个可能的实现可能是

Hidden& Hidden::punned(Hidden const& other) {
  *(BHP*)(data) += *(BHP*)(other.data);
  return *this;
}

当然,这是未定义的行为,因为我们访问了一个 BHP 类型的对象通过 char 类型的指针,因此违反了严格的别名规则。正确的做法是:

Hidden& Hidden::proper(Hidden const& other) {
  BHP tmp; std::memcpy(&tmp, data, sz);
  BHP tmp2; std::memcpy(&tmp2, other.data, sz);
  tmp += tmp2;
  std::memcpy(data, &tmp, sz);
  return *this;
}

现在这些 memcpy 可能看起来“很明显”电话可以优化掉。不幸的是,情况并非如此,它们仍然存在并制作proper()punned() 大得多.

我想知道 a) 将数据直接存储在 Hidden 中的正确方法是什么对象和 b) 避免不必要的拷贝来重新解释它,并且 c) 避免违反严格的对齐规则,并且 d) 不要携带指向存储区域的额外指针。

有一个godbolt link here ;请注意,我测试的所有编译器(GCC 4.9 - trunk、Clang 3.9、4.0 和 5.0 以及 Intel 18)都没有“优化”memcpy。某些版本的 GCC(例如 5.3)也直接提示违反了严格的别名规则,尽管并非所有版本都这样做。我还插入了一个 Direct知道 BHP 的类因此可以直接调用它,但我想避免这种情况。

最小工作示例:

#include <cstring>

constexpr std::size_t sz = 64;

struct Base {
  char foo[sz];
  Base& operator+=(Base const& other) { foo[0] += other.foo[0]; return *this; }
};
typedef Base BHP;

// or:
//#include <boost/multiprecision/cpp_bin_float.hpp>
//typedef boost::multiprecision::number<boost::multiprecision::cpp_bin_float<76> > BHP;

struct Hidden {
  char data[sz];

  Hidden& proper(Hidden const& other);
  Hidden& punned(Hidden const& other);
};

Hidden& Hidden::proper(Hidden const& other) {
  BHP tmp; std::memcpy(&tmp, data, sz);
  BHP tmp2; std::memcpy(&tmp2, other.data, sz);
  tmp += tmp2;
  std::memcpy(data, &tmp, sz);
  return *this;
}

Hidden& Hidden::punned(Hidden const& other) {
  *(BHP*)(data) += *(BHP*)(other.data);
  return *this;
}

struct Direct {
  BHP member;
  Direct& direct(Direct const& other);
};

Direct& Direct::direct(Direct const& other) {
  member += other.member;
  return *this;
}

struct Pointer {
  char storage[sz];
  BHP* data;

  Pointer& also_ok(Pointer const& other);
};

Pointer& Pointer::also_ok(Pointer const& other) {
  *data += *other.data;
  return *this;
}

最佳答案

This, of course, is undefined behaviour, because we access an object of type BHP through a pointer of type char.

其实不是这样的。通过 char* 访问 is fine 假设那里确实有一个BHP对象。也就是说,只要双方都有:

new (data) BHP(...);

那么这完全没问题:

*(BHP*)(data) += *(BHP*)(other.data);

只需确保您的字符数组也是 alignas(BHP)

请注意,gcc 有时不喜欢您重新解释 char[],因此您可以选择使用类似 std::aligned_storage_t 的内容.

关于c++ - memcpy 在尝试 ‘fast’ pimpl 期间未优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47815831/

相关文章:

c++ - memcpy(),更改预定义变量的值时出现段错误

c - 警告 : passing argument 2 of ‘memcpy’ makes pointer from integer without a cast [enabled by default]

c++ - 以符合标准的方式使用与数组相同类型的成员重新解释结构

c++ - 如何创建不破坏严格别名的 uint8_t 数组?

C++ STL : Trouble with iterators

c++ - 在这种情况下我可以依赖连续内存吗?

c++ - 在内存中设置的二维数组是否与模拟二维数组的一维数组连续相似?

C++ 类型别名,其中值被替换

c++ - 如何通过 CLI 发布使用 C++ 库的应用程序?

c++ - 无法为 Windows Mobile 构建 C++ 项目