admin管理员组

文章数量:1130349

导读

Valgrind 最为开发者熟知和广泛使用的工具莫过于 Memcheck,它是检查 c/c++ 程序内存错误的神器,报告结果非常之精准。

本文主要分享作者在使用该神器解决内存问题的过程中积累的一些实战经验,希望帮助你快速定位问题甚至在编码阶段就规避这些问题。

Memcheck 可以检查哪些内存错误?

Memcheck 可以检查 c/c++ 程序中常见的以下问题:

  1. 内存泄漏,包括进程运行过程中的泄漏和进程结束前的泄漏。
  2. 访问不应该访问的内存,即内存非法读写。
  3. 变量未初始化,即使用未定义的值。
  4. 不正确的释放堆内存,比如 double free 或者 malloc/new/new[] 与 free/delete/delete[] 不匹配。
  5. 内存块重叠,比如使用 memcpy 函数时源地址和目标地址发生重叠。
  6. 向内存分配函数的 size 参数传递非法值(fishy value),比如,负值。

其中,问题 1 中的内存泄漏一般是比较好定位与解决的,但是作者在实际项目开发中遇到过 still reachable 错误掩盖 definitely lost 错误的情况,这就加大了定位内存泄漏点的难度。问题 2 和 3 属于出现频率较高的一类内存错误,它们往往会引发程序 crash,这类错误必须要高度重视,且一定要解决。问题 4、5、6 也属于典型的内存错误,使用 Memcheck 可以很快的定位并解决这些问题。

对于 c/c++ 开发者来说,如果不能及时发现并消除这些内存隐患,那么,偶尔的 crash、难以诊断的 coredump 将会是挥之不去的噩梦。而且这些内存问题可能很难通过一己之力去定位,尤其是当程序的代码量庞大、逻辑抽象且复杂的时候,更是会让人焦头烂额。此时,Memcheck 就是辅助我们解决这堆内存问题的神器。

使用 Memcheck 解决问题的原则

当使用 Memcheck 工具输出程序的内存检查报告后,我们该如何着手去解决报告中的问题呢?作者根据长期使用积累的经验,总结了如下四个原则。

原则 1,内存非法读写错误一定要解决

这类错误在检查报告中以 Invalid read/write of size x 的格式输出。这类错误出现的场景主要有三种:

  1. 动态分配的内存已经被释放,然而开发者还在对这块无效的内存进行读写操作。

比如悬挂指针,即基类指针指向的子对象已经被释放,然而却继续使用该基类指针调用其方法。

  1. 动态分配的内存未被释放,然而访问这块内存发生越界。

比如拷贝字符串时忘记结尾的字符 0。

比如 memcpy(dst, src, len);,src 内存大小为 1024 B,然而 len 的值为 1025。

  1. 访问栈空间越界(即堆栈溢出)

比如对数组的越界访问。

其中,场景 1 的出现频率较高。因此,当我们处理 Invalid read/write 这类内存读写错误时,一个较为高效的解决思路是:首先要考虑的是非法读写的 block(内存块)是否在读写之前已经因为程序的某些异常处理被释放了,然后仔细的审查代码来验证这种可能性。如果排除了内存释放的可能,我们再看是否存在内存访问越界的可能,然后继续去验证。

在这个过程中,我们要充分阅读 Memcheck 输出的 Invalid read/write 的详细信息。比如,非法读写的内存块是在哪里分配的?在哪里释放的?又是在哪里非法读写的? 将这些线索结合到具体的项目代码中,帮助我们更高效的解决问题。

忽略这类错误将会给自己的程序带来巨大的隐患,最坏的结果是程序 crash,这对于服务器来说是致命的。

记得有一次使用 c++11 的范围循环语法遍历删除 map 中的元素,Memcheck 检查出了红黑树节点写内存错误。当时以为错误出现在 STL 库底层,且程序改动很小,便忽略了这个错误,熟不知底层的错误正是由于上层代码引起。后来在压测中发现程序频繁 crash,正是因为该错误导致。幸亏当时服务程序没有上线,否则后果不敢想象。所以,这类错误一定要解决,作为服务端开发者,再谨慎也不为过

最后,我们来演示一下这类错误,代码如下:

void foo() {  char* buffer = (char*)malloc(5);  strcpy(buffer, "01234");  cout << "buffer[5]="       << buffer[5] << endl;  free(buffer);}

在 foo 函数中动态分配了 5 个字节大小的内存块,随后拷贝字符串 "01234" 到这块内存,但是忽略了字符串的结尾字符 0,最终将 6 字节大小的字符串写入到 5 字节大小的内存空间,导致内存写越界,Memcheck 报错为 Invalid write of size 2。

最后一行代码在打印 buffer[5] 时发生内存读越界,即字符数组越界访问,Memcheck 报错为 Invalid read of size 1。

这里只演示了部分内存非法读写的场景,其它

导读

Valgrind 最为开发者熟知和广泛使用的工具莫过于 Memcheck,它是检查 c/c++ 程序内存错误的神器,报告结果非常之精准。

本文主要分享作者在使用该神器解决内存问题的过程中积累的一些实战经验,希望帮助你快速定位问题甚至在编码阶段就规避这些问题。

Memcheck 可以检查哪些内存错误?

Memcheck 可以检查 c/c++ 程序中常见的以下问题:

  1. 内存泄漏,包括进程运行过程中的泄漏和进程结束前的泄漏。
  2. 访问不应该访问的内存,即内存非法读写。
  3. 变量未初始化,即使用未定义的值。
  4. 不正确的释放堆内存,比如 double free 或者 malloc/new/new[] 与 free/delete/delete[] 不匹配。
  5. 内存块重叠,比如使用 memcpy 函数时源地址和目标地址发生重叠。
  6. 向内存分配函数的 size 参数传递非法值(fishy value),比如,负值。

其中,问题 1 中的内存泄漏一般是比较好定位与解决的,但是作者在实际项目开发中遇到过 still reachable 错误掩盖 definitely lost 错误的情况,这就加大了定位内存泄漏点的难度。问题 2 和 3 属于出现频率较高的一类内存错误,它们往往会引发程序 crash,这类错误必须要高度重视,且一定要解决。问题 4、5、6 也属于典型的内存错误,使用 Memcheck 可以很快的定位并解决这些问题。

对于 c/c++ 开发者来说,如果不能及时发现并消除这些内存隐患,那么,偶尔的 crash、难以诊断的 coredump 将会是挥之不去的噩梦。而且这些内存问题可能很难通过一己之力去定位,尤其是当程序的代码量庞大、逻辑抽象且复杂的时候,更是会让人焦头烂额。此时,Memcheck 就是辅助我们解决这堆内存问题的神器。

使用 Memcheck 解决问题的原则

当使用 Memcheck 工具输出程序的内存检查报告后,我们该如何着手去解决报告中的问题呢?作者根据长期使用积累的经验,总结了如下四个原则。

原则 1,内存非法读写错误一定要解决

这类错误在检查报告中以 Invalid read/write of size x 的格式输出。这类错误出现的场景主要有三种:

  1. 动态分配的内存已经被释放,然而开发者还在对这块无效的内存进行读写操作。

比如悬挂指针,即基类指针指向的子对象已经被释放,然而却继续使用该基类指针调用其方法。

  1. 动态分配的内存未被释放,然而访问这块内存发生越界。

比如拷贝字符串时忘记结尾的字符 0。

比如 memcpy(dst, src, len);,src 内存大小为 1024 B,然而 len 的值为 1025。

  1. 访问栈空间越界(即堆栈溢出)

比如对数组的越界访问。

其中,场景 1 的出现频率较高。因此,当我们处理 Invalid read/write 这类内存读写错误时,一个较为高效的解决思路是:首先要考虑的是非法读写的 block(内存块)是否在读写之前已经因为程序的某些异常处理被释放了,然后仔细的审查代码来验证这种可能性。如果排除了内存释放的可能,我们再看是否存在内存访问越界的可能,然后继续去验证。

在这个过程中,我们要充分阅读 Memcheck 输出的 Invalid read/write 的详细信息。比如,非法读写的内存块是在哪里分配的?在哪里释放的?又是在哪里非法读写的? 将这些线索结合到具体的项目代码中,帮助我们更高效的解决问题。

忽略这类错误将会给自己的程序带来巨大的隐患,最坏的结果是程序 crash,这对于服务器来说是致命的。

记得有一次使用 c++11 的范围循环语法遍历删除 map 中的元素,Memcheck 检查出了红黑树节点写内存错误。当时以为错误出现在 STL 库底层,且程序改动很小,便忽略了这个错误,熟不知底层的错误正是由于上层代码引起。后来在压测中发现程序频繁 crash,正是因为该错误导致。幸亏当时服务程序没有上线,否则后果不敢想象。所以,这类错误一定要解决,作为服务端开发者,再谨慎也不为过

最后,我们来演示一下这类错误,代码如下:

void foo() {  char* buffer = (char*)malloc(5);  strcpy(buffer, "01234");  cout << "buffer[5]="       << buffer[5] << endl;  free(buffer);}

在 foo 函数中动态分配了 5 个字节大小的内存块,随后拷贝字符串 "01234" 到这块内存,但是忽略了字符串的结尾字符 0,最终将 6 字节大小的字符串写入到 5 字节大小的内存空间,导致内存写越界,Memcheck 报错为 Invalid write of size 2。

最后一行代码在打印 buffer[5] 时发生内存读越界,即字符数组越界访问,Memcheck 报错为 Invalid read of size 1。

这里只演示了部分内存非法读写的场景,其它

本文标签: 内存初始化错误Valgrind