memcpy 绝对是 C++里的史前巨坑! - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tool2d
V2EX    C++

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

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

    最近写了一个服务器程序,会随机出现一些乱字符,很不好调试,查了半天,发现是 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 版本的影响范围,我就中招了。
    ethusdt
        7
    ethusdt  
       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     Solana     2570 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 15:14 PVG 23:14 LAX 07:14 JFK 10:14
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86