流言终结者——C语言内存管理
2013-05-31 22:00
411 查看
写在前头:
我不能保证此文中,我的观点和理解全是对的,这也不是一篇教学贴,只是我偶尔突发奇想了几个特殊的场景,然后用实验得到结果,对结果进行分析,遂成此文。所以文中肯定存在错误,我也没想到会上首页,引来众人围观。
最后,欢迎拍砖,我觉得错了不要紧,改就是了,最惨的是不知道自己错在哪。
首先看一下man手册中的定义,
void*malloc(size_tsize);
向系统申请size个Bytes长的连续内存,返回一个void类型的指针,指向这块儿内存的首地址。这块申请到的内存是不洁的(也就是非全0x00,内容可以是任意的,随机的)
如果size的值是0,那么返回的指针要么是指向NULL,要么是指向一个unique的地址,这个地址是可以被free释放的。(这里的解释是有问题的,例子(8)会证明)
voidfree(void*ptr);
释放ptr指向的内存空间,ptr必须是之前调用过malloc,calloc,realloc这三个函数返回的,否则,如果free(ptr)已经执行过了,而又执行一次,那么会导致意外发生(undefinedbehavioroccurs.),如果ptr指向的是NULL,则不会做任何操作。
(1)假设有
那么p获得的内存块的长度到底是多少?能否往里面写入数据?
答:不妨用这段代码来测试:
这段代码结果如下所示:Fedora14:
我重新编译、运行了很多次,最后打印结果都是135159;
这个结果证明了malloc(0)返回的指针指向的是一个非NULL的内存地址处,并且该处的值是0x00,并且对于该进程,不仅该处是可写的,而且之后的135159个Bytes也都是可写的,也就是属于这个进程空间。直到最后,可能写到别的进程的空间里面去了,才被内核发来段错误信号,自己结束了。
其实在写该处就已经是内存越界了,往后继续写更加是内存越界,只是刚好那么巧,该处往后的内存块依然是该进程的有效heap区间,所以才没有被内核发段错误,而往往这种情况是最惨的,在实际开发工作中,假设哪天真的出个这情况,又不会被警告,但是却有发现数据被窜改。
(2)假设有
那么稍后p需要用free(p)来释放,以避免内存泄漏吗?
答:不妨用这段代码来测试:
结果如下所示:
从打印看来,虽然是调用malloc(0);,但是每次指向的地址都不同,并且逐渐增大,偏移是0x10,也就是16个字节。
可以不用while(1)来测试,用一个有限的不是很大的值来测试,然后用top命令来观察这个进程的资源使用情况,可以看出a.out消耗的内存在不断增加,所以答案就是,malloc(0)申请的内存,也要通过free()来释放,以避免内存泄漏。
(3)假设有
那么会导致进程退出吗?
答案:不会,free(NULL)相当于啥事儿不干。
(4)假设有
那么会导致进程退出吗?
答案:不会,进程永远循环在while(1)里面,不会出错退出。
(5)假设有
那么进程会出错退出吗?
答案:进程会出错退出,打印堆栈信息,提示
类似的信息。所以已经free掉的内存,除非又申请回来了,否则不能再次去free它,否则进程会出错。
(6)为什么经常有人说free(p);要和p=NULL;一起用,以避免“野指针”的出现?
答案:其实用上面那个例子(5)就能看出,如果free超过1次就会出错,但是从例子(3)和例子(4)可以看出,free(NULL);可以执行任意次都不会出错。所以一般free(p);之后,马上把p指向NULL;,从而即使别人再去执行free(p);也不会出现错误。不仅如此,通过让p指向NULL,也很好的给别人一个提示,你是否对p进行了成功的操作,让别人好判断。不妨看看如下的例子:
假设foo函数是你写的,你同事在调用foo功能的时候,又不确定你是否free了传进去的那块内存,于是他就在调用完foo之后,加了判断,然后执行free,结果他得到的结果是,虽然你free了指针p,但是这仅仅是告诉内核,这块内存我不用了,你可以收回了,值得注意的是,p依然指向这块内存,换句话说也就是指针变量p存的值依然是刚才释放掉的那块内存的首地址。所以你同事的判断得到的结果是true,然后又会执行free(p);结果当然和例子(5)一样了,如下所示:
那么“野指针”还可以这样定义,指向所有非法地址(NULL除外)的指针都可以叫野指针。
那么程序应该改成这样较妥:
运行一遍,看看打印如何:
我想,根据打印信息来看,没什么需要解释的了。顺便还弄透彻了指针以及函数传参。
(7)刚malloc后,马上就free,然后一直循环,会不会总是申请到同一块内存?
答案:这不是真的。不信?你用这些代码测试一下就知道了:
看看打印吧:
为什么不会一样呢?这个可以深究一下Linux系统的内存分配方式了,这就涉及到内核了。
(8)malloc(0)返回的真的入man手册所说:要么是NULL,要么是一个unique的pointer?
答案:不妨看下这段代码:
打印如下所示:
所以从打印看来,我用Fedora14测试的时候,返回的既不是NULL,也不是一个唯一的地址,我现在也迷惑了,man手册中的unique到底应该如何理解。很遗憾man手册说得不太准确。如果你知道为什么,请告诉我,如果我哪一天弄明白了,我会在这里贴出来的。
(9)如果你也和我一样,做了这么多的实验,你是不是发现,malloc得到的地址的值总是大于0x80000000的(32bits机器)?
答案:不好意思,我也不知道为什么,做了好多次,不管如何重新编译、运行,得到的结果都是大于0x80000000的,如果你知道为什么,也请告诉我,如果我哪一天弄明白了,我会在这里贴出来的。
我不能保证此文中,我的观点和理解全是对的,这也不是一篇教学贴,只是我偶尔突发奇想了几个特殊的场景,然后用实验得到结果,对结果进行分析,遂成此文。所以文中肯定存在错误,我也没想到会上首页,引来众人围观。
最后,欢迎拍砖,我觉得错了不要紧,改就是了,最惨的是不知道自己错在哪。
首先看一下man手册中的定义,
void*malloc(size_tsize);
向系统申请size个Bytes长的连续内存,返回一个void类型的指针,指向这块儿内存的首地址。这块申请到的内存是不洁的(也就是非全0x00,内容可以是任意的,随机的)
如果size的值是0,那么返回的指针要么是指向NULL,要么是指向一个unique的地址,这个地址是可以被free释放的。(这里的解释是有问题的,例子(8)会证明)
voidfree(void*ptr);
释放ptr指向的内存空间,ptr必须是之前调用过malloc,calloc,realloc这三个函数返回的,否则,如果free(ptr)已经执行过了,而又执行一次,那么会导致意外发生(undefinedbehavioroccurs.),如果ptr指向的是NULL,则不会做任何操作。
(1)假设有
1 | char *p=NULL; |
2 | p=( char *) malloc (0); |
答:不妨用这段代码来测试:
01 | int main( int argc, char **argv) |
02 | { |
03 | char *value=NULL; |
04 | char *ori=NULL; |
05 | value= malloc (0); |
06 | ori=value; |
07 | printf ( "value[0]is[%c]\n" ,*value); |
08 | while (1){ |
09 | *value= 'a' ; |
10 | value++; |
11 | printf ( "valuelen[%d]\n" , strlen (ori)); |
12 | } |
13 | } |
01 | [michael@localhostmem- test ]$./a.out |
02 | value[0]is[] |
03 | yydebug:[./mem- test .c]:[34]:valuelen[1] |
04 | yydebug:[./mem- test .c]:[34]:valuelen[2] |
05 | yydebug:[./mem- test .c]:[34]:valuelen[3] |
06 | ...(省略N行) |
07 | yydebug:[./mem- test .c]:[34]:valuelen[135157] |
08 | yydebug:[./mem- test .c]:[34]:valuelen[135158] |
09 | yydebug:[./mem- test .c]:[34]:valuelen[135159] |
10 | Segmentationfault(coredumped) |
11 | [michael@localhostmem- test ]$ |
这个结果证明了malloc(0)返回的指针指向的是一个非NULL的内存地址处,并且该处的值是0x00,并且对于该进程,不仅该处是可写的,而且之后的135159个Bytes也都是可写的,也就是属于这个进程空间。直到最后,可能写到别的进程的空间里面去了,才被内核发来段错误信号,自己结束了。
其实在写该处就已经是内存越界了,往后继续写更加是内存越界,只是刚好那么巧,该处往后的内存块依然是该进程的有效heap区间,所以才没有被内核发段错误,而往往这种情况是最惨的,在实际开发工作中,假设哪天真的出个这情况,又不会被警告,但是却有发现数据被窜改。
(2)假设有
1 | void *p=NULL; |
2 | p= malloc (0); |
答:不妨用这段代码来测试:
01 | int main( int argc, char **argv) |
02 | { |
03 | char *value=NULL; |
04 |
05 | while (1){ |
06 | value=( char *) malloc (0); |
07 | printf ( "valueaddr[%p]\n" ,value); |
08 | } |
09 | return 0; |
10 | } |
1 | ...(幸亏我及时Ctrl+C停住,运行超过几秒,电脑就会卡爆了) |
2 | valueaddr[0x87aa5c8] |
3 | valueaddr[0x87aa5d8] |
4 | valueaddr[0x87aa5e8]^C |
5 | [michael@localhostmem- test ]$ |
可以不用while(1)来测试,用一个有限的不是很大的值来测试,然后用top命令来观察这个进程的资源使用情况,可以看出a.out消耗的内存在不断增加,所以答案就是,malloc(0)申请的内存,也要通过free()来释放,以避免内存泄漏。
(3)假设有
1 | char *p=NULL; |
2 | free (p); |
答案:不会,free(NULL)相当于啥事儿不干。
(4)假设有
1 | char *p=NULL; |
2 | while (1){ |
3 | free (p); |
4 | } |
答案:不会,进程永远循环在while(1)里面,不会出错退出。
(5)假设有
1 | void *p=NULL; |
2 | p= malloc (256); |
3 | free (p); |
4 | free (p); |
答案:进程会出错退出,打印堆栈信息,提示
1 | [michael@localhostmem-test]$./a.out |
2 | ***glibcdetected***./a.out: double free orcorruption(fasttop):0x09dad008*** |
(6)为什么经常有人说free(p);要和p=NULL;一起用,以避免“野指针”的出现?
答案:其实用上面那个例子(5)就能看出,如果free超过1次就会出错,但是从例子(3)和例子(4)可以看出,free(NULL);可以执行任意次都不会出错。所以一般free(p);之后,马上把p指向NULL;,从而即使别人再去执行free(p);也不会出现错误。不仅如此,通过让p指向NULL,也很好的给别人一个提示,你是否对p进行了成功的操作,让别人好判断。不妨看看如下的例子:
01 | void foo( char *in) //你做的功能函数 |
02 | { |
03 | free (in); |
04 | } |
05 | int main( int argc, char **argv) |
06 | { |
07 | char *p=NULL; |
08 | p=strdup( "hello_world" ); |
09 | printf ( "p=[%s],len=[%d]\n" ,p, strlen (p)); |
10 | foo(p); //你同事在调用你的函数 |
11 |
12 | //感谢@<ahref="http://my.oschina.net/louisluo"target="_blank"rel="nofollow">ColoredCotton</a>的贡献 |
13 | //由于foo函数的形参是*p,所以无法在foo函数内修改实参指针的指向,所以这的判断总是true |
14 | if (NULL!=p){ //他不确定你有木有free(),他还很聪明的做了一个判断 |
15 | free (p); |
16 | p=NULL; //他习惯很好,free之后指向NULL |
17 | } |
18 | return 0; |
19 | } |
1 | [michael@localhostmem- test ]$./a.out |
2 | p=[hello_world],len=[11] |
3 | ***glibcdetected***./a.out:double free orcorruption(fasttop):0x09088008*** |
那么程序应该改成这样较妥:
01 | void foo( char **in) //调用方式应该是传入某个指针的地址 |
02 | { |
03 | printf ( "&in=[%p],in\'saddress\n" ,&in); |
04 | printf ( "in=[%p],in\'svalue\n" ,in); |
05 | printf ( "*in=[%p]\n" ,*in); |
06 | printf ( "**in=[%c]\n" ,**in); |
07 | free (*in); //*in是实参的指针变量p的指向的被分配的内存 |
08 | *in=NULL; //使得p指向NULL,也就是修改变量p的值 |
09 | } |
10 | int main( int argc, char **argv) |
11 | { |
12 | char *p=NULL; |
13 | p=strdup( "hello_world" ); |
14 | printf ( "p=[%s],len=[%d]\n" ,p, strlen (p)); |
15 | printf ( "&p=[%p],p\'saddress\n" ,&p); |
16 | printf ( "p=[%p],p\'svalue\n" ,p); |
17 | printf ( "*p=[%c],valueofaddrNo.[%p]\n" ,*p,p); |
18 | foo(&p); //这里应该传入p的地址,即&p |
19 |
20 | //感谢@ColoredCotton的贡献 |
21 | //而现在,这里的判断就会是false了 |
22 | if (NULL!=p){ //这里的判断就有意义了 |
23 | free (p); |
24 | p=NULL; |
25 | } |
26 | else { |
27 | printf ( "pisNULL\n" ); |
28 | } |
29 | return 0; |
30 | } |
01 | [michael@localhostmem- test ]$./a.out |
02 | p=[hello_world],len=[11] |
03 | &p=[0xbf94014c],p'saddress |
04 | p=[0x9964008],p'svalue |
05 | *p=[h],valueofaddrNo.[0x9964008] |
06 | & in =[0xbf940130], in 'saddress |
07 | in =[0xbf94014c], in 'svalue |
08 | * in =[0x9964008] |
09 | ** in =[h] |
10 | pisNULL |
11 | [michael@localhostmem- test ]$ |
(7)刚malloc后,马上就free,然后一直循环,会不会总是申请到同一块内存?
答案:这不是真的。不信?你用这些代码测试一下就知道了:
01 | int main( int argc, char **argv) |
02 | { |
03 | char *p=NULL; |
04 | int ra=0; |
05 | while (1){ |
06 | ra= rand ()%100+1; //生成一个1-100之间的随机数 |
07 | if (NULL!=(p=( char *) malloc (ra))){ |
08 | printf ( "paddr[%p],ra=[%d]\n" ,p,ra); |
09 | } |
10 | else { |
11 | return -1; |
12 | } |
13 | free (p); |
14 | } |
15 | return 0; |
16 | } |
1 | paddr[0x8bd8008],ra=[59] |
2 | paddr[0x8bd8048],ra=[78] |
3 | paddr[0x8bd8008],ra=[41] |
4 | paddr[0x8bd8038],ra=[46] |
5 | paddr[0x8bd8070],ra=[82] |
6 | paddr[0x8bd8008],ra=[62] |
7 | paddr[0x8bd8008],ra=[91] |
8 | paddr[0x8bd8008],ra=[24] |
(8)malloc(0)返回的真的入man手册所说:要么是NULL,要么是一个unique的pointer?
答案:不妨看下这段代码:
01 | int main( int argc, char **argv) |
02 | { |
03 | char *p=NULL; |
04 | char *p0=NULL; |
05 | int ra=0; |
06 | while (1){ |
07 | ra= rand ()%100+1; |
08 | if (NULL==(p=( char *) malloc (ra))){ |
09 | printf ( "erroroccurs\n" ); |
10 | } |
11 | if (NULL!=(p0= malloc (0))){ |
12 | printf ( "p0addr[%p]\n" ,p0); |
13 | } |
14 | free (p); |
15 | free (p0); |
16 | } |
17 | return 0; |
18 | } |
1 | p0addr[0x97eb008] #我随便截取了一段打印 |
2 | p0addr[0x97eb008] |
3 | p0addr[0x97eb008] |
4 | p0addr[0x97eb040] |
5 | p0addr[0x97eb040] |
6 | p0addr[0x97eb040] |
(9)如果你也和我一样,做了这么多的实验,你是不是发现,malloc得到的地址的值总是大于0x80000000的(32bits机器)?
答案:不好意思,我也不知道为什么,做了好多次,不管如何重新编译、运行,得到的结果都是大于0x80000000的,如果你知道为什么,也请告诉我,如果我哪一天弄明白了,我会在这里贴出来的。
相关文章推荐
- 分享:流言终结者——C语言内存管理
- 分享:流言终结者——C语言内存管理
- 分享:流言终结者——C语言内存管理
- 流言终结者——C语言内存管理 - michael的个人空间 - 开源中国社区
- 分享:流言终结者——C语言内存管理
- 分享:流言终结者——C语言内存管理
- C语言内存管理相关知识点
- C语言 内存管理详解
- c语言libjson库一些api和内存管理
- 黑马程序员——C语言之字符串操作函数与内存管理
- objective-c语言_内存管理
- C语言内存管理,林锐博士的文章,绝对经典
- C语言(内存管理、文件处理)
- C语言中的内存管理
- C语言内存管理(林锐博士的文章,绝对经典)
- C语言实现简单的内存管理机制
- C语言中的内存管理
- C语言内存管理(初级)----动态数组
- C语言的内存管理
- C语言知识整理(3):内存管理(详细版)