内存调试技巧

内存调试技巧

2007 年 6 月 21 日

本文将带您了解一些良好的和内存相关的编码实践,以将内存错误保持在控制范围内。内存错误是 C 和 C++ 编程的祸根:它们很普遍,认识其严重性已有二十多年,但始终没有彻底解决,它们可能严重影响应用程序,并且很少有开发团队对其制定明确的管理计划。但好消息是,它们并不怎么神秘。

引言

C 和 C++ 程序中的内存错误非常有害:它们很常见,并且可能导致严重的后果。来自计算机应急响应小组(请参见参考资料)和供应商的许多最严重的安全公告都是由简单的内存错误造成的。自从 70 年代末期以来,C 程序员就一直讨论此类错误,但其影响在 2007 年仍然很大。更糟的是,如果按我的思路考虑,当今的许多 C 和 C++ 程序员可能都会认为内存错误是不可控制而又神秘的顽症,它们只能纠正,无法预防。

但事实并非如此。本文将让您在短时间内理解与良好内存相关的编码的所有本质:

  • 正确的内存管理的重要性
  • 内存错误的类别
  • 内存编程的策略
  • 结束语

正确的内存管理的重要性

存在内存错误的 C 和 C++ 程序会导致各种问题。如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行;如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击。从 1988 年著名的莫里斯蠕虫 攻击到有关 Flash Player 和其他关键的零售级程序的最新安全警报都与缓冲区溢出有关:“大多数计算机安全漏洞都是缓冲区溢出”,Rodney Bates 在 2004 年写道。

