memcpy 绝对是 C++里的史前巨坑!

2023-03-23 15:50:14 +08:00
 tool2d
都说圣斗士不会在同一套招式下倒下两次,我偏偏就在同一个坑里,掉进去了两次。

最近写了一个服务器程序,会随机出现一些乱字符,很不好调试,查了半天,发现是 memcpy 复制的时候,有 overlapping buffer 内存,而这个行为在 C++标准里, 是 UB 行为,官方允许复制结果出错!

windows 上有类似功能的函数,叫 CopyMemory ,就从来没出过这种情况,真是大意了。

其实以前遇到过这个 bug ,查到有资料说,高版本 linux 会自动判断输入范围,自动改成 memmove ,就轻视了。没想到后来 glibc 为了性能优化,中间又给改回去了。真是晕过去,API 行为都能反复横跳的吗?

我再也不想用 memcpy 了,别和我说什么优化和性能,以后只用 memmove 走天下。
4832 次点击
所在节点    C++
28 条回复
greensea
2023-03-23 15:56:41 +08:00
我觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
optional
2023-03-23 15:57:16 +08:00
有时候 memcpy 的结果才是你想要的呢。
zagfai
2023-03-23 15:57:54 +08:00
我觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
lwh0328
2023-03-23 16:03:38 +08:00
我也觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
dodng12
2023-03-23 16:04:42 +08:00
我也觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
tool2d
2023-03-23 16:05:11 +08:00
@greensea 习惯了 windows 开发,memcpy 从来就不需要额外检测,自动处理同一块内存里的复制搬运。微软就是一个好保姆,一切都默默帮你处理好了。

memcpy 行为和 windows 相差甚远,单纯为了性能,我也是能理解的。但不能说 glibc 改了一半后,高版本号又给改了回去啊。这不算偷袭老年人嘛。

https://man7.org/linux/man-pages/man3/memcpy.3.html 里 note 部分,写明了部分 glibc 版本的影响范围,我就中招了。
FaiChou
2023-03-23 16:05:32 +08:00
确实要注意下, memmove 如果碰到 src 地址小于 dest, 会从尾巴地方往后处理, 这样就避免了 overlapping 数据
blinue
2023-03-23 16:07:02 +08:00
lixile
2023-03-23 16:09:52 +08:00
asan msan tsan lsan ubsan 五管齐下 可以用工具检测
cy18
2023-03-23 16:12:35 +08:00
说出来是知道的,但是时间久了容易忘,还是得靠工具检查。
koebehshian
2023-03-23 16:58:09 +08:00
有重叠用 memmove ,没有重叠用 memcpy ,memcpy 都让传长度了,有没有溢出肯定程序员负责的。刚学 C 的时候,一看这俩函数功能差不多,就仔细查一下它们的区别。
sloknyyz
2023-03-23 17:07:54 +08:00
你通过正经手段分配的两段内存怎么可能会重叠。还不是因为自己指针搞来搞去,出事了又来怪 memcpy 。
tool2d
2023-03-23 17:13:54 +08:00
@sloknyyz 所谓重叠,就是 memcpy 复制内存的时候,dst 和 src 是同一块内存的不同区域。

我就想把一块内存后半段,搬运到前半段,这需求还是挺常见的吧。
tool2d
2023-03-23 17:18:12 +08:00
@koebehshian 也就是多一个 if 判断的问题,我已经打算自己写一个封装函数了。

if (dst <= src || dst >= (src + count))
{
 // Non-Overlapping Buffers
  memcpy();
} else {
  memmove();
}
nmap
2023-03-23 17:39:38 +08:00
自己菜,这个问题属于常识
ivvei
2023-03-23 17:54:34 +08:00
UB 就是这样的了,一个版本一个样也不奇怪。
icyalala
2023-03-23 18:07:56 +08:00
memcpy 对于 overlap UB 这个是写文档里的。。更早之前 glibc 的 memcpy 刚好对 overlap 还没问题,然后 2010 年马凌提了个性能优化的 patch ,性能有提升,但对 overlap 不支持了。正好 Adobe 程序员和楼主一样乱用,然后导致 flash 出现爆音之类的问题,当时很多人来辩论,甚至 Linus 还过来骂了几句: https://bugzilla.redhat.com/show_bug.cgi?id=638477

那个作者还在知乎,楼主可以去对线: https://www.zhihu.com/question/35172305/answer/73698602
ysc3839
2023-03-23 18:18:14 +08:00
https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa366535(v=vs.85)
If the source and destination blocks overlap, the results are undefined. For overlapped blocks, use the MoveMemory function.
nightwitch
2023-03-23 18:30:42 +08:00
#14 楼的代码有什么意义。。memmove 里自带这个判断。
最好的办法就是当 memcpy 这个函数不存在,memmove 是它的上位替代
junkun
2023-03-23 22:18:39 +08:00
历史遗留问题,名字取得不好。rust 里这两个函数就改成了 std::ptr::copy 和 copy_nonoverlapping ,这就没人会认错了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/926553

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX