推荐给好友 上一篇 | 下一篇

C 语言中的指针和内存泄漏

fp"D8C-jj u在使用 C 语言时,您是否对花时间调试指针内存泄漏问题感到厌倦?如果是这样,那么本文就适合您。您将了解可能导致内存破坏的指针操作类型,您还将研究一些场景,了解要在使用动态内存分配时考虑什么问题。BSD爱好者乐园Ui(i)M!V6UEs/s1a
引言BSD爱好者乐园fu"[LUji

BSD爱好者乐园$C2JZEa'DR

对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏。这些的确是消耗了开发人员大多数调试时间的事项。指针和内存泄漏对某些开发人员来说似乎令人畏惧,但是一旦您了解了指针及其关联内存操作的基础,它们就是您在 C 语言中拥有的最强大工具。BSD爱好者乐园i$n#f uLF5T\.U4FV

;v[\*g2G o:L-u/aF5n本文将与您分享开发人员在开始使用指针来编程前应该知道的秘密。本文内容包括:

d3rL d|bZz d'V

l]%~ j;Jr0@#o导致内存破坏的指针操作类型BSD爱好者乐园0@MsAw
在使用动态内存分配时必须考虑的检查点BSD爱好者乐园f f%w5I7HnW
导致内存泄漏的场景
.W+NQgt+yBFa如果您预先知道什么地方可能出错,那么您就能够小心避免陷阱,并消除大多数与指针和内存相关的问题。
Jyd WV&OE Fr 

