【问题标题】:Type-Safe C++ wrapper for memcpy?memcpy 的类型安全 C++ 包装器?
【发布时间】:2017-07-31 21:56:48
【问题描述】:

鉴于std::copy(显然对于普通类型)只能作为memmove(*) 的包装器来实现,我想知道:

  • 在您需要memcpy 的时候,是否有标准C++ 类型安全包装器? (我数不清有多少次我忘了乘以sizeof。)
  • 如果标准中没有任何内容,是否有任何建议?如果没有,为什么不呢?
  • 在提供自动执行 sizeof 乘法的 memcpy 包装器方面是否存在任何特定障碍?

(*):C++ 标准库实现(从 MSVC 2005(!) 到现代 MSVC2015、libc++ 等)衰减 std::copy TriviallyCopyable 类型到 memmove。机器人memcpy。因为:

  • std::copy(src_first, src_last, destination_first) 定义为:

    如果d_first[first, last) 范围内,则行为未定义。

    • 只有目标范围的开始不得在源范围内。目标范围允许扩展到源范围。也就是说,d_first 可以位于源范围的“左侧”,而目标范围可以延伸到源范围内。
  • 对于std::memcpy,定义是这样的

    如果对象重叠,则行为未定义。

    • 也就是说,整个范围不得重叠:这就是 memcpy 成为最快变体的原因,因为它可以假设源和目标的内存完全不相交。
  • 对于std::memmove,定义为:

    对象可能重叠:复制就像将字符复制到临时字符数组,然后将字符从数组复制到目标一样。

    • 即源和目的范围可以任意重叠,没有限制。

鉴于此,很明显,您可以使用 std::memove 为 TrivialllyCopyable 类型实现 std::copy,因为 memmove 没有施加任何限制,并且可以在编译时通过类型特征完成对正确实现的调度 - -

但在 memcpy 方面很难实现 std::copy,因为 (a) 检查指针范围是否重叠必须在运行时完成,以及 (b) 甚至实现运行时检查不相关的内存范围could be quite a mess

所以,这给我们留下了

void* memcpy( void* dest, const void* src, std::size_t count );

一个接口不太出色的函数,您需要不断地将非字符对象的输入计数乘以它们的sizeof,而这完全是无类型的。

但是 memcpy 是最快的(并且有相当大的差距,请自行衡量),当您需要快速复制 TriviallyCopyable 类型时,您可以使用 memcpy。 表面上应该很容易包装在类型安全的包装器中,例如:

template<typename T>
T* trivial_copy(T* dest, T* src, std::size_t n) {
    return static_cast<T*>(std::memcpy(dest, src, sizeof(T) * n));
}

但是,不清楚您是否应该通过std::is_trival 或类似的方式进行编译时检查,当然可能会有一些讨论是否使用确切的memcpy 签名顺序,yadda yadda。

那么我真的必须自己重新发明这个轮子吗?是否针对标准进行了讨论?等等。


【问题讨论】:

  • 是什么让你觉得std::copy必须由memmove实现?
  • 这种包装器的用例是什么?为什么你对std::copy 本身不满意?
  • 你能举个例子,你需要手动memcpy(甚至根本复制)吗?在几乎所有情况下,所有这些都是在后台使用复制构造函数和复制分配完成的。
  • @nwp 不是“必须使用memmove”,而是“不能使用memcpy,可以(只能)使用memmove”,这就是我认为OP的意思。
  • “鉴于 std::copy 只能实现为 memmove 的包装器” - 这是不正确的。 std::copy 的文档说“如果源和目标范围重叠,则行为未定义。”,这使其衰减到 memcpy

标签: c++ memcpy


【解决方案1】:

为了澄清mencpy和memove的区别,根据docs memmove 可以将内存复制到与源内存重叠的位置,对于 memcpy,这是未定义的行为。

对象可能重叠:复制过程就像将字符复制到临时字符数组,然后将字符从数组复制到目标。

在您需要 memcpy 的时候,是否有标准 C++ 类型安全包装器? (我数不清有多少次我忘了乘以 sizeof。)

是的,std::copy(也许,解释如下)

如果标准中没有任何内容,是否有任何建议?如果没有,为什么不呢?

据我所知,该标准并未强制将 memmove/memcpy 用于 std::copy 用于琐碎类型。所以它的实施。比如visual studio update 2015 update 2 they did use memmove to speed things up:

"提高了 std::vector 重新分配和 std::copy() 的速度;当它们调用 memmove() 来处理普通可复制类型(包括用户定义的类型)时,它们的速度提高了 9 倍.

在提供自动执行 sizeof 乘法的 memcpy 包装器方面是否有任何特定障碍?

不,实际上你可以使用std::is_trivial自己实现这个

编辑:

根据this document 第 25.3.1 节,对 std::copy 实现的复杂性没有限制:

复杂性:正好是最后一个 - 第一个作业。

当您考虑到 memcpy 使用 cpu 特定指令(并非在所有 cpu 上都可用)来加速内存复制时,这非常有意义。

【讨论】:

  • @Martin Ba 添加了一个编辑,请跟进,因为我今天将在堆栈代码审查上发布替代实现。
  • fwiw,即使是 MSVC 2005 也对 std::copy 进行了 memmove 优化(我认为他们在 2005 年使用 memmove_s 在 2015 年使用 memmove,所以 2015 版本确实优化了更多。)跨度>
猜你喜欢
  • 2015-08-12
  • 1970-01-01
  • 2019-07-17
  • 1970-01-01
  • 2018-06-30
  • 2023-03-09
  • 2016-12-29
  • 1970-01-01
  • 2020-04-29
相关资源
最近更新 更多