研讨linux下C/C++,GTK,Shell,Oracle
(转载)C 语言最大难点揭秘
上一篇 / 下一篇 2008-03-23 13:45:02 / 个人分类:C/C++
查看( 604 ) /
评论( 17 )
本文将带您了解一些良好的和内存相关的编码实践,以将内存错误保持在控制范围内。内存错误是 C 和 C++ 编程的祸根:它们很普遍,认识其严重性已有二十多年,但始终没有彻底解决,它们可能严重影响应用程序,并且很少有开发团队对其制定明确的管理计划。但好消息是,它们并不怎么神秘。
9B2F/peE yz Zae6Q0
q$k'WA/v]'A0 C 和 C++ 程序中的内存错误非常有害:它们很常见,并且可能导致严重的后果。来自计算机应急响应小组(请参见参考资料)和供应商的许多最严重的安全公告都是由简单的内存错误造成的。自从 70 年代末期以来,C 程序员就一直讨论此类错误,但其影响在 2007 年仍然很大。更糟的是,如果按我的思路考虑,当今的许多 C 和 C++ 程序员可能都会认为内存错误是不可控制而又神秘的顽症,它们只能纠正,无法预防。Linux宝库/?} WX;H#x$m7u0U$bzt
0]:T f IpUv0但事实并非如此。本文将让您在短时间内理解与良好内存相关的编码的所有本质:Linux宝库c/J&TRNJ+_
-]0`9^.vvc3OX0正确的内存管理的重要性
1`g`,K_UA&wQ0内存错误的类别
+j.n@9b K9O0内存编程的策略Linux宝库;upQ6k?,cm
Linux宝库9HN \-Yle
正确的内存管理的重要性
eYA'_*L1}_0
b8^n? s0存在内存错误的 C 和 C++ 程序会导致各种问题。如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行;如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击。从 1988 年著名的莫里斯蠕虫 攻击到有关 Flash Player 和其他关键的零售级程序的最新安全警报都与缓冲区溢出有关:“大多数计算机安全漏洞都是缓冲区溢出”,Rodney Bates 在 2004 年写道。
.j9j7j5^z@Z Q['HM0
?swo_8dr0在可以使用 C 或 C++ 的地方,也广泛支持使用其他许多通用语言(如 Java?、Ruby、Haskell、C#、Perl、Smalltalk 等),每种语言都有众多的爱好者和各自的优点。但是,从计算角度来看,每种编程语言优于 C 或 C++ 的主要优点都与便于内存管理密切相关。与内存相关的编程是如此重要,而在实践中正确应用又是如此困难,以致于它支配着面向对象编程语言、功能性编程语言、高级编程语言、声明性编程语言和另外一些编程语言的所有其他变量或理论。Linux宝库IYpT4q#e/K.g
Linux宝库p.^w#W3EVf}
与少数其他类型的常见错误一样,内存错误还是一种隐性危害:它们很难再现,症状通常不能在相应的源代码中找到。例如,无论何时何地发生内存泄漏,都可能表现为应用程序完全无法接受,同时内存泄漏不是显而易见。Linux宝库el/Xs+N&L
Linux宝库3yUfh?5s(t_
因此,出于所有这些原因,需要特别关注 C 和 C++ 编程的内存问题。让我们看一看如何解决这些问题,先不谈是哪种语言。Linux宝库8E(@d5ME/J#ro
@N-Gds*KM0内存错误的类别
bwII(HN+Q0Linux宝库 c5a8i'Y3@-zi&l
首先,不要失去信心。有很多办法可以对付内存问题。我们先列出所有可能存在的实际问题:
0m)?"i,Yg pvZ0Linux宝库CN"@lq?-h]q
内存泄漏
d)|kKm(d/zn0错误分配,包括大量增加 free() 释放的内存和未初始化的引用
8W dM7bspkW0悬空指针Linux宝库$d0])v?7I:q,t&@
数组边界违规
*}8?6C @M'Tw0这是所有类型。即使迁移到 C++ 面向对象的语言,这些类型也不会有明显变化;无论数据是简单类型还是 C 语言的 struct 或 C++ 的类,C 和 C++ 中内存管理和引用的模型在原理上都是相同的。以下内容绝大部分是“纯 C”语言,对于扩展到 C++ 主要留作练习使用。
xnv%o){wq0Linux宝库bN$I&Eq:r}
内存泄漏Linux宝库'l(n2KDq)b
Linux宝库pL-]({Cu:vt"w^E
在分配资源时会发生内存泄漏,但是它从不回收。下面是一个可能出错的模型(请参见清单 1):
-gT#si1Zm8|0
!yT A"Pd v#mn0Linux宝库ep HQ7w@$Q
清单 1. 简单的潜在堆内存丢失和缓冲区覆盖
kdO.SL/j.v0
Te3]*u!y*GJ0在实际的 C 和 C++ 编程中,这不足以影响您对 malloc() 或 new 的使用,本部分开头的句子提到了“资源”不是仅指“内存”,因为还有类似以下内容的示例(请参见清单 2)。FILE 句柄可能与内存块不同,但是必须对它们给予同等关注:Linux宝库4Yf4Q{,qB^
Linux宝库:CB*a/t\N~
+D@nF EEcL:En1X0清单 2. 来自资源错误管理的潜在堆内存丢失
4S??yo"X%c0Linux宝库*~9H|ar'p.O
内存错误分配
us/`0q `7E0
0?,xltt6v;ur0错误分配的管理不是很困难。下面是一个示例(请参见清单 3):
2C0Sz1~ D}0在此错误类型中存在多个变种。free() 释放的内存比 malloc() 更频繁(请参见清单 4):Linux宝库A_ t9w;zZ
Linux宝库*d2dB&wq6R
Linux宝库B T d#Dp2Pmby+q
清单 4. 两个错误的内存释放
0z%P$NZ;zrX/d0pwJ0
#]Pm0t&L%A+a0悬空指针Linux宝库"hGu3c I$x
Linux宝库.~*^SLFg,V
悬空指针比较棘手。当程序员在内存资源释放后使用资源时会发生悬空指针(请参见清单 5):Linux宝库L1\F1j:M,TT"`$l5l4L
Xll-Yx,o/e vI-] ~v0Linux宝库.?6FX ih4I
清单 5. 悬空指针
Linux宝库1[B,nhX0igP
即使影响提前释放内存范围的代码已本地化,内存的使用仍然可能取决于应用程序甚至(在极端情况下)不同进程中的其他执行位置。
BF^j2fi#Ej F0悬空指针可能发生在以微妙方式使用内存的代码中。结果是,即使内存在释放后立即被覆盖,并且新指向的值不同于预期值,也很难识别出新值是错误值。Linux宝库9R7X:e d.S&^-P
悬空指针不断威胁着 C 或 C++ 程序的运行状态。
pey+KZ_1vUj0
]B"e(He7t1IkBb;V0数组边界违规
+A!}{6b?5]l0
/c P @mh\l0数组边界违规十分危险,它是内存错误管理的最后一个主要类别。回头看一下清单 1;如果 explanation 的长度超过 80,则会发生什么情况?回答:难以预料,但是它可能与良好情形相差甚远。特别是,C 复制一个字符串,该字符串不适于为它分配的 100 个字符。在任何常规实现中,“超过的”字符会覆盖内存中的其他数据。内存中数据分配的布局非常复杂并且难以再现,所以任何症状都不可能追溯到源代码级别的具体错误。这些错误通常会导致数百万美元的损失。
;B/C5p4b/z%R+S l0
hU'C2syY7ZO0内存编程的策略
\+^5{"d(Jte1x;r!b3t0Linux宝库NTk4Yq,TK!I1g
勤奋和自律可以让这些错误造成的影响降至最低限度。下面我们介绍一下您可以采用的几个特定步骤;我在各种组织中处理它们的经验是,至少可以按一定的数量级持续减少内存错误。
N7h7sFh(N`}I0
r0p qJ? t9vu5_7]t0编码风格
g?f/Z$o4p0Linux宝库a8f0U&i(G3}I%W3`'j;XQ%^
编码风格是最重要的,我还从没有看到过其他任何作者对此加以强调。影响资源(特别是内存)的函数和方法需要显式地解释本身。下面是有关标头、注释或名称的一些示例(请参见清单 6)。Linux宝库9LGx8c3lzgD7PY ~
Linux宝库2J J]8D"zC Xei/|
O$Nh0A"x3{-Q0清单 6. 识别资源的源代码示例
Linux宝库b.OI$Es
专用库
8yD Z^}Q|(GvTu2s0语言Linux宝库 g;B7pN[B.F
软件工具
nt.}7s,fC|~0硬件检查器
| @S j ]0在这整个领域中,我始终认为最有用并且投资回报率最大的是考虑改进源代码的风格。它不需要昂贵的代价或严格的形式;可以始终取消与内存无关的段的注释,但影响内存的定义当然需要显式注释。添加几个简单的单词可使内存结果更清楚,并且内存编程会得到改进。Linux宝库 K O~Tj)F
Linux宝库As _.g1D-m;zjzv4YI!q
我没有做受控实验来验证此风格的效果。如果您的经历与我一样,您将发现没有说明资源影响的策略简直无法忍受。这样做很简单,但带来的好处太多了。Linux宝库/mL0J%us7C)k%ej}
9Z!S!A1_ Xv![,V0检测
J5J1juMxX+c0Linux宝库Wo%{Y%FIo:aiue'o
检测是编码标准的补充。二者各有裨益,但结合使用效果特别好。机灵的 C 或 C++ 专业人员甚至可以浏览不熟悉的源代码,并以极低的成本检测内存问题。通过少量的实践和适当的文本搜索,您能够快速验证平衡的 *alloc() 和 free() 或者 new 和 delete 的源主体。人工查看此类内容通常会出现像清单 7 中一样的问题。
W4z-B0Tu{`*Rx;fA^0Linux宝库y2Vvm(x
Linux宝库8WD-?k$YuqL0]8E K
清单 7. 棘手的内存泄漏
?9| bY4]7bd0
wb:s5{$vz0静态的自动语法分析
$aM i1J h"[0
,sM q%?qT0当然,并不是只有人类才能读取源代码。您还应使静态语法分析 成为开发流程的一部分。静态语法分析是 lint、严格编译 和几种商业产品执行的内容:扫描编译器接受的源文本和目标项,但这可能是错误的症状。Linux宝库iO2HCW*{6jR:H-lJ
M F)}e(v!HY0希望让您的代码无 lint。尽管 lint 已过时,并有一定的局限性,但是,没有使用它(或其较高级的后代)的许多程序员犯了很大的错误。通常情况下,您能够编写忽略 lint 的优秀的专业质量代码,但努力这样做的结果通常会发生重大错误。其中一些错误影响内存的正确性。与让客户首先发现内存错误的代价相比,即使对这种类别的产品支付最昂贵的许可费也失去了意义。清除源代码。现在,即使 lint 标记的编码可能向您提供所需的功能,但很可能存在更简单的方法,该方法可满足 lint,并且比较强键又可移植。
6w;S9h F$^W.Q'?Q"q\u0
SQ7uag|-hy/gk/f0内存库
2MWD*MjV!`0Linux宝库VW~5c(K.JEr]
补救方法的最后两个类别与前三个明显不同。前者是轻量级 的;一个人可以容易地理解并实现它们。另一方面,内存库和工具通常具有较高的许可费用,对部分开发人员来说,它们需要进一步完善和调整。有效地使用库和工具的程序员是理解轻量级的静态 方法的人员。可用的库和工具给人的印象很深:其作为组的质量很高。但是,即使最优秀的编程人员也可能会被忽略内存管理基本原则的非常任性的编程人员搅乱。据我观察,普通的编程人员在尝试利用内存库和工具进行隔离工作时也只能感到灰心。Linux宝库CrH&RaQ!z
a L,d7l8k0由于这些原因,我们催促 C 和 C++ 程序员为解决内存问题先了解一下自己的源。在这完成之后,才去考虑库。
9B2F/peE yz Zae6Q0
q$k'WA/v]'A0 C 和 C++ 程序中的内存错误非常有害:它们很常见,并且可能导致严重的后果。来自计算机应急响应小组(请参见参考资料)和供应商的许多最严重的安全公告都是由简单的内存错误造成的。自从 70 年代末期以来,C 程序员就一直讨论此类错误,但其影响在 2007 年仍然很大。更糟的是,如果按我的思路考虑,当今的许多 C 和 C++ 程序员可能都会认为内存错误是不可控制而又神秘的顽症,它们只能纠正,无法预防。Linux宝库/?} WX;H#x$m7u0U$bzt
0]:T f IpUv0但事实并非如此。本文将让您在短时间内理解与良好内存相关的编码的所有本质:Linux宝库c/J&TRNJ+_
-]0`9^.vvc3OX0正确的内存管理的重要性
1`g`,K_UA&wQ0内存错误的类别
+j.n@9b K9O0内存编程的策略Linux宝库;upQ6k?,cm
Linux宝库9HN \-Yle
正确的内存管理的重要性
eYA'_*L1}_0
b8^n? s0存在内存错误的 C 和 C++ 程序会导致各种问题。如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行;如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击。从 1988 年著名的莫里斯蠕虫 攻击到有关 Flash Player 和其他关键的零售级程序的最新安全警报都与缓冲区溢出有关:“大多数计算机安全漏洞都是缓冲区溢出”,Rodney Bates 在 2004 年写道。
.j9j7j5^z@Z Q['HM0
?swo_8dr0在可以使用 C 或 C++ 的地方,也广泛支持使用其他许多通用语言(如 Java?、Ruby、Haskell、C#、Perl、Smalltalk 等),每种语言都有众多的爱好者和各自的优点。但是,从计算角度来看,每种编程语言优于 C 或 C++ 的主要优点都与便于内存管理密切相关。与内存相关的编程是如此重要,而在实践中正确应用又是如此困难,以致于它支配着面向对象编程语言、功能性编程语言、高级编程语言、声明性编程语言和另外一些编程语言的所有其他变量或理论。Linux宝库IYpT4q#e/K.g
Linux宝库p.^w#W3EVf}
与少数其他类型的常见错误一样,内存错误还是一种隐性危害:它们很难再现,症状通常不能在相应的源代码中找到。例如,无论何时何地发生内存泄漏,都可能表现为应用程序完全无法接受,同时内存泄漏不是显而易见。Linux宝库el/Xs+N&L
Linux宝库3yUfh?5s(t_
因此,出于所有这些原因,需要特别关注 C 和 C++ 编程的内存问题。让我们看一看如何解决这些问题,先不谈是哪种语言。Linux宝库8E(@d5ME/J#ro
@N-Gds*KM0内存错误的类别
bwII(HN+Q0Linux宝库 c5a8i'Y3@-zi&l
首先,不要失去信心。有很多办法可以对付内存问题。我们先列出所有可能存在的实际问题:
0m)?"i,Yg pvZ0Linux宝库CN"@lq?-h]q
内存泄漏
d)|kKm(d/zn0错误分配,包括大量增加 free() 释放的内存和未初始化的引用
8W dM7bspkW0悬空指针Linux宝库$d0])v?7I:q,t&@
数组边界违规
*}8?6C @M'Tw0这是所有类型。即使迁移到 C++ 面向对象的语言,这些类型也不会有明显变化;无论数据是简单类型还是 C 语言的 struct 或 C++ 的类,C 和 C++ 中内存管理和引用的模型在原理上都是相同的。以下内容绝大部分是“纯 C”语言,对于扩展到 C++ 主要留作练习使用。
xnv%o){wq0Linux宝库bN$I&Eq:r}
内存泄漏Linux宝库'l(n2KDq)b
Linux宝库pL-]({Cu:vt"w^E
在分配资源时会发生内存泄漏,但是它从不回收。下面是一个可能出错的模型(请参见清单 1):
-gT#si1Zm8|0
!yT A"Pd v#mn0Linux宝库ep HQ7w@$Q
清单 1. 简单的潜在堆内存丢失和缓冲区覆盖
CODE:Linux宝库"Hd \'`@cqE(p| {
'j"gW.z }C5T%{*i0 void f1(char *explanation)Linux宝库y;_+~pvCd j
}
{Linux宝库\%nW-R0~]tT9} X
char p1;Linux宝库$cG+g+B,n.Cr:^ UP
UI}u)Z0 p1 = malloc(100);Linux宝库u
tGc[q
(void) sprintf(p1,
4l7l+AtzS0 "The f1 error occurred because of '%s'.",Linux宝库z*zA4h NZ
explanation);Linux宝库4G Nf^/P(oKn!C
local_log(p1);Linux宝库K!b2k2WI!n:pT6a
}Linux宝库'fe;I"Y,D
MeKR
您看到问题了吗?除非 local_log() 对 free() 释放的内存具有不寻常的响应能力,否则每次对 f1 的调用都会泄漏 100 字节。在记忆棒增量分发数兆字节内存时,一次泄漏是微不足道的,但是连续操作数小时后,即使如此小的泄漏也会削弱应用程序。kdO.SL/j.v0
Te3]*u!y*GJ0在实际的 C 和 C++ 编程中,这不足以影响您对 malloc() 或 new 的使用,本部分开头的句子提到了“资源”不是仅指“内存”,因为还有类似以下内容的示例(请参见清单 2)。FILE 句柄可能与内存块不同,但是必须对它们给予同等关注:Linux宝库4Yf4Q{,qB^
Linux宝库:CB*a/t\N~
+D@nF EEcL:En1X0清单 2. 来自资源错误管理的潜在堆内存丢失
CODE:Linux宝库j2O@/s8P
Linux宝库/EN!lQKT$L
int getkey(char *filename)
*IW]'XPA!H3_0 {
`7k ]"N.STI0 FILE *fp;
lGh5v O&}&s0 int key;Linux宝库 vSOt zV
7J5GQ"R
|s G0 fp = fopen(filename, "r");Linux宝库
Y.X4Z'TB)r3r
fscanf(fp, "%d", &key);Linux宝库.S3k$Uz`8t
return key;Linux宝库0E9\
Oxw.f(aA`
}
8X:nk1u2G*~'W6p]%C0fopen 的语义需要补充性的 fclose。在没有 fclose() 的情况下,C 标准不能指定发生的情况时,很可能是内存泄漏。其他资源(如信号量、网络句柄、数据库连接等)同样值得考虑。4S??yo"X%c0Linux宝库*~9H|ar'p.O
内存错误分配
us/`0q `7E0
0?,xltt6v;ur0错误分配的管理不是很困难。下面是一个示例(请参见清单 3):
CODE:Linux宝库]fq[ n;w.A
清单 3. 未初始化的指针
SSk`/kd J.c5_;B0 Linux宝库Mo$}/X KN7`!LEX
void f2(int datum)
]kk"M)bT0 {
f-a2wZHc0 int *p2;
^K
eY%Xo\-RW4Q0
+]*yp"t Tt0GOIU0 /* Uh-oh! No one has initialized p2. */
LL hh hxw(v^0 *p2 = datum;
7}m
u8I-EmS@J0 ...Linux宝库*Q)XwWHdks
}关于此类错误的好消息是,它们一般具有显著结果。在 AIX? 下,对未初始化指针的分配通常会立即导致 segmentation fault 错误。它的好处是任何此类错误都会被快速地检测到;与花费数月时间才能确定且难以再现的错误相比,检测此类错误的代价要小得多。Linux宝库;fOi)jiF5ay2C0Sz1~ D}0在此错误类型中存在多个变种。free() 释放的内存比 malloc() 更频繁(请参见清单 4):Linux宝库A_ t9w;zZ
Linux宝库*d2dB&wq6R
Linux宝库B T d#Dp2Pmby+q
清单 4. 两个错误的内存释放
CODE:Linux宝库hP`'Z/f%HG
4V0fgN9EM;twn^H0 /* Allocate once, free twice. */
(| P7d/j/n \b0 void f3()
4hYTI"C8`'xI0 {Linux宝库Wlh7E)~ L*[]'v
char *p;Linux宝库$`1F'Rw^g,K:o
Linux宝库%y,e6c#mS buE gh
p = malloc(10);
2u@*ke/[sj0 ...Linux宝库"|S!ucD7AEo
free(p);
/E)\Yw
P)U;?0 ...Linux宝库3`+Mg)j1TX/Q
free(p);
X:Y*e9@e+I0 }
2|&U;Q2S2d"\h
L0Linux宝库?o'z t9U
/* Allocate zero times, free once. */
;{
f$O$Xz5o%E
t9T0 void f4()Linux宝库e6R&Xp^a
{
/E1l0E\$~/a,Hz
q0 char *p;
eMr%RV+b{0Linux宝库? ?
i6L:u8]
/* Note that p remains uninitialized here. */
5bi$n`!k0 free(p);
r1_K4g}:G2NXN0 }
0X(aZ6QQS0_)B)X0这些错误通常也不太严重。尽管 C 标准在这些情形中没有定义具体行为,但典型的实现将忽略错误,或者快速而明确地对它们进行标记;总之,这些都是安全情形。0z%P$NZ;zrX/d0pwJ0
#]Pm0t&L%A+a0悬空指针Linux宝库"hGu3c I$x
Linux宝库.~*^SLFg,V
悬空指针比较棘手。当程序员在内存资源释放后使用资源时会发生悬空指针(请参见清单 5):Linux宝库L1\F1j:M,TT"`$l5l4L
Xll-Yx,o/e vI-] ~v0Linux宝库.?6FX ih4I
清单 5. 悬空指针
CODE:Linux宝库2_~zf%}
Linux宝库5W+eGs C{!GL)m
void f8()Linux宝库3Q#\6g;M.lT{+W
{
U6E7p
X Ic!yz0 struct x *xp;Linux宝库qwf%K~gI
FE
P
O6g.`m"H0 xp = (struct x *) malloc(sizeof (struct x));Linux宝库!y&|Mo9N1I @
xp.q = 13;Linux宝库,p~c)z'Q2Rd
...Linux宝库(U%y9T5~}
free(xp);
0R f+xP(J g7nE0 ...
wd1~"E0cM7^t%l&Vv0 /* Problem! There's no guarantee thatLinux宝库AjUaGI(o n
the memory block to which xp pointsLinux宝库sd2R5_&?6N$z
w
hasn't been overwritten. */Linux宝库k-vfE4` _E
return xp.q;Linux宝库T1ag2NL-k
}传统的“调试”难以隔离悬空指针。由于下面两个明显原因,它们很难再现:Linux宝库wc$?agNO[ `[Linux宝库1[B,nhX0igP
即使影响提前释放内存范围的代码已本地化,内存的使用仍然可能取决于应用程序甚至(在极端情况下)不同进程中的其他执行位置。
BF^j2fi#Ej F0悬空指针可能发生在以微妙方式使用内存的代码中。结果是,即使内存在释放后立即被覆盖,并且新指向的值不同于预期值,也很难识别出新值是错误值。Linux宝库9R7X:e d.S&^-P
悬空指针不断威胁着 C 或 C++ 程序的运行状态。
pey+KZ_1vUj0
]B"e(He7t1IkBb;V0数组边界违规
+A!}{6b?5]l0
/c P @mh\l0数组边界违规十分危险,它是内存错误管理的最后一个主要类别。回头看一下清单 1;如果 explanation 的长度超过 80,则会发生什么情况?回答:难以预料,但是它可能与良好情形相差甚远。特别是,C 复制一个字符串,该字符串不适于为它分配的 100 个字符。在任何常规实现中,“超过的”字符会覆盖内存中的其他数据。内存中数据分配的布局非常复杂并且难以再现,所以任何症状都不可能追溯到源代码级别的具体错误。这些错误通常会导致数百万美元的损失。
;B/C5p4b/z%R+S l0
hU'C2syY7ZO0内存编程的策略
\+^5{"d(Jte1x;r!b3t0Linux宝库NTk4Yq,TK!I1g
勤奋和自律可以让这些错误造成的影响降至最低限度。下面我们介绍一下您可以采用的几个特定步骤;我在各种组织中处理它们的经验是,至少可以按一定的数量级持续减少内存错误。
N7h7sFh(N`}I0
r0p qJ? t9vu5_7]t0编码风格
g?f/Z$o4p0Linux宝库a8f0U&i(G3}I%W3`'j;XQ%^
编码风格是最重要的,我还从没有看到过其他任何作者对此加以强调。影响资源(特别是内存)的函数和方法需要显式地解释本身。下面是有关标头、注释或名称的一些示例(请参见清单 6)。Linux宝库9LGx8c3lzgD7PY ~
Linux宝库2J J]8D"zC Xei/|
O$Nh0A"x3{-Q0清单 6. 识别资源的源代码示例
CODE:Linux宝库]V C"F4e
Linux宝库,m.ooU"h]B
/********Linux宝库n2K0q5hbd
* ...Linux宝库qL,gy6Nct V*U[w
*Linux宝库8h&zDk5y
* Note that any function invoking protected_file_read()Linux宝库6W;X&T-E'rrm&`mU
* assumes responsibility eventually to fclose() its
2JE9cjbS;n?0 * return value, UNLESS that value is NULL.Linux宝库nA4y`6y%[
J
*
wlZm#PTbuLCn0 ********/
t3r\1n3@8R0 FILE *protected_file_read(char *filename)Linux宝库l;Y%{1Vz
{Linux宝库_)H8h$u1@(g*L!\
FILE *fp;Linux宝库Me6Kf*P|
bwe
Linux宝库M8K4V3e&YN
fp = fopen(filename, "r");
p
h;lze;s y\)X0 if (fp) {
ZK)n!?kN)TrN0 ...
K CCghjZ8h#v&W&I0 } else {Linux宝库[z7Qo(T.m1F
...
a$RND'w{0 }
_,j+Qr(E9k5u,U9b]0 return fp;
s8U7Ka"x+v0 }Linux宝库Sh
cc9K_g
Linux宝库9m1H,Gui
/*******Linux宝库}w7ob&r6?X]
* ...Linux宝库jB%d&Z/U4l3\#tC` `?
*Linux宝库5b0l*?+d5o
v&J9y3U
* Note that the return value of get_message points to a
O
`;p,Q N0 * fixed memory location. Do NOT free() it; remember toLinux宝库hK5x/[+g(Z%M-P
* make a copy if it must be retained ...Linux宝库
Lk3X_
SI3D@
*Linux宝库sX)H
C0T1bt
********/Linux宝库9fw Uj3ltbd
M
char *get_message()Linux宝库6lp/} };R
{Linux宝库$mB
[ Y
Ec(E+{'mf
static char this_buffer[400];
NXVa@a
XH%?v0Linux宝库x(uoS*n(O4G*F
...
7j*g7n{;|
NF0 (void) sprintf(this_buffer, ...);
.l6d"cx#aE7A0 return this_buffer;
^!Qm4pyxhZJ0 }Linux宝库'kIw%D.b,a
Linux宝库|,b|HK~ d
|of|z{G C3a0 /********Linux宝库IoF6M1h#@%K%?
* ...Linux宝库?x'\U}P3fwV
* While this function uses heap memory, and soLinux宝库W.a F2tVX/k!A
* temporarily might expand the over-all memory
|-v8t7Sy6r0~ w0 * footprint, it properly cleans up after itself.Linux宝库;L:qjb(v
*
%F3p/]W
K#Vx0 ********/Linux宝库t%zR9cA8Bx
int f6(char *item1)
{,v5W_y mD_,ed0 {
A0JTlh0 my_class c1;
Ey(AP#Q${5y0 int result;
V|w7Cbk0 ...Linux宝库1nK H8p:}
c1 = new my_class(item1);
+?8b:AA;H"E9FxK0 ...
!\vn&~A#oa0 result = c1.x;Linux宝库|#li7Igw;i5q?k
delete c1;
utKj0F0 return result;Linux宝库
[V9A)fh^
}Linux宝库k-{-F3y}6a"h
R
/********
n3ck2Qt z"c*R0 * ...
S{+P!w?mF
z
wo0 * Note that f8() is documented to return a valueLinux宝库H2Jy+W
e
* which needs to be returned to heap; as f7 thinly
$tb0bM(n aH0 * wraps f8, any code which invokes f7() must beLinux宝库3Y]3]y&`C9\8@
* careful to free() the return value.Linux宝库 ^i8D+b8S5v/js
*Linux宝库$gM)} w'['F5@am
********/Linux宝库lJ A9s0P#i o-G
int *f7()Linux宝库@p(n F&Y @
{
`{ n'BX/D0 int *p;
.z
t/Am$X%~ ]0
a Q5o5f)X0 p = f8(...);Linux宝库8c!n*@^"r lq
...
oWU _6k2q0 return p;Linux宝库D
m@6uui1P+bQ
}Linux宝库8QN.z'Q,jlO1l(dC#aRnr
使这些格式元素成为您日常工作的一部分。可以使用各种方法解决内存问题:Linux宝库VY1r#{JlLinux宝库b.OI$Es
专用库
8yD Z^}Q|(GvTu2s0语言Linux宝库 g;B7pN[B.F
软件工具
nt.}7s,fC|~0硬件检查器
| @S j ]0在这整个领域中,我始终认为最有用并且投资回报率最大的是考虑改进源代码的风格。它不需要昂贵的代价或严格的形式;可以始终取消与内存无关的段的注释,但影响内存的定义当然需要显式注释。添加几个简单的单词可使内存结果更清楚,并且内存编程会得到改进。Linux宝库 K O~Tj)F
Linux宝库As _.g1D-m;zjzv4YI!q
我没有做受控实验来验证此风格的效果。如果您的经历与我一样,您将发现没有说明资源影响的策略简直无法忍受。这样做很简单,但带来的好处太多了。Linux宝库/mL0J%us7C)k%ej}
9Z!S!A1_ Xv![,V0检测
J5J1juMxX+c0Linux宝库Wo%{Y%FIo:aiue'o
检测是编码标准的补充。二者各有裨益,但结合使用效果特别好。机灵的 C 或 C++ 专业人员甚至可以浏览不熟悉的源代码,并以极低的成本检测内存问题。通过少量的实践和适当的文本搜索,您能够快速验证平衡的 *alloc() 和 free() 或者 new 和 delete 的源主体。人工查看此类内容通常会出现像清单 7 中一样的问题。
W4z-B0Tu{`*Rx;fA^0Linux宝库y2Vvm(x
Linux宝库8WD-?k$YuqL0]8E K
清单 7. 棘手的内存泄漏
CODE:Linux宝库 B t*F_1qx qYw
1fso|!QG/^
l0 static char *important_pointer = NULL;Linux宝库$j%PMQD-Wy6I
dA&e
void f9()
z-H*n5KZW%z^Z4|$@0 {
S(s-I
[3}&J0 if (!important_pointer)Linux宝库9[M9fOM
important_pointer = malloc(IMPORTANT_SIZE);Linux宝库lE$? @,[W1L f
...Linux宝库^D8S yc_S
if (condition)Linux宝库6_8TA9BmF"Y]4M;y
/* Ooops! We just lost the referenceLinux宝库/YQ6Ca
V
important_pointer already held. */
m.m*xLQ:aZ5g0 important_pointer = malloc(DIFFERENT_SIZE);Linux宝库4EDM%NV0P\A)r'e
...
/S,M[+vy0 }Linux宝库K,[#S5J;`Vz$x;e
如果 condition 为真,简单使用自动运行时工具不能检测发生的内存泄漏。仔细进行源分析可以从此类条件推理出证实正确的结论。我重复一下我写的关于风格的内容:尽管大量发布的内存问题描述都强调工具和语言,对于我来说,最大的收获来自“软的”以开发人员为中心的流程变更。您在风格和检测上所做的任何改进都可以帮助您理解由自动化工具产生的诊断。?9| bY4]7bd0
wb:s5{$vz0静态的自动语法分析
$aM i1J h"[0
,sM q%?qT0当然,并不是只有人类才能读取源代码。您还应使静态语法分析 成为开发流程的一部分。静态语法分析是 lint、严格编译 和几种商业产品执行的内容:扫描编译器接受的源文本和目标项,但这可能是错误的症状。Linux宝库iO2HCW*{6jR:H-lJ
M F)}e(v!HY0希望让您的代码无 lint。尽管 lint 已过时,并有一定的局限性,但是,没有使用它(或其较高级的后代)的许多程序员犯了很大的错误。通常情况下,您能够编写忽略 lint 的优秀的专业质量代码,但努力这样做的结果通常会发生重大错误。其中一些错误影响内存的正确性。与让客户首先发现内存错误的代价相比,即使对这种类别的产品支付最昂贵的许可费也失去了意义。清除源代码。现在,即使 lint 标记的编码可能向您提供所需的功能,但很可能存在更简单的方法,该方法可满足 lint,并且比较强键又可移植。
6w;S9h F$^W.Q'?Q"q\u0
SQ7uag|-hy/gk/f0内存库
2MWD*MjV!`0Linux宝库VW~5c(K.JEr]
补救方法的最后两个类别与前三个明显不同。前者是轻量级 的;一个人可以容易地理解并实现它们。另一方面,内存库和工具通常具有较高的许可费用,对部分开发人员来说,它们需要进一步完善和调整。有效地使用库和工具的程序员是理解轻量级的静态 方法的人员。可用的库和工具给人的印象很深:其作为组的质量很高。但是,即使最优秀的编程人员也可能会被忽略内存管理基本原则的非常任性的编程人员搅乱。据我观察,普通的编程人员在尝试利用内存库和工具进行隔离工作时也只能感到灰心。Linux宝库CrH&RaQ!z
a L,d7l8k0由于这些原因,我们催促 C 和 C++ 程序员为解决内存问题先了解一下自己的源。在这完成之后,才去考虑库。