5](K&Y,c e.|5FG Q

:q$i2yDL'IJ什么地方可能出错?

'w7k:XR*\:Nr&k,R

LL%k+Z'mE~U有几种问题场景可能会出现,从而可能在完成生成后导致问题。在处理指针时,您可以使用本文中的信息来避免许多问题。

9eLhM[j,s(LX+g6?BSD爱好者乐园H+A*@5KVE

BSD爱好者乐园(s&l&{%|J2M
未初始化的内存

s'U$i*i-Pz.xBSD爱好者乐园"~"e$`5I,RW

在本例中,p 已被分配了 10 个字节。这 10 个字节可能包含垃圾数据,如图 1 所示。BSD爱好者乐园p'|D{!XE

0j[9`p8Whochar *p = malloc ( 10 );
iR {(M#ekg6f
Vu[-GcbJL 图 1. 垃圾数据
^$|:B3{&gs:R1?t:?q 

D'B _PYkWmo%c

c!bGg.G@? Z如果在对这个 p 赋值前,某个代码段尝试访问它,则可能会获得垃圾值,您的程序可能具有不可预测的行为。p 可能具有您的程序从未曾预料到的值。

0jp7s s#Dy9e/\3[.kBSD爱好者乐园`HPd:W"_6hg

良好的实践是始终结合使用 memset 和 malloc,或者使用 calloc。

h*j v%B|;z5GU&YBSD爱好者乐园TVi hB q!Evm_

char *p = malloc (10);memset(p,’\0’,10);
J3xAFo9Z1k BSD爱好者乐园eS+f*X9_han
现在,即使同一个代码段尝试在对 p 赋值前访问它,该代码段也能正确处理 Null 值(在理想情况下应具有的值),然后将具有正确的行为。

%p-`,Ph^ yBSD爱好者乐园(@3Z@R.GE0?


0`G9Lf8gy Ta内存覆盖

-\j0gj w

@*^,NC'mN由于 p 已被分配了 10 个字节,如果某个代码片段尝试向 p 写入一个 11 字节的值,则该操作将在不告诉您的情况下自动从其他某个位置“吃掉”一个字节。让我们假设指针 q 表示该内存。BSD爱好者乐园r#sp'}/S/Mt

BSD爱好者乐园EL;yP`_6k Qo

BSD爱好者乐园_1|;N$o&K:W4j6K
图 2. 原始 q 内容
6X ~4gYJ2B.Es BSD爱好者乐园U%Hka/_k`+H H

-o1W2L)Z/Ao}图 3. 覆盖后的 q 内容BSD爱好者乐园 S+U |;Tg,h
 

MW Y`,Wv2XV

*]u:j&Q+CeT)SP~ u ^结果,指针 q 将具有从未预料到的内容。即使您的模块编码得足够好,也可能由于某个共存模块执行某些内存操作而具有不正确的行为。下面的示例代码片段也可以说明这种场景。BSD爱好者乐园vp3U9j^'tv`

BSD爱好者乐园5N]{d,P

char *name = (char *) malloc(11); // Assign some value to namememcpy ( p,name,11); // Problem begins hereBSD爱好者乐园$x@)q ?xD(u
 BSD爱好者乐园Iw+U%s,L)LK

%VWbC+W2lx#ug A#L[在本例中,memcpy 操作尝试将 11 个字节写到 p,而后者仅被分配了 10 个字节。BSD爱好者乐园h&F#^qi"N V{A

K['Y)p$q5U作为良好的实践,每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉核对。一般情况下,memcpy 函数将是用于此目的的检查点。BSD爱好者乐园F?%MGn.m*Z3v

BSD爱好者乐园"n.Z/p,i"@d6Z u

BSD爱好者乐园-A3f`6] R*w
内存读取越界BSD爱好者乐园q V#O:t$Zo"PB

6a kZ6L-a V-Pq#DN内存读取越界 (overread) 是指所读取的字节数多于它们应有的字节数。这个问题并不太严重,在此就不再详述了。下面的代码提供了一个示例。

K S%S&wYOA@

@%N Oz*N O])T0gi1Xchar *ptr = (char *)malloc(10);char name[20] ;memcpy ( name,ptr,20); // Problem begins here
C/jwby+l.op 

,i6N%Rp@#f

+I2D#i P Dl'`^G#YBSD爱好者乐园eY$p6Y:xy
在本例中,memcpy 操作尝试从 ptr 读取 20 个字节,但是后者仅被分配了 10 个字节。这还会导致不希望的输出。

8oWpz&e]Nl

,uPNn b3w7?
T"^%LD"^N^s9qx内存泄漏BSD爱好者乐园2hMlJD wD*T

9E!mp(? H#n内存泄漏可能真正令人讨厌。下面的列表描述了一些导致内存泄漏的场景。BSD爱好者乐园L:t$x$KI3W [|+sQ

f)AJ[/]-C重新赋值BSD爱好者乐园'F.{"` w{:U-P'Si1x9h
我将使用一个示例来说明重新赋值问题。

kCp:p9^fsBSD爱好者乐园v`K-x}8C+Dq

char *memoryArea = malloc(10);char *newArea = malloc(10);BSD爱好者乐园PRJWsC,n(x9S
 BSD爱好者乐园,Li:V_g!aD

BSD爱好者乐园SuGc @"q

这向如下面的图 4 所示的内存位置赋值。

Vs }1oC8T Gk^BSD爱好者乐园(T7y&Rx7L]E,zS


E8yS&oF"Rr V?图 4. 内存位置BSD爱好者乐园8Ai i@&Sb)Wj*GF
 BSD爱好者乐园 U/}:pAgxG

BSD爱好者乐园&b4Cf8\\]

BSD爱好者乐园 pq8b3QzV
memoryArea 和 newArea 分别被分配了 10 个字节,它们各自的内容如图 4 所示。如果某人执行如下所示的语句(指针重新赋值)…… memoryArea = newArea;
%F Yq:_:W qx.?O BSD爱好者乐园E,{r5Q(gj}

BSD爱好者乐园J_/@/|!J;|5BQo

则它肯定会在该模块开发的后续阶段给您带来麻烦。BSD爱好者乐园z2A3r~;u

2x$S:X,p)?'rn)o[s^在上面的代码语句中,开发人员将 memoryArea 指针赋值给 newArea 指针。结果,memoryArea 以前所指向的内存位置变成了孤立的,如下面的图 5 所示。它无法释放,因为没有指向该位置的引用。这会导致 10 个字节的内存泄漏。

g$z^N/RBSD爱好者乐园p$S6zT.K'v6HxS


7] P#Sw ]#kFU+j图 5. 内存泄漏
d^T#w-Sx ^M 

&m c]7@/`\j@BSD爱好者乐园-bDW&`:`FL@N

在对指针赋值前,请确保内存位置不会变为孤立的。

/Ho/fY&d+@g3bOBSD爱好者乐园y(r*dRI{)s

首先释放父块BSD爱好者乐园9r tQa`}2N~
假设有一个指针 memoryArea,它指向一个 10 字节的内存位置。该内存位置的第三个字节又指向某个动态分配的 10 字节的内存位置,如图 6 所示。BSD爱好者乐园|5[ m\;|wv gc Cn

BSD爱好者乐园z;gn(xP],X


|C,^!f.D)KiBeoT图 6. 动态分配的内存 
~&@:jx'm { a 

C%J c[)iA2B{4aBSD爱好者乐园w9JJ;i5sl*[yQ)?!e

free(memoryArea)
%vx6g5[1B BSD爱好者乐园 FP6{%T+P(\1Q Tx

xz|,iK1M:\
J`.f3}x xm#m\ G如果通过调用 free 来释放了 memoryArea,则 newArea 指针也会因此而变得无效。newArea 以前所指向的内存位置无法释放,因为已经没有指向该位置的指针。换句话说,newArea 所指向的内存位置变为了孤立的,从而导致了内存泄漏。BSD爱好者乐园4yj:j6n_?8B&g?

BSD爱好者乐园QYyv]oH^r

每当释放结构化的元素,而该元素又包含指向动态分配的内存位置的指针时,应首先遍历子内存位置(在此例中为 newArea),并从那里开始释放,然后再遍历回父节点。

R C}/fd

