前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C/C++】为什么不都用memmove代替memcpy

【C/C++】为什么不都用memmove代替memcpy

作者头像
嵌入式与Linux那些事
发布2023-03-24 14:09:58
6340
发布2023-03-24 14:09:58
举报

在知乎看见一个比较好的问题,整理下分享给大家。

memmove相比memcpy增加了内存重叠的判断,更加安全,效率只是差了那么一丢丢, 为什么经常看见memcpy, 很少看见memmove 呢 ?是不是因为memcpy语义上说明了两段内存是不重叠的所以有些场景使用memcpy 更合适 ?

杨个毛答案:

不要嘲讽题主,其实某天很愤怒的Linus也是这么想的——

Strange sound on mp3 flash website

Linus Torvalds 2010-11-30 20:50:25 EST (In reply to comment #128) > In Adobe's software. > > I'm no great fan of flash but it's an essential part of life on the web these > > days and I had thought that the Fedora project had finally put its days of > > broken flash support behind it. > Fedora's flash support is fine. Adobe's software is broken. Quite frankly, I find your attitude to be annoying and downright stupid. How hard can it be to understand the following simple sentence: THE USER DOESN'T CARE. Pushing the blame around doesn't help anybody. The only thing that helps is Fedora being helpful, not being obstinate. Also, the fact is, that from a Q&A standpoint, a memcpy() that "just does the right thing" is simply better. Quoting standards is just stupid, when there's two simple choices: "it works" or "it doesn't work because bugs happen". Standards are paper. I use paper to wipe my butt every day. That's how much that paper is worth. Reality is what matters. When glibc changed memcpy, it created problems. Saying "not my problem" is irresponsible when it hurts users. And pointing fingers at Adobe and blaming them for creating bad software is doubly irresponsible if you are then not willing to set a higher standard for your own project. And "not my problem" is not a higher standard. So please just fix it. The easy and technically nice solution is to just say "we'll alias memcpy to memmove - good software should never notice, and it helps bad software and a known problem".

(加粗是我加的)

当然他这是从库函数的角度来说,他觉得从一开始就干脆搞成memcpy就是memmove,然后就没这么多毛病了。

另外有人质疑说到底性能差多少。Linus的Argument是memmove就比memcpy多一条判断指令。

我来换句话说,如果反正地址是不重叠的,那么memmove一定可以写成if (地址不重叠) memcpy();的形式。而如果地址是重叠的,速度慢一点的defined behavior总比undefined behavior强。

発趷答案:

就是历史包袱。

题主全用 memmove 代替 memcpy 的想法,不仅不可笑,而且如果放到现在来设计标准库,只提供一个函数才是正确的设计。

举证几点:

  1. Linus 曾经谈过这个话题, @杨个毛的答案中已经完整引述了。
  2. Windows 的 C 运行库(msvcrt)里,memcpy 实际上是 memmove 的 alias。微软虽然在 MSDN 里还是按照标准的说法描述 memcpy 和 memmove,但它的 C 库实际上二十年前就不区分了。
  3. 【这一条有误,删除】

有其他答主认为:

因为实际情况中,两个区域是否重叠往往是可以预期的

真的未必。Linus 说的那一大段,当时背景就是 adobe flash player 里有一些该使用 memmove 的地方误用了 memcpy,glibc 某一次升级后暴露了 flash 的这个问题,导致 flash 在 Linux 下面播放音频有杂音。

此时让程序崩溃掉反而是好事,因为崩溃能够更明显的提醒你这里有个bug

memmove 误用 memcpy 不一定会崩掉,可能只会让复制结果不正确。【评论区提示 OpenBSD 的 memcpy 在重叠时会崩】。

关于效率,也就是 memmove 开头加一个分支,不重叠时走 memcpy 一样的代码。调用一次函数比那个分支贵多了,真在极端情况下要省一两个 cycle,也应该先考虑内联。

另,现在很多 Linux 发行版已经在 gcc 中默认把 _FORTIFY_SOURCE 给打开了,它给很多函数增加额外的安全检查,例如 memcpy(dst, src, n) 会被替换成 __memcpy_chk(dst, __builtin_object_size(dst), src, n),后者会增加对缓冲区大小的检查;有的发行版还把 -fstack-protector-all 也给默认打开了。可以看出,大家现在已经不再认为这些简单的检查会有什么效率问题。

C 的历史太久,不要觉得它的设计都是对的。再举个例子,time 函数大家都熟悉:

time_t time(time_t *ptr);

为什么它既把时间写进 *ptr,又作为返回值返回呢?见过有人一本正经地论证这个设计有多么牛逼…… 其实哪有那么复杂啊,就是因为 time_t 最初是一个结构体,当时是 void time(time_t *ptr),后来改成整数了,加上返回值方便一点,又为减少对旧代码的影响没有删掉那个参数。

又比如 & | 优先级比 == 低,不要试图去认证它有多合理,它就是不合理。这么设计,纯粹是因为最早的 C 没有 && ||,当时逻辑与、或也用 & |(对,没有短路特性)遗留下来的。

韦易笑答案

当 memcpy / memmove 优化到极致,多一两次判断对整体性能的影响都是比较大的,特别是再流水线比较长的处理器体系中,即便就是有多次判断,一般也要想各种办法归并,比如 memmove 开头需要用两个判断来比较是否有覆盖,类似这样:

代码语言:javascript
复制
if (dst < src || dst > src + size) { 正向拷贝 } else { 逆向拷贝 }

用位运算,可以将两次判断合并成一次判断:

代码语言:javascript
复制
if (((dst - src) | (src + size - dst)) & 0x80000000) { 正向拷贝 } else { 逆向拷贝 }

这样分支少了一半,推而广之,连续的大小比较可以归纳为一次比较:

代码语言:javascript
复制
if (x > a || y > b || z > c || w > d) { ... }

可以变换成:

代码语言:javascript
复制
if (((a - x) | (b - y) | (c - z) | (d - w)) < 0) { ... }

或者

代码语言:javascript
复制
if (((a - x) | (b - y) | (c - z) | (d - w)) & 0x80000000) { ... }

用位运算将四次比较合并成了一次。

类似的还有,两次判断

代码语言:javascript
复制
if (x >= 0 && x < limit) { ... }

合并成:

代码语言:javascript
复制
if ((unsigned)x < limit) { ... }

以及四次

代码语言:javascript
复制
if (x < 0 || x >= w || y < 0 || y >= h) { ... }

也可以变成

代码语言:javascript
复制
if (((x) | (w - 1 - x) | (y) | (h - 1 - y)) < 0) { ... }

四归一

---

不过 c 标准库里冗余挺多的,有些也不一定是性能问题,还有历史遗留原因。

原文链接:https://www.zhihu.com/question/264505093?utm_campaign=Sharon&utm_medium=social&utm_oi=805166261268004864&utm_psn=1619101169628004352&utm_source=wechat_session&utm_content=group1_supplementQuestions 版权归原作者所有,侵权请联系小编删除

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-03-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式与Linux那些事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档