在可以使用 C 或 C++ 的地方,也广泛支持使用其他许多通用语言(如 Java、Ruby、Haskell、C#、Perl、Smalltalk 等),每种语言都有众多的爱好者和各自的优点。但是,从计算角度来看,每种编程语言优于 C 或 C++ 的主要优点都与便于内存管理密切相关。与内存相关的编程是如此重要,而在实践中正确应用又是如此困难,以致于它支配着面向对象编程语言、功能性编程语言、高级编程语言、声明性编程语言和另外一些编程语言的所有其他变量或理论。

与少数其他类型的常见错误一样,内存错误还是一种隐性危害:它们很难再现,症状通常不能在相应的源代码中找到。例如,无论何时何地发生内存泄漏,都可能表现为应用程序完全无法接受,同时内存泄漏不是显而易见。

因此,出于所有这些原因,需要特别关注 C 和 C++ 编程的内存问题。让我们看一看如何解决这些问题,先不谈是哪种语言。

内存错误的类别

首先,不要失去信心。有很多办法可以对付内存问题。我们先列出所有可能存在的实际问题:

  • 内存泄漏
  • 错误分配,包括大量增加 free() 释放的内存和未初始化的引用
  • 悬空指针
  • 数组边界违规

这是所有类型。即使迁移到 C++ 面向对象的语言,这些类型也不会有明显变化;无论数据是简单类型还是 C 语言的 struct 或 C++ 的类,C 和 C++ 中内存管理和引用的模型在原理上都是相同的。以下内容绝大部分是“纯 C”语言,对于扩展到 C++ 主要留作练习使用。

内存泄漏

在分配资源时会发生内存泄漏,但是它从不回收。下面是一个可能出错的模型(请参见清单 1):


清单 1. 简单的潜在堆内存丢失和缓冲区覆盖

                   void f1(char *explanation)   {       char p1;         p1 = malloc(100);              (void) sprintf(p1,                             "The f1 error occurred because of '%s'.",                             explanation);              local_log(p1);   }      

您看到问题了吗?除非 local_log()free() 释放的内存具有不寻常的响应能力,否则每次对f1 的调用都会泄漏 100 字节。在记忆棒增量分发数兆字节内存时,一次泄漏是微不足道的,但是连续操作数小时后,即使如此小的泄漏也会削弱应用程序。

在实际的 C 和 C++ 编程中,这不足以影响您对 malloc()new 的使用,本部分开头的句子提到了“资源”不是仅指“内存”,因为还有类似以下内容的示例(请参见清单 2)。FILE 句柄可能与内存块不同,但是必须对它们给予同等关注:


清单 2. 来自资源错误管理的潜在堆内存丢失

                   int getkey(char *filename)   {       FILE *fp;       int key;         fp = fopen(filename, "r");       fscanf(fp, "%d", &key);       return key;          }     

fopen 的语义需要补充性的 fclose。在没有 fclose() 的情况下,C 标准不能指定发生的情况时,很可能是内存泄漏。其他资源(如信号量、网络句柄、数据库连接等)同样值得考虑。

内存错误分配

错误分配的管理不是很困难。下面是一个示例(请参见清单 3):


清单 3. 未初始化的指针

                   void f2(int datum)   {       int *p2;                    /* Uh-oh!  No one has initialized p2. */              *p2 = datum;          ...          }      

关于此类错误的好消息是,它们一般具有显著结果。在 AIX® 下,对未初始化指针的分配通常会立即导致 segmentation fault 错误。它的好处是任何此类错误都会被快速地检测到;与花费数月时间才能确定且难以再现的错误相比,检测此类错误的代价要小得多。

在此错误类型中存在多个变种。free() 释放的内存比 malloc() 更频繁(请参见清单 4):


清单 4. 两个错误的内存释放

                   /* Allocate once, free twice. */   void f3()   {       char *p;         p = malloc(10);        ...              free(p);        ...              free(p);          }            /* Allocate zero times, free once. */   void f4()   {       char *p;                    /* Note that p remains uninitialized here. */       free(p);   }      

这些错误通常也不太严重。尽管 C 标准在这些情形中没有定义具体行为,但典型的实现将忽略错误,或者快速而明确地对它们进行标记;总之,这些都是安全情形。

悬空指针

悬空指针比较棘手。当程序员在内存资源释放后使用资源时会发生悬空指针(请参见清单 5):


清单 5. 悬空指针

                         void f8()          {      struct x *xp;        xp = (struct x *) malloc(sizeof (struct x));      xp.q = 13;      ...      free(xp);      ...          /* Problem!  There's no guarantee that      the memory block to which xp points      hasn't been overwritten. */      return xp.q;         }      

传统的“调试”难以隔离悬空指针。由于下面两个明显原因,它们很难再现:

  • 即使影响提前释放内存范围的代码已本地化,内存的使用仍然可能取决于应用程序甚至(在极端情况下)不同进程中的其他执行位置。
  • 悬空指针可能发生在以微妙方式使用内存的代码中。结果是,即使内存在释放后立即被覆盖,并且新指向的值不同于预期值,也很难识别出新值是错误值。

悬空指针不断威胁着 C 或 C++ 程序的运行状态。

数组边界违规

数组边界违规十分危险,它是内存错误管理的最后一个主要类别。回头看一下清单 1;如果explanation 的长度超过 80,则会发生什么情况?回答:难以预料,但是它可能与良好情形相差甚远。特别是,C 复制一个字符串,该字符串不适于为它分配的 100 个字符。在任何常规实现中,“超过的”字符会覆盖内存中的其他数据。内存中数据分配的布局非常复杂并且难以再现,所以任何症状都不可能追溯到源代码级别的具体错误。这些错误通常会导致数百万美元的损失。

内存编程的策略

勤奋和自律可以让这些错误造成的影响降至最低限度。下面我们介绍一下您可以采用的几个特定步骤;我在各种组织中处理它们的经验是,至少可以按一定的数量级持续减少内存错误。

编码风格

编码风格是最重要的,我还从没有看到过其他任何作者对此加以强调。影响资源(特别是内存)的函数和方法需要显式地解释本身。下面是有关标头、注释或名称的一些示例(请参见清单 6)。


清单 6. 识别资源的源代码示例

                   /********    * ...    *    * Note that any function invoking protected_file_read()    * assumes responsibility eventually to fclose() its    * return value, UNLESS that value is NULL.    *    ********/   FILE *protected_file_read(char *filename)   {       FILE *fp;         fp = fopen(filename, "r");       if (fp) {    ...       } else {    ...       }       return fp;   }            /*******    * ...    *    * Note that the return value of get_message points to a    * fixed memory location.  Do NOT free() it; remember to    * make a copy if it must be retained ...    *    ********/   char *get_message()   {       static char this_buffer[400];                ...       (void) sprintf(this_buffer, ...);       return this_buffer;          }              /********    * ...    * While this function uses heap memory, and so     * temporarily might expand the over-all memory    * footprint, it properly cleans up after itself.    *     ********/          int f6(char *item1)   {       my_class c1;       int result;              ...       c1 = new my_class(item1);       ...              result = c1.x;       delete c1;       return result;   }   /********    * ...    * Note that f8() is documented to return a value    * which needs to be returned to heap; as f7 thinly    * wraps f8, any code which invokes f7() must be    * careful to free() the return value.    *    ********/   int *f7()   {       int *p;         p = f8(...);       ...       return p;   }      

使这些格式元素成为您日常工作的一部分。可以使用各种方法解决内存问题:

  • 专用库
  • 语言
  • 软件工具
  • 硬件检查器

在这整个领域中,我始终认为最有用并且投资回报率最大的是考虑改进源代码的风格。它不需要昂贵的代价或严格的形式;可以始终取消与内存无关的段的注释,但影响内存的定义当然需要显式注释。添加几个简单的单词可使内存结果更清楚,并且内存编程会得到改进。

我没有做受控实验来验证此风格的效果。如果您的经历与我一样,您将发现没有说明资源影响的策略简直无法忍受。这样做很简单,但带来的好处太多了。

检测

检测是编码标准的补充。二者各有裨益,但结合使用效果特别好。机灵的 C 或 C++ 专业人员甚至可以浏览不熟悉的源代码,并以极低的成本检测内存问题。通过少量的实践和适当的文本搜索,您能够快速验证平衡的*alloc()free() 或者 newdelete 的源主体。人工查看此类内容通常会出现像清单 7 中一样的问题。


清单 7. 棘手的内存泄漏

                   static char *important_pointer = NULL;   void f9()   {       if (!important_pointer)     important_pointer = malloc(IMPORTANT_SIZE);              ...       if (condition)        /* Ooops!  We just lost the reference            important_pointer already held. */    important_pointer = malloc(DIFFERENT_SIZE);              ...          }     

如果 condition 为真,简单使用自动运行时工具不能检测发生的内存泄漏。仔细进行源分析可以从此类条件推理出证实正确的结论。我重复一下我写的关于风格的内容:尽管大量发布的内存问题描述都强调工具和语言,对于我来说,最大的收获来自“软的”以开发人员为中心的流程变更。您在风格和检测上所做的任何改进都可以帮助您理解由自动化工具产生的诊断。

静态的自动语法分析

当然,并不是只有人类才能读取源代码。您还应使静态语法分析 成为开发流程的一部分。静态语法分析是 lint严格编译 和几种商业产品执行的内容:扫描编译器接受的源文本和目标项,但这可能是错误的症状。

希望让您的代码无 lint。尽管 lint 已过时,并有一定的局限性,但是,没有使用它(或其较高级的后代)的许多程序员犯了很大的错误。通常情况下,您能够编写忽略lint 的优秀的专业质量代码,但努力这样做的结果通常会发生重大错误。其中一些错误影响内存的正确性。与让客户首先发现内存错误的代价相比,即使对这种类别的产品支付最昂贵的许可费也失去了意义。清除源代码。现在,即使lint 标记的编码可能向您提供所需的功能,但很可能存在更简单的方法,该方法可满足 lint,并且比较强键又可移植。

内存库

补救方法的最后两个类别与前三个明显不同。前者是轻量级 的;一个人可以容易地理解并实现它们。另一方面,内存库和工具通常具有较高的许可费用,对部分开发人员来说,它们需要进一步完善和调整。有效地使用库和工具的程序员是理解轻量级的静态 方法的人员。可用的库和工具给人的印象很深:其作为组的质量很高。但是,即使最优秀的编程人员也可能会被忽略内存管理基本原则的非常任性的编程人员搅乱。据我观察,普通的编程人员在尝试利用内存库和工具进行隔离工作时也只能感到灰心。

由于这些原因,我们催促 C 和 C++ 程序员为解决内存问题先了解一下自己的源。在这完成之后,才去考虑库。

使用几个库能够编写常规的 C 或 C++ 代码,并保证改进内存管理。Jonathan Bartlett 在 developerWorks 的 2004 评论专栏中介绍了主要的候选项,可以在下面的参考资料部分获得。库可以解决多种不同的内存问题,以致于直接对它们进行比较是非常困难的;这方面的常见主题包括垃圾收集智能指针智能容器。大体上说,库可以自动进行较多的内存管理,这样程序员可以犯更少的错误。

我对内存库有各种感受。他们在努力工作,但我看到他们在项目中获得的成功比预期要小,尤其在 C 方面。我尚未对这些令人失望的结果进行仔细分析。例如,业绩应该与相应的手动 内存管理一样好,但是这是一个灰色区域——尤其在垃圾收集库处理速度缓慢的情况下。通过这方面的实践得出的最明确的结论是,与 C 关注的代码组相比,C++ 似乎可以较好地接受智能指针。

内存工具

开发真正基于 C 的应用程序的开发团队需要运行时内存工具作为其开发策略的一部分。已介绍的技术很有价值,而且不可或缺。在您亲自尝试使用内存工具之前,其质量和功能您可能还不了解。

本文主要讨论了基于软件的内存工具。还有硬件内存调试器;在非常特殊的情况下(主要是在使用不支持其他工具的专用主机时)才考虑它们。

市场上的软件内存工具包括专有工具(如 IBM Rational® Purify 和 Electric Fence)和其他开放源代码工具。其中有许多可以很好地与 AIX 和其他操作系统一起使用。

所有内存工具的功能基本相同:构建可执行文件的特定版本(很像在编译时通过使用 -g 标记生成的调试版本)、练习相关应用程序和研究由工具自动生成的报告。请考虑如清单 8 所示的程序。


清单 8. 示例错误

                   int main()   {       char p[5];       strcpy(p, "Hello, world.");       puts(p);   }      

此程序可以在许多环境中“运行”,它编译、执行并将“Hello, world.\n”打印到屏幕。使用内存工具运行相同应用程序会在第四行产生一个数组边界违规的报告。在了解软件错误(将十四个字符复制到了只能容纳五个字符的空间中)方面,这种方法比在客户处查找错误症状的花费小得多。这是内存工具的功劳。

结束语

作为一名成熟的 C 或 C++ 程序员,您认识到内存问题值得特别关注。通过制订一些计划和实践,可以找到控制内存错误的方法。学习内存使用的正确模式,快速发现可能发生的错误,使本文介绍的技术成为您日常工作的一部分。您可以在开始时就消除应用程序中的症状,否则可能要花费数天或数周时间来调试。

共享本文……

digg请 Digg 该故事
del.icio.us发布到 del.icio.us
SlashdotSlashdot 一下!


参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 计算机应急响应团队:计算机应急响应小组是“联邦政府资助的研发中心”,通过该中心以及许多其他活动发布关于specific software vulnerabilities 的技术计算机安全警报。

  • Why do good programmers follow bad practices?:这是助理教授 Rodney Bates 为ACM Queue 撰写的一篇关于 C 编程和缓冲区溢出的文章。

  • "内存管理内幕"(developerWorks,2004 年 11 月):概述了对 Linux® 程序员有用的内存管理技术,主要适用于 C 语言,但是也适用于其他语言。

  • Rational Purify:学习更多关于此主要专有内存工具的内容。

  • Coverity, Incorporated:此站点提供产品和服务,而且与 C 和 C++ 的静态源代码分析有关。

  • Memory hygiene in C and C++, Part 2:Commercial tools:这是我在 2004 年撰写的一篇文章。我还维护personal notes on memory debuggers 的网页。

  • AIX and UNIX:AIX and UNIX developerWorks专区提供了大量与 AIX 系统管理的所有方面相关并扩展您的 UNIX 技能的信息。

  • New to AIX and UNIX:访问 New to AIX and UNIX 页面以了解更多关于 AIX 和 UNIX 的内容。

  • AIX 5L? Wiki:AIX 相关技术信息的协作环境。

  • 查看 Cameron Laird 撰写的其他文章和教程。
    • 整个 developerWorks

  • 按主题搜索“AIX and UNIX”库:
    • 系统管理
    • 应用程序开发
    • 性能
    • 移植
    • 安全性
    • 提示
    • 工具和实用程序
    • Java technology
    • Linux
    • 开放源代码

  • Safari 书店:访问这个电子参考库以查找特定的技术资源。

  • developerWorks 技术事件和网络广播:了解最新的 developerWorks 技术事件和网络广播。

  • Podcasts:收听 Podcast 并与 IBM 技术专家保持同步。


获得产品和技术

  • IBM 试用软件:使用 IBM 试用软件开发您的下一个项目,可直接从 developerWorks 下载这些试用软件。

讨论

  • 参与 developerWorks blogs,从而加入到 developerWorks 社区中来。

  • 参与“AIX and UNIX”论坛:
    • AIX 5L——技术论坛
    • AIX for Developers 论坛
    • 集群系统管理
    • IBM Support Assistant
    • 性能工具——技术
    • 虚拟化——技术
    • 更多“AIX and UNIX”论坛

关于作者

 

Cameron Laird 是 developerWorks 长期投稿者和前专栏作家。他经常编写关于促进其公司应用程序开发的开源项目的文章,主要关注可靠性和安全性。


转载于:https://www.cnblogs.com/ajian005/archive/2012/10/30/2753642.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296449.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学课本上的几大变态之处

全世界只有3.14 % 的人关注了爆炸吧知识数学课本上的几大变态--完--

使用Redis set 解决数据的唯一性问题

前言最近遇到一个问题,就是接收第三方数据的时候,类似这种直播数据,由于业务的缘故,导致对方给的数据每次都是全量的,而且请求很频繁,有时候一秒好几十次。直播数据一般都是刷刷刷的,这个大家或…

mysql集群从节点无法启动_一次galera cluster集群故障节点无法启动问题排查

现象环境:Server version: 10.0.25-MariaDB-wsrep MariaDB Server, wsrep_25.13.raf7f02e配置文件:[rootnode-23 mariadb]# more /etc/my.cnf[mysqld]server_id3bind_address node-23port 3306datadir/var/lib/mysqllog-error/var/log/mariadb/mariadb…

webform 页面传值的方法总结

ASP.NET页面之间传递值的几种方式 页面传值是学习asp.net初期都会面临的一个问题,总的来说有页面传值、存储对象传值、ajax、类、model、表单等。但是一般来说,常用的较简单有QueryString,Session,Cookies,Application…

iNeuOS工业互联网操作系统,智慧用电测控应用案例

目 录1. 概述... 22. 系统部署结构... 23. 用电测控终端... 34. 系统应用介绍... 61. 概述通过物联网技术对引发电气火灾的主要因素(导线温度、电流和漏电流等)进行不间断的数据跟踪与统计分析,实时发现电气线路和用电设备存在的安全隐…

出现了!豆瓣最高9.9分,2020年最值得看的美剧!你居然还没看过?【内附资源】...

全世界只有3.14 % 的人关注了爆炸吧知识在调性普遍黄暴烧脑的美剧大流中,《This is us》没有大牌主演,没有炫酷特效,却让观众集体沦陷,被称为5年难得一见的美剧。有人说,这是「有生之年看过的最温柔的美剧」。但它取得…

C# 修改配置文件进行窗体logo切换

01—前言:题外的话大家可能发现这个号现在原创越来越少了,其实小编并没有放弃持续更新,只是把一手原创放到了 【dotnet编程大全】这个号了,那个号目前原创主要更新的是wpf mvvm方面的知识,框架用的Caliburn.Micro&…

数学有趣地超乎你的想象

全世界只有3.14 % 的人关注了爆炸吧知识说起数学你是拒绝还是喜欢看完这一组,对于数学他的震撼、霸气、美来感受下哇1三角形内角和为1802多边形外角和为360(图来源于可乐学习)3怎样将一个正三角形剪拼成正方形?4怎样把两正方形剪拼…

使用C#像google/zx一样编写脚本

google/zxzx是谷歌开源的一个能够帮助开发者快速编写脚本的工具,它使用JavaScript作为编程语言。示例脚本如下:#!/usr/bin/env zxawait $cat package.json | grep namelet branch await $git branch --show-current await $dep deploy --branch${branch…

redis 查询缓存_Redis缓存总结:淘汰机制、缓存雪崩、数据不一致....

在实际的工作项目中, 缓存成为高并发、高性能架构的关键组件 ,那么Redis为什么可以作为缓存使用呢?首先可以作为缓存的两个主要特征:在分层系统中处于内存/CPU具有访问性能良好,缓存数据饱和,有良好的数据淘…

5部适合学英语的动画电影,快和孩子一起看!

全世界只有3.14 % 的人关注了爆炸吧知识今天我们与大家分享5部非常适合小学生学习英语的动画电影,家长们可依据不同类别和主题为孩子挑选喜欢的影片,在家陪孩子一起观看。文末可免费领取哦~01 《丁丁历险记》讲述的是一天丁丁买了一只古老的船模送给船长…

OC面向对象—封装

OC面向对象—封装 一、面向对象和封装 面向对象的三大特性:封装(成员变量)、继承和多态 在OC语言中,使用interface和implementation来处理类。 interface就好像暴露在外面的时钟表面,像外界提供展示以及接口。implemen…

10张让你大脑崩溃的图,敢接受挑战吗?

全世界只有3.14 % 的人关注了爆炸吧知识快睡了吧?来做一组视觉游戏~一些人热爱挑战各种错觉,如果你也是这类型图片的粉丝,这10张图片会让你非常过瘾!入门篇【挑战一】在这张图片中,你能看到几个红球?5个&am…

Source Generator 单元测试

Source Generator 单元测试IntroSource Generator 是 .NET 5.0 以后引入的一个在编译期间动态生成代码的一个机制,介绍可以参考 C# 强大的新特性 Source GeneratorGetStarted使用起来还算比较简单的,我平时一般用 xunit,所以下面的示例也是使…

又一个中国男人荣获巨奖!拿奖拿的手软,却坦言“我对诺奖没有兴趣”...

全世界只有3.14 % 的人关注了爆炸吧知识获得诺奖似乎只是时间问题2020年9月10日,2021年科学突破奖( BREAKTHROUGH PRIZES)正式公布。来自中国香港的科学家卢煜明获得了生命科学科学突破奖,华人数学家孙崧获得了数学新视野奖。前几…

ffbe攻略站_最终幻想勇气启示录ffbe兵员强化攻略

最终幻想勇气启示录兵员如何强化?兵员强化后有哪些加强?来看看9k9k小编带来的最终幻想勇气启示录ffbe兵员强化攻略。在兵员选栏中,我们可以看到有强化兵员这一选项,在这里面,我们可以选择兵员进行强化,强化…

不止命令行!自定义VS生成事件

前言在VS中打开项目属性,选择“生成事件”选项卡。在“生成前事件命令行”或“生成后事件命令行”文本框中可以输入任何命令提示符或.bat文件中有效的命令:但是,有没有可能执行更丰富的命令呢?生成事件的本质上面设置的“生成事件…

如果你女朋友不让你看她卸妆......

1 如果你女朋友不让你看她卸妆▼2 扫地机器人的正确用法(图源网络,侵删)▼3 来比个心(素材来源网络,侵删)▼4 精彩攻防战▼5 那些吃辣条的小学生长大了...▼6 人生的道路上有时候也要回头看看▼7 先礼…

[9月29日的脚本] 枚举SharePoint列表(PowerShell)

脚本下载: SPListEnumerator.zip http://gallery.technet.microsoft.com/scriptcenter/SPListEnumerator-PowerShell-b0ce0b9f 本脚本通过一个“大型”列表或者是文档库来枚举并为相关项提供信息。 在SharePoint(2007版和2010版)中,我们有一个…

在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务

在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务https://procodeguide.com/programming/polly-in-aspnet-core/在本文中,我们将了解如何在微服务中实现弹性容错,即在 ASP.NET Core 中使用 Polly 构建弹性微服务(Web API&#xff0…