|
我是老温,一名热爱学习的嵌入式工程师
1 s9 R) M) Y5 a: N( ]; N( h关注我,一起变得更加优秀!1.前言 最近部门不同产品接连出现内存泄漏导致的网上问题,具体表现为单板在现网运行数月以后,因为内存耗尽而导致单板复位现象。
" s" Z+ d ~ w: B* v一方面,内存泄漏问题属于低级错误,此类问题遗漏到现网,影响很坏;另一方面,由于内存泄漏问题很可能导致单板运行固定时间以后就复位,只能通过批量升级才能解决,实际影响也很恶劣。
! ]9 A! M: U$ P. C1 X, [同时,接连出现此类问题,尤其是其中一例问题还是我们老员工修改引入,说明我们不少员工对内存泄漏问题认识还是不够深刻的。4 Z7 {, \; V; W" k& t: ^, S
本文通过介绍内存泄漏问题原理及检视方法,希望后续能够从编码检视环节就杜绝此类问题发生。
# O) H3 D& \, S0 B) h( O说明:预防内存泄漏问题有多种方法,如加强代码检视、工具检测和内存测试等,本文聚集于开发人员能力提升方面。
% b) V1 I+ D0 ?: U U g/ E2.内存泄漏问题原理 2.1堆内存在C代码中的存储方式内存泄漏问题只有在使用堆内存的时候才会出现,栈内存不存在内存泄漏问题,因为栈内存会自动分配和释放。C代码中堆内存的申请函数是malloc,常见的内存申请代码如下:
" p+ a9 V' y+ C2 echar *info = NULL; /**转换后的字符串**/
- k# L/ W# e. Y6 U4 p1 d9 S6 J1 Finfo = (char*)malloc(NB_MEM_SPD_INFO_MAX_SIZE);1 r) x9 f# w7 `7 S* v
if( NULL == info)4 Y' D, O2 b) o# e7 }1 {) Y
{
5 U/ R2 N- K7 G1 v: x+ V (void)tdm_error("malloc error!$ |3 i, l1 o5 E* ^
");* Z( ]( T+ E$ {+ X* Q. y- O
return NB_SA_ERR_HPI_OUT_OF_MEMORY;
& P0 o* `3 X" @3 l; m9 b+ e$ Y. Q}* _: X/ M) p7 n- K% d. l
由于malloc函数返回的实际上是一个内存地址,所以保存堆内存的变量一定是一个指针(除非代码编写极其不规范)。再重复一遍,保存堆内存的变量一定是一个指针,这对本文主旨的理解很重要。当然,这个指针可以是单指针,也可以是多重指针。
0 ~% U% v1 G; Q. a9 Q2 Imalloc函数有很多变种或封装,如g_malloc、g_malloc0、VOS_Malloc等,这些函数最终都会调用malloc函数。6 I) {* Q2 H: X4 R! k8 z3 X
2.2堆内存的获取方法看到本小节标题,可能有些同学有疑惑,上一小节中的malloc函数,不就是堆内存的获取方法吗?的确是,通过malloc函数申请是最直接的获取方法,如果只知道这种堆内存获取方法,就容易掉到坑里了。一般的来讲,堆内存有如下两种获取方法:
P4 X" L! o" c& I方法一:将函数返回值直接赋给指针,一般表现形式如下:& o' R4 `) C# r5 A- L+ ^$ u
char *local_pointer_xx = NULL;
- B0 {! x6 U0 C. E' j. V& ~/ xlocal_pointer_xx = (char*)function_xx(para_xx, …);
6 v" ?% h, K/ }/ s; V# j5 N( f该类涉及到内存申请的函数,返回值一般都指针类型,例如:+ X `$ X" l7 ?
GSList* g_slist_append (GSList *list, gpointer data);
" Q! B; X8 M4 i方法二:将指针地址作为函数返回参数,通过返回参数保存堆内存地址,一般表现形式如下:1 R# E8 r" |% i3 o& k
int ret;
! B$ _' {+ o2 X' ~3 \3 s. Bchar *local_pointer_xx = NULL; /**转换后的字符串**/
& f4 U) z, t- ^ret = (char*)function_xx(..., &local_pointer_xx, ...);
/ D) \, y' M; J该类涉及到内存申请的函数,一般都有一个入参是双重指针,例如:
! N) n, I: u. a! Y! w& b1 o__STDIO_INLINE _IO_ssize_t;$ }* c6 i! M$ z+ W" U7 F
getline (char **__lineptr, size_t *__n, FILE *__stream);' E6 Y- L7 E. p7 m/ ~
前面说通过malloc申请内存,就属于方法一的一个具体表现形式。其实这两类方法的本质是一样的,都是函数内部间接申请了内存,只是传递内存的方法不一样,方法一通过返回值传递内存指针,方法二通过参数传递内存指针。6 |" j' Z, ^& {' l
2.3内存泄漏三要素最常见的内存泄漏问题,包含以下三个要素:9 M: o; W$ P7 q* E$ w& ?
要素一:函数内有局部指针变量定义; g* i. h$ j$ u) M6 o: O$ t5 S
要素二:对该局部指针有通过上一小节中“两种堆内存获取方法”之一获取内存;
( M, Q' Y5 J- A要素三:在函数返回前(含正常分支和异常分支)未释放该内存,也未保存到其它全局变量或返回给上一级函数。
$ d/ q4 r, _( o# o( e# z5 U! s, l2.4内存释放误区稍微使用过C语言编写代码的人,都应该知道堆内存申请之后是需要释放的。但为何还这么容易出现内存泄漏问题呢?一方面,是开发人员经验不足、意识不到位或一时疏忽导致;另一方面,是内存释放误区导致。很多开发人员,认为要释放的内存应该局限于以下两种:* G8 A4 s ^* U0 w
1) 直接使用内存申请函数申请出来的内存,如malloc、g_malloc等;
0 g1 S. ~, Q/ I0 T2)该开发人员熟悉的接口中,存在内存申请的情况,如iBMC的兄弟,都应该知道调用如下接口需要释放list指向的内存:- a4 c9 p9 l( R' X# F5 L
dfl_get_object_list(const char* class_name, GSList **list);
5 T) L$ g# `$ X2 Z' b按照以上思维编写代码,一旦遇到不熟悉的接口中需要释放内存的问题,就完全没有释放内存的意识,内存泄漏问题就自然产生了。$ l; ^9 I) \! Y9 e3 e ?) b
5 c: X7 m1 b/ t* S3.内存泄漏问题检视方法 检视内存泄漏问题,关键还是要养成良好的编码检视习惯。与内存泄漏三要素对应,需) E7 K! U( f- I: K! b8 A, `) {% A
要做到如下三点:
$ b, O" P3 e! N9 P' Y. W0 i1) 在函数中看到有局部指针,就要警惕内存泄漏问题,养成进一步排查的习惯% ^! G( d; j) z( Z) a- ^' ^
2) 分析对局部指针的赋值操作,是否属于前面所说的“两种堆内存获取方法”之一,如果是,就要分析函数返回的指针到底指向啥?是全局数据、静态数据还是堆内存?对于不熟悉的接口,要找到对应的接口文档或源代码分析;又或者看看代码中其它地方对该接口的引用,是否进行了内存释放; j0 B0 z/ O t. @3 e9 h% d8 G
3) 如果确认对局部指针存在内存申请操作,就需要分析该内存的去向,是会被保存在全局变量吗?又或者会被作为函数返回值吗?如果都不是,就需要排查函数所有有”return“的地方,保证内存被正确释放。
5 P p% n/ P% y% L4 y, _+ W _) t4 A
原文:https://my.oschina.net/u/4526289/blog/4539592
8 R% H" Y2 b# t: s% ~3 j7 l
: o0 W0 R# e( ~8 w2 T0 x-END-5 t( |2 k% c+ I& D: u$ t8 `" D3 }
往期推荐:点击图片即可跳转阅读) d4 R9 C( j6 |7 L( u
) m% V1 L2 |6 a2 p* z# ?0 K
* t- o+ K( m$ X$ D) ^) o) N
( I O3 I& }- ]; e B1 s7 _ ; b( |7 Y: q1 L" o: Z
. J: b+ H* ~* y; U
c31kfmchx2264032303722.jpg
! H- v7 B/ S3 j: P8 h # d- f9 y ?5 u4 ?0 I
嵌入式 C 语言,位操作的几种常见用法。5 K6 Z4 ^4 u8 l: H2 f$ w
5 u1 Z# ~6 J1 o+ P* ]5 w1 v
( b5 o6 j2 e! p. A {. _: J
/ f: l( d$ e9 k; U% P6 h ( ~' W3 f: M: h; E
2 p" g$ o( e4 j0 _4 T
. m' ~1 ]* |1 x, s/ S3 ^" G: Q
+ C; {0 Q; {/ v- }! P, O6 r3 N5 g 9 T" c% V; V. M% x
0 I9 R* d! k* `0 I. i* n
- u! v5 B& a. {# M
ckwxczm1erf64032303822.jpg
0 O% a4 N8 f! |. X
5 Z4 D6 `+ z$ s+ V# }1 ? 如何设计有缺陷的嵌入式软件?
2 Z! Z2 b! s/ k/ Z! } 4 V; V# I7 [8 E% t
) c0 D8 { B$ {& S0 q9 l , ]! f/ I+ g0 M u; O7 }: U9 I3 R
4 u9 z: n& U/ m
! e+ }" l1 r! y& ]
2 u' v0 b5 _ {/ r
; | z! {* L# q* d+ T- q2 P 0 S% o' |/ k4 n- ^" v
4 W9 w) V2 r3 p* t: Q
: D, w% `. f# i& e1 {& A: n
oitvbtpkle464032303922.jpg
$ b; c) f& s' D& M
4 ? j) v* v/ X: G2 b; m1 A 在内卷中突围,开始学习嵌入式 AI 和多媒体应用技术!
1 B1 N0 z- M$ E( }8 d9 \我是老温,一名热爱学习的嵌入式工程师
- f' p* ]" d* p8 }5 A关注我,一起变得更加优秀!; j6 R, _5 o2 ^; L/ N
7 f# e( ^1 t5 I3 ?- D' W6 j" q t) N
otcjzjrmnmm64032304023.png
|
|