流言终结者——C语言内存管理
写在前头:
我不能保证此文中,我的观点和理解全是对的,这也不是一篇教学贴,只是我偶尔突发奇想了几个特殊的场景,然后用实验得到结果,对结果进行分析,遂成此文。所以文中肯定存在错误,我也没想到会上首页,引来众人围观。
最后,欢迎拍砖,我觉得错了不要紧,改就是了,最惨的是不知道自己错在哪。
首先看一下man手册中的定义,
void *malloc(size_t size);
向系统申请size个Bytes长的连续内存,返回一个void类型的指针,指向这块儿内存的首地址。这块申请到的内存是不洁的(也就是非全0x00,内容可以是任意的,随机的)
如果size的值是0,那么返回的指针要么是指向NULL,要么是指向一个unique的地址,这个地址是可以被free释放的。(这里的解释是有问题的,例子(8)会证明)
void free(void *ptr);
释 放ptr指向的内存空间,ptr必须是之前调用过malloc,calloc,realloc这三个函数返回的,否则,如果free(ptr)已经执行过 了,而又执行一次,那么会导致意外发生(undefined behavior occurs.),如果ptr指向的是NULL,则不会做任何操作。
(1)假设有
1
|
char *p = NULL;
|
2
|
p = ( char *) malloc (0);
|
那么p获得的内存块的长度到底是多少?能否往里面写入数据?
答:不妨用这段代码来测试:
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 ( "value len [%d]\n" , strlen (ori));
|
12
|
}
|
13
|
}
|
这段代码结果如下所示:Fedora14:
01
|
[michael@localhost mem- test ]$ ./a.out
|
02
|
value[0] is []
|
03
|
yydebug:[./mem- test .c]:[34]:value len [1]
|
04
|
yydebug:[./mem- test .c]:[34]:value len [2]
|
05
|
yydebug:[./mem- test .c]:[34]:value len [3]
|
06
|
...(省略N行)
|
07
|
yydebug:[./mem- test .c]:[34]:value len [135157]
|
08
|
yydebug:[./mem- test .c]:[34]:value len [135158]
|
09
|
yydebug:[./mem- test .c]:[34]:value len [135159]
|
10
|
Segmentation fault (core dumped)
|
11
|
[michael@localhost mem- test ]$
|
我重新编译、运行了很多次,最后打印结果都是135159;
这 个结果证明了malloc(0)返回的指针指向的是一个非NULL的内存地址处,并且该处的值是0x00,并且对于该进程,不仅该处是可写的,而且之后的 135159个Bytes也都是可写的,也就是属于这个进程空间。直到最后,可能写到别的进程的空间里面去了,才被内核发来段错误信号,自己结束了。
其实在写该处就已经是内存越界了,往后继续写更加是内存越界,只是刚好那么巧,该处往后的内存块依然是该进程的有效heap区间,所以才没有被内核发段错误,而往往这种情况是最惨的,在实际开发工作中,假设哪天真的出个这情况,又不会被警告,但是却有发现数据被窜改。
(2)假设有
1
|
void *p = NULL;
|
2
|
p = malloc (0);
|
那么稍后p需要用free(p)来释放,以避免内存泄漏吗?
答:不妨用这段代码来测试:
01
|
int main( int argc, char **argv)
|
02
|
{
|
03
|
char *value = NULL;
|
04
|
05
|
while (1) {
|
06
|
value = ( char *) malloc (0);
|
07
|
printf ( "value addr [%p]\n" , value);
|
08
|
}
|
09
|
return 0;
|
10
|
}
|
结果如下所示:
1
|
...(幸亏我及时Ctrl+C停住,运行超过几秒,电脑就会卡爆了)
|
2
|
value addr [0x87aa5c8]
|
3
|
value addr [0x87aa5d8]
|
4
|
value addr [0x87aa5e8]^C
|
5
|
[michael@localhost mem- test ]$
|
从打印看来,虽然是调用malloc(0);,但是每次指向的地址都不同,并且逐渐增大,偏移是0x10,也就是16个字节。
可以不用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@localhost mem-test]$ ./a.out
|
2
|
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09dad008 ***
|
类似的信息。所以已经free掉的内存,除非又申请回来了,否则不能再次去free它,否则进程会出错。
(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
|
//感谢@<a href="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
|
}
|
假设foo函数是你写的,你同事在调用foo功能的时候,又不确定你是否free了传进去的那块内存,于是他就在调用完foo之后,加了判断,然后执行 free,结果他得到的结果是,虽然你free了指针p,但是这仅仅是告诉内核,这块内存我不用了,你可以收回了,值得注意的是,p依然指向这块内存,换 句话说也就是指针变量p存的值依然是刚才释放掉的那块内存的首地址。所以你同事的判断得到的结果是true,然后又会执行free(p);结果当然和例子 (5)一样了,如下所示:
1
|
[michael@localhost mem- test ]$ ./a.out
|
2
|
p = [hello_world], len = [11]
|
3
|
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09088008 ***
|
那么“野指针”还可以这样定义,指向所有非法地址(NULL除外)的指针都可以叫野指针。
那么程序应该改成这样较妥:
01
|
void foo( char **in) //调用方式应该是传入某个指针的地址
|
02
|
{
|
03
|
printf ( " &in = [%p], in\'s address\n" , &in);
|
04
|
printf ( " in = [%p], in\'s value\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\'s address\n" , &p);
|
16
|
printf ( " p = [%p], p\'s value\n" , p);
|
17
|
printf ( " *p = [%c], value of addr No.[%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 ( "p is NULL\n" );
|
28
|
}
|
29
|
return 0;
|
30
|
}
|
运行一遍,看看打印如何:
01
|
[michael@localhost mem- test ]$ ./a.out
|
02
|
p = [hello_world], len = [11]
|
03
|
&p = [0xbf94014c], p's address
|
04
|
p = [0x9964008], p's value
|
05
|
*p = [h], value of addr No.[0x9964008]
|
06
|
& in = [0xbf940130], in 's address
|
07
|
in = [0xbf94014c], in 's value
|
08
|
* in = [0x9964008]
|
09
|
** in = [h]
|
10
|
p is NULL
|
11
|
[michael@localhost mem- 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 ( "p addr [%p], ra = [%d]\n" , p, ra);
|
09
|
}
|
10
|
else {
|
11
|
return -1;
|
12
|
}
|
13
|
free (p);
|
14
|
}
|
15
|
return 0;
|
16
|
}
|
看看打印吧:
1
|
p addr [0x8bd8008], ra = [59]
|
2
|
p addr [0x8bd8048], ra = [78]
|
3
|
p addr [0x8bd8008], ra = [41]
|
4
|
p addr [0x8bd8038], ra = [46]
|
5
|
p addr [0x8bd8070], ra = [82]
|
6
|
p addr [0x8bd8008], ra = [62]
|
7
|
p addr [0x8bd8008], ra = [91]
|
8
|
p addr [0x8bd8008], ra = [24]
|
为什么不会一样呢?这个可以深究一下Linux系统的内存分配方式了,这就涉及到内核了。
(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 ( "error occurs\n" );
|
10
|
}
|
11
|
if (NULL != (p0 = malloc (0))) {
|
12
|
printf ( "p0 addr [%p]\n" , p0);
|
13
|
}
|
14
|
free (p);
|
15
|
free (p0);
|
16
|
}
|
17
|
return 0;
|
18
|
}
|
打印如下所示:
1
|
p0 addr [0x97eb008] #我随便截取了一段打印
|
2
|
p0 addr [0x97eb008]
|
3
|
p0 addr [0x97eb008]
|
4
|
p0 addr [0x97eb040]
|
5
|
p0 addr [0x97eb040]
|
6
|
p0 addr [0x97eb040]
|
所以从打印看来,我用Fedora14测试的时候,返回的既不是NULL,也不是一个唯一的地址,我现在也迷惑了,man手册中的unique到底应该如何理解。很遗憾man手册说得不太准确。如果你知道为什么,请告诉我,如果我哪一天弄明白了,我会在这里贴出来的。
(9)如果你也和我一样,做了这么多的实验,你是不是发现,malloc得到的地址的值总是大于0x80000000的(32bits机器)?
答案:不好意思,我也不知道为什么,做了好多次,不管如何重新编译、运行,得到的结果都是大于0x80000000的,如果你知道为什么,也请告诉我,如果我哪一天弄明白了,我会在这里贴出来的。
转载于:https://www.cnblogs.com/shihao/archive/2013/01/25/2876739.html
流言终结者——C语言内存管理相关推荐
- 分享:流言终结者——C语言内存管理
流言终结者--C语言内存管理 http://my.oschina.net/michaelyuanyuan/blog/104421
- C语言内存管理内幕(二)----半自动内存管理策略
2019独角兽企业重金招聘Python工程师标准>>> C语言内存管理内幕(二)----半自动内存管理策略 转载于:https://my.oschina.net/hengcai001 ...
- C语言内存管理超详解
补充: 1.一个正在运行着的C编译程序占用的内存分为栈区.堆区.未初始化数据区(BBS).初始化数据区.代码区5个部分. (1)栈区:存放函数的参数值.局部变量的值.由编译器自动分配释放. (2)堆区 ...
- 【C语言重点难点精讲】C语言内存管理
文章目录 一:相关动态内存函数 (1)malloc和free (2)calloc (3)realloc 二:进程地址空间 三:常见内存错误 C语言内存管理其实是一个很糟糕的话题,很烦这个,但是没有办法 ...
- C语言内存管理机制精讲-高手必修课视频教程-黄强-专题视频课程
C语言内存管理机制精讲-高手必修课视频教程-384人已学习 课程介绍 在企业级项目开发中一个非常重要的设计就是如何有效地管理内存资源.在C语言中,关于内存管理的知识点比较多,如函数变量 ...
- C语言文件联系人管理碎片整理,深入理解C语言内存管理.docx
深入理解C语言内存管理 之前在学Java的时候对于Java虚拟机中的内存分布有一定的了解,但是最近在看一些C,发现居然自己对于C语言的内存分配了解的太少. 问题不能拖,我这就来学习一下吧,争取一次搞定 ...
- 内存管理模拟程序c语言,C语言 内存管理详解
本文出自: 伟大的Bill Gates曾经失言: 640K ought to be enough for everybody -Bill Gates 1981. 程序员们经常编写内存管理程序,往往提心 ...
- C语言 内存管理之栈
内存管理之栈 什么是栈 栈管理内存的特点(小内存.自动化) 栈的应用:局部变量 栈的约束 关于栈的感悟 什么是栈 1.栈是一种数据结构,C语言中使用栈来保存局部变量.栈是管理内存的. 栈管理内存的特点 ...
- c语言malloc calloc,C语言内存管理:malloc、calloc、free的实现
任何一个对C稍稍有了解的人都知道malloc.calloc.free.前面两个是用户态在堆上分配一段连续(虚拟地址)的内存空间,然后可以通过free释放,但是,同时也会有很多人对其背后的实现机制不了解 ...
最新文章
- 在深度学习的路上,哪些框架或学习平台值得推荐?
- 打包成单独的HTML文件,【WEBPACK】分离css单独打包
- python基础代码事例-Python基础总结成千行代码,让Python入门更简单!
- UVa 242 邮票和信封(完全背包)
- R语言实战应用精讲50篇(九)-正态分布的检验
- hadoop jetty的应用
- 2014百度之星资格赛第二题
- java varbinary_SQL 中存放 varbinary型数据
- sqlite mysql php_PHP实现的简单操作SQLite数据库类与用法示例
- baidumap vue 判断范围_vue中百度地图API的调用
- 使用Kotlin的Android CoordinatorLayout
- 探讨SQL Server中Case 的不同用法
- eclipse远程发布代码的方法(SSH自动同步)
- atitit.提升兼容性最佳实践 p825.doc
- Python爬虫--爬取电影天堂网站电影下载地址
- EditText属性输入框Kotlin.Android
- STC8单片机驱动ADS1256多路AD采集
- 学透JavaScript 你真的懂 Array 吗?
- Faster R-CNN论文翻译——中英文对照
- ES6基本知识及API