升级为C++11后程序挂了

作者: | 更新日期:

分享一个内存的知识点

本文首发于公众号:天空的代码世界,微信号:tiankonguse

一、背景

之前介绍过,我们的开发语言是c++,编译环境是c++98,最近在搞 EP、上云、中台等。

由于业务需要,正常跑了五六年的服务需要接入一个新功能,别人提供了一个静态库。

正常情况下也没啥,这五六年来接入了无数新功能。

唯一的不同是这个静态库使用 c++11 实现,我的服务要接入就需要也使用 c++11 编译。

这本来也没什么,结果发现接入新功能后,服务发布后有失败,于是我花了快两小时研究这个问题,过程记录一下。

二、谁引入的问题

为了排除是新功能的问题,我另外发布了两个版本。

第一个版本是什么都不做,直接拉git代码编译发布,来验证现有的编译环境是正常的。

其中包括变更前的代码、目前的编译机器,目前的公共库(其他人维护自动更新)。

验证发现,不做变更的代码没有任何问题。

第二个版本只引入c++11编译参数,而不引入新的功能与库。

结果发现,引入c++11后问题就出现了。

于是确定问题,代码什么都不该,编译选项增加c++11引入新问题。

三、包有问题

上面提到我的服务发布后,出问题了。

其实并不是我的服务出问题了,而是我的其中一个下游出问题了,不识别我的请求包。

如上图,其他人调用我的服务,我会调用三个服务 A、MQ、B。
结果MQ 服务出现不识别我的请求包。

面对这个问题,我先使用 tcpdump 抓了一下 MQ 的 TCP 二进制包,分析了一下,发现数据包是正确的。

这怎么可能呢?

MQ 服务恰好也是我的,我便给 MQ 服务加了一些辅助日志信息,发现并不是所有的请求包都有问题。

只有请求大于 1K 的时候才会发生这个问题。

另外我还发现,发生问题时,包的数据乱了。

接着我抓了所有 TCP 包,找到一个有问题的包,一分析,发现我的服务发出的包就不对。

实锤是引入 c++11,导致某个逻辑变得不符合预期了。

四、memcpy的锅

知道是大于 1K 的包有问题了,那就在测试环境构造一个大的数据包,然后使用 gdb 来分析打包函数。

很快定位到是一个公共库的锅,它执行完 memcpy 后,数据发生错乱了。

什么是公共库呢?
就是大家一起维护的常用的功能库。

这些库一般已经经过无数业务线上验证。
比如这次这个公共库,已经运行了七八年了。

不过之前是 c++98的编译环境,现在换成了 c++11,这些公共库是否正常就不好说了。

这个 memcpy 问题显然是内存覆盖问题,行为是未定义的,恰好以前的编译环境没问题。

五、内存覆盖问题

啥是内存覆盖问题呢?
看段代码就明白了。

假设我们要讲一个字符串从一片内存空间复制进另一个内存空间中,一般会使用 memcpy 来复制。

正常情况下复制是没问题的。

但当要复制的内存空间与目标内存空间有重叠时,就会发生内存问题。

如上图,一个长度为 m 的字符串,需要在内存上左移 n 字节, 或者右移 n 字节。
而 n 小于 m 时就存在内存覆盖问题。

覆盖的部分是未定义的,因为我们不知道复制是从前到后,还是从后到前,或者从中间复制。

那怎么解决这个问题呢?
一般推荐的是 memmove 来移动。

不过 memmove 的实现是先开辟一个临时空间,这样导致性能比较低下。

我们可以自己实现一个代理函数,先判断空间是否有重叠,没有了直接 memcpy,有重叠了再来使用 memmove 即可。

六、最后

回头想想,memcpy 这个问题其实并不容易定位。

幸好两个服务都是我的,可以随时加日志,然后抓包来辅助,才使得一个小时多定位出问题来。

感觉其他情况,这个问题要花费一天时间了。

思考了:除了memcpy,你还遇到什么编译参数导致的问题?

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号ID:tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越