Jk*U5y U/\W这里的正确实现应该为:BSD爱好者乐园0Jx7] qJ#Kt

;Ov x gO ZpDfree( memoryArea->newArea);free(memoryArea);
b/z1i\b b BSD爱好者乐园 RNV:w(E

k6p%i9|(U"mBSD爱好者乐园-}&p,th^1A-L
返回值的不正确处理BSD爱好者乐园M}h S+D0|Mp
有时,某些函数会返回对动态分配的内存的引用。跟踪该内存位置并正确地处理它就成为了 calling 函数的职责。BSD爱好者乐园E0k6RTa:r

BSD爱好者乐园ORV,`m m;Nd}D

char *func ( ){  return malloc(20); // make sure to memset this location to ‘\0’…}void callingFunc ( ){  func ( ); // Problem lies here}
x:k,ngwV BSD爱好者乐园C4^+h-S E:w(X}

2jMAkC2[J
Qy _iF在上面的示例中,callingFunc() 函数中对 func() 函数的调用未处理该内存位置的返回地址。结果,func() 函数所分配的 20 个字节的块就丢失了,并导致了内存泄漏。

'b:V9Uu+S Y3zcV

AXaYuy&B$P$Dr:M
%h9DT1r^i K归还您所获得的BSD爱好者乐园m ?5^+G9q/^JUw

lHM1nT)k-]7F在开发组件时,可能存在大量的动态内存分配。您可能会忘了跟踪所有指针(指向这些内存位置),并且某些内存段没有释放,还保持分配给该程序。BSD爱好者乐园(P)MY9I#g5S} _r

BSD爱好者乐园D[9\gZ-}Y+q2BJ

始终要跟踪所有内存分配,并在任何适当的时候释放它们。事实上,可以开发某种机制来跟踪这些分配,比如在链表节点本身中保留一个计数器(但您还必须考虑该机制的额外开销)。

M['x@WWr

.}(` a7T&WY+p
Q&[+n["~uL}访问空指针BSD爱好者乐园-}i(_vw_Wwy^%D

BSD爱好者乐园6w f.[;|9IO

访问空指针是非常危险的,因为它可能使您的程序崩溃。始终要确保您不是 在访问空指针。BSD爱好者乐园#zqLGX

BSD爱好者乐园cq)_-t{[B-um

BSD爱好者乐园G;|F/n |g7C D"b
总结BSD爱好者乐园2c#Q E]2Q;T

3n$N ?i EGmH%I本文讨论了几种在使用动态内存分配时可以避免的陷阱。要避免内存相关的问题,良好的实践是:BSD爱好者乐园\j2V1q,wR }

BSD爱好者乐园3bb o+G4_T

始终结合使用 memset 和 malloc,或始终使用 calloc。
$@r1a4v}每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉核对。
Nx#g,Qc#th7_~o4V9Q在对指针赋值前,要确保没有内存位置会变为孤立的。
:CzE]Yhy每当释放结构化的元素(而该元素又包含指向动态分配的内存位置的指针)时,都应首先遍历子内存位置并从那里开始释放,然后再遍历回父节点。
v&F/bxB.K$E始终正确处理返回动态分配的内存引用的函数返回值。
m5t[`@oq _^6?A每个 malloc 都要有一个对应的 free。BSD爱好者乐园0iO$^yW0md
确保您不是在访问空指针。

w9Hp6B/Z@*s
[重要提醒]对本篇资料有疑问,请到论坛讨论,尽量使文章准确无误>>>
[版权声明]BSD爱好者乐园站内文章,如来源不是互联网,则均系原创或翻译之作,可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。
TAG: 指针 内存泄漏
 

评分:0

我来说两句

seccode