V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tool2d
V2EX  ›  C++

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

  •  
  •   tool2d · 2023-03-23 15:50:14 +08:00 · 5167 次点击
    这是一个创建于 602 天前的主题,其中的信息可能已经有所发展或是发生改变。
    都说圣斗士不会在同一套招式下倒下两次,我偏偏就在同一个坑里,掉进去了两次。

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

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

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

    我再也不想用 memcpy 了,别和我说什么优化和性能,以后只用 memmove 走天下。
    28 条回复    2023-03-24 15:30:10 +08:00
    greensea
        1
    greensea  
       2023-03-23 15:56:41 +08:00   ❤️ 18
    我觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
    optional
        2
    optional  
       2023-03-23 15:57:16 +08:00 via iPhone   ❤️ 2
    有时候 memcpy 的结果才是你想要的呢。
    zagfai
        3
    zagfai  
       2023-03-23 15:57:54 +08:00
    我觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
    lwh0328
        4
    lwh0328  
       2023-03-23 16:03:38 +08:00
    我也觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
    dodng12
        5
    dodng12  
       2023-03-23 16:04:42 +08:00
    我也觉得 memcpy 不检查边界这件事情对 C 程序员来说应该是常识来着的
    tool2d
        6
    tool2d  
    OP
       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
        7
    FaiChou  
       2023-03-23 16:05:32 +08:00
    确实要注意下, memmove 如果碰到 src 地址小于 dest, 会从尾巴地方往后处理, 这样就避免了 overlapping 数据
    blinue
        8
    blinue  
       2023-03-23 16:07:02 +08:00
    lixile
        9
    lixile  
       2023-03-23 16:09:52 +08:00
    asan msan tsan lsan ubsan 五管齐下 可以用工具检测
    cy18
        10
    cy18  
       2023-03-23 16:12:35 +08:00
    说出来是知道的,但是时间久了容易忘,还是得靠工具检查。
    koebehshian
        11
    koebehshian  
       2023-03-23 16:58:09 +08:00
    有重叠用 memmove ,没有重叠用 memcpy ,memcpy 都让传长度了,有没有溢出肯定程序员负责的。刚学 C 的时候,一看这俩函数功能差不多,就仔细查一下它们的区别。
    sloknyyz
        12
    sloknyyz  
       2023-03-23 17:07:54 +08:00
    你通过正经手段分配的两段内存怎么可能会重叠。还不是因为自己指针搞来搞去,出事了又来怪 memcpy 。
    tool2d
        13
    tool2d  
    OP
       2023-03-23 17:13:54 +08:00
    @sloknyyz 所谓重叠,就是 memcpy 复制内存的时候,dst 和 src 是同一块内存的不同区域。

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

    if (dst <= src || dst >= (src + count))
    {
     // Non-Overlapping Buffers
      memcpy();
    } else {
      memmove();
    }
    nmap
        15
    nmap  
       2023-03-23 17:39:38 +08:00
    自己菜,这个问题属于常识
    ivvei
        16
    ivvei  
       2023-03-23 17:54:34 +08:00
    UB 就是这样的了,一个版本一个样也不奇怪。
    icyalala
        17
    icyalala  
       2023-03-23 18:07:56 +08:00   ❤️ 4
    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
        18
    ysc3839  
       2023-03-23 18:18:14 +08:00 via Android
    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
        19
    nightwitch  
       2023-03-23 18:30:42 +08:00   ❤️ 1
    #14 楼的代码有什么意义。。memmove 里自带这个判断。
    最好的办法就是当 memcpy 这个函数不存在,memmove 是它的上位替代
    junkun
        20
    junkun  
       2023-03-23 22:18:39 +08:00   ❤️ 2
    历史遗留问题,名字取得不好。rust 里这两个函数就改成了 std::ptr::copy 和 copy_nonoverlapping ,这就没人会认错了。
    Cormic
        21
    Cormic  
       2023-03-23 22:23:30 +08:00
    老话说得好:没有金箍棒别揽瓷器活
    xarthur
        22
    xarthur  
       2023-03-23 22:25:01 +08:00 via iPhone
    UB 害人(
    k9982874
        23
    k9982874  
       2023-03-23 22:27:35 +08:00 via Android
    菜是原罪
    xuboying
        24
    xuboying  
       2023-03-24 10:18:05 +08:00
    都说的很好,但是这好像是 C 的问题,C++干嘛要背锅。。。
    yolee599
        25
    yolee599  
       2023-03-24 10:36:44 +08:00
    memcpy 这个应该是一个程序中用得最多的函数,要求必须是高性能的,如果它内部加了很多判断,这样每调用一次性能都有损失。一个程序中有很多 memcpy 损失的性能就很可观了,因为大部分场景不需要 overlapping 但还是做了判断。
    Yeen
        26
    Yeen  
       2023-03-24 10:52:58 +08:00
    我记得多年前,很多公司会专门用 memcpy 的传递参数顺序来出题。
    tool2d
        27
    tool2d  
    OP
       2023-03-24 11:06:03 +08:00
    @yolee599 我已经掉坑里两次了,绝对不想要第三次的体验。已经全局#undef memcpy 了,全部走封装函数。

    UB 不是说能稳定复现的 BUG ,我宁可每次调用函数前,多判断一次的,牺牲不了什么性能。


    @ysc3839
    文档虽让这样说,但其实微软的 memcpy 是 crt 里开源的代码,感觉从来没出现过这类问题。我试了一下安卓,也没问题。仅仅是部分 linux 下的 glibc 有问题。
    smdbh
        28
    smdbh  
       2023-03-24 15:30:10 +08:00
    感觉是用 memcpy 干 memmove 的活啊
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1302 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 779ms · UTC 17:58 · PVG 01:58 · LAX 09:58 · JFK 12:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.