流言终结者——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)假设有

1char *p = NULL;
2p = (char*)malloc(0);

那么p获得的内存块的长度到底是多少?能否往里面写入数据?
答:不妨用这段代码来测试:

01int 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    
02value[0] is []
03yydebug:[./mem-test.c]:[34]:value len [1]
04yydebug:[./mem-test.c]:[34]:value len [2]
05yydebug:[./mem-test.c]:[34]:value len [3]
06...(省略N行)
07yydebug:[./mem-test.c]:[34]:value len [135157]
08yydebug:[./mem-test.c]:[34]:value len [135158]
09yydebug:[./mem-test.c]:[34]:value len [135159]
10Segmentation fault (core dumped)
11[michael@localhost mem-test]$

我重新编译、运行了很多次,最后打印结果都是135159;
这 个结果证明了malloc(0)返回的指针指向的是一个非NULL的内存地址处,并且该处的值是0x00,并且对于该进程,不仅该处是可写的,而且之后的 135159个Bytes也都是可写的,也就是属于这个进程空间。直到最后,可能写到别的进程的空间里面去了,才被内核发来段错误信号,自己结束了。

其实在写该处就已经是内存越界了,往后继续写更加是内存越界,只是刚好那么巧,该处往后的内存块依然是该进程的有效heap区间,所以才没有被内核发段错误,而往往这种情况是最惨的,在实际开发工作中,假设哪天真的出个这情况,又不会被警告,但是却有发现数据被窜改。

(2)假设有

1void *p = NULL;
2p = malloc(0);

那么稍后p需要用free(p)来释放,以避免内存泄漏吗?
答:不妨用这段代码来测试:

01int 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停住,运行超过几秒,电脑就会卡爆了)
2value addr [0x87aa5c8]
3value addr [0x87aa5d8]
4value addr [0x87aa5e8]^C
5[michael@localhost mem-test]$

从打印看来,虽然是调用malloc(0);,但是每次指向的地址都不同,并且逐渐增大,偏移是0x10,也就是16个字节。

可以不用while(1)来测试,用一个有限的不是很大的值来测试,然后用top命令来观察这个进程的资源使用情况,可以看出a.out消耗的内存在不断增加,所以答案就是,malloc(0)申请的内存,也要通过free()来释放,以避免内存泄漏。

(3)假设有

1char *p = NULL;
2free(p);

那么会导致进程退出吗?
答案:不会,free(NULL)相当于啥事儿不干。


(4)假设有

1char *p = NULL;
2while(1) {
3    free(p);
4}

那么会导致进程退出吗?
答案:不会,进程永远循环在while(1)里面,不会出错退出。


(5)假设有

1void *p = NULL;
2p = malloc(256);
3free(p);
4free(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进行了成功的操作,让别人好判断。不妨看看如下的例子:

01void foo(char *in)//你做的功能函数
02{
03    free(in);   
04}
05int 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
2p = [hello_world], len = [11]
3*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09088008 ***

那么“野指针”还可以这样定义,指向所有非法地址(NULL除外)的指针都可以叫野指针。
那么程序应该改成这样较妥:

01void 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}
10int 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]
10p is NULL
11[michael@localhost mem-test]$

我想,根据打印信息来看,没什么需要解释的了。顺便还弄透彻了指针以及函数传参。


(7)刚malloc后,马上就free,然后一直循环,会不会总是申请到同一块内存?

答案:这不是真的。不信?你用这些代码测试一下就知道了:

01int 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}

看看打印吧:

1p addr [0x8bd8008], ra = [59]
2p addr [0x8bd8048], ra = [78]
3p addr [0x8bd8008], ra = [41]
4p addr [0x8bd8038], ra = [46]
5p addr [0x8bd8070], ra = [82]
6p addr [0x8bd8008], ra = [62]
7p addr [0x8bd8008], ra = [91]
8p addr [0x8bd8008], ra = [24]

为什么不会一样呢?这个可以深究一下Linux系统的内存分配方式了,这就涉及到内核了。


(8)malloc(0)返回的真的入man手册所说:要么是NULL,要么是一个unique的pointer?
答案:不妨看下这段代码:

01int 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}

打印如下所示:

1p0 addr [0x97eb008] #我随便截取了一段打印
2p0 addr [0x97eb008]
3p0 addr [0x97eb008]
4p0 addr [0x97eb040]
5p0 addr [0x97eb040]
6p0 addr [0x97eb040]

所以从打印看来,我用Fedora14测试的时候,返回的既不是NULL,也不是一个唯一的地址,我现在也迷惑了,man手册中的unique到底应该如何理解。很遗憾man手册说得不太准确。如果你知道为什么,请告诉我,如果我哪一天弄明白了,我会在这里贴出来的。


(9)如果你也和我一样,做了这么多的实验,你是不是发现,malloc得到的地址的值总是大于0x80000000的(32bits机器)?
答案:不好意思,我也不知道为什么,做了好多次,不管如何重新编译、运行,得到的结果都是大于0x80000000的,如果你知道为什么,也请告诉我,如果我哪一天弄明白了,我会在这里贴出来的。

转载于:https://www.cnblogs.com/shihao/archive/2013/01/25/2876739.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/433936.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mediastreamer2 的简介

原文&#xff1a;http://www.linphone.org/eng/documentation/dev/mediastreamer2.htmlMediastreamer2 是一个功能强大且小巧的流引擎&#xff0c;专门为音视频电话应用而开发。这个库为linphone中所有的接收、发送多媒体流提供处理&#xff0c;包括音/视频捕获&#xff0c;编码…

C# 监控字段_监控交换机选择:千兆/百兆/核心/PoE/光纤交换机选型指南

我们就交换机选型时的四个主要方面讲一下。01选择千兆还是百兆&#xff1f;视频监控系统的网络中&#xff0c;需要传输大量、持续的视频数据&#xff0c;这就要求交换机具有稳定转发数据的能力。交换机接入的摄像头数量越多&#xff0c;流经该交换机的数据量就会越大。我们可以…

python 头条 上传_Python+selenium自动化之文件上传

邮箱的主要功能就是邮件消息的收发阅读&#xff0c;之前的文章写了邮件的查收和编写&#xff0c;本篇介绍邮件的附件上传。还是以腾讯企业邮箱为例&#xff0c;进行实际案例操作。文件上传的实现大体分为两种&#xff0c;一种是input标签&#xff0c;一种非input标签。腾讯企业…

JAVA学习笔记——JAVA基础语法之精华

一、标识符 概念&#xff1a;JAVA里面我们可以给他取名字的&#xff08;变量、类、方法等等&#xff09;就是标识符&#xff1a; 注意&#xff1a;1、标识符只能包含字母、数字、下划线还有美元符号$ 2、只能以字母、下划线和美元符号开头 二、变量 概念&#xff1a;JAVA中储存…

编译mediastreamer2/ffmpeg/linphone(x86平台)

--------------------------在x86环境下编译mediastreamer2的步骤--------------------------------------1&#xff09;编译OGG库 音频编解码 http://www.xiph.org/downloads/ ./configure --prefix/usr --disable-static 2&#xff09;编译SPEEX 音频编解码./configure -…

c语言 行程长度编码,C语言编程题,求大佬帮助,关于数组的。

满意答案6kidf3xhs2017.11.07采纳率&#xff1a;41% 等级&#xff1a;8已帮助&#xff1a;62人2 个关键&#xff1a;2位数字的随机数&#xff1a; a[i] 10 rand() % 90;10位或个位 含5 的 并高于平均值的 数&#xff1a;if (a[i]>ave && ( a[i]%50 || (a[i]/10)…

python多级字典嵌套_使用pythonscsv DictReader创建多级嵌套字典

完全是Python noob&#xff0c;可能遗漏了一些明显的东西。我到处找遍了&#xff0c;还没有找到解决办法&#xff0c;所以我想我应该寻求一些帮助。在我正在尝试编写一个函数&#xff0c;它将从一个大的csv文件构建一个嵌套字典。输入文件的格式如下&#xff1a;Product,Price,…

wpf学习笔记二 深入学习 xaml

1、XAML 主要用于绘制UI界面&#xff0c;最大的优点是能使UI与运行逻辑分离开来&#xff0c;使得整个程序回到逻辑处理上来。 每一个标签对应.NET Framework类库的一个控件类。通过设置标签的Attribute&#xff0c;不仅可以对标签所对应的控件 对象Property进行赋值&#xf…

cortex a7 a53_小号“A7”终于亮相,配4米9车长 大溜背!堪称15万内最强颜值!

原标题&#xff1a;小号“A7”终于亮相&#xff0c;配4米9车长 大溜背&#xff01;堪称15万内最强颜值&#xff01;今天来推荐一款b级轿车&#xff0c;大家都知道现在国内热度最高的就上suv车型了&#xff0c;但是销量最高的车型依旧还是轿车车型&#xff0c;因为轿车车型的粉丝…

speex

Speex是一套主要针对语音的开源免费&#xff0c;无专利保护的音频压缩格式。Speex工程着力于通过提供一个可以替代高性能语音编解码来降低语音应用输入门槛 。另外&#xff0c;相对于其它编解码器&#xff0c;Speex也很适合网络应用&#xff0c;在网络应用上有着自己独特的优势…

C语言数据结构迷宫实验报告,数据结构c语言课程设计报告之迷宫

数据结构c语言课程设计报告之迷宫 C语言与数据结构课程设计报告学 号 ** 姓 名 ** 课程设计题目 迷 宫 求 解 2012 年 5 月目 录1 需求分析 1.1 功能与数据需求 1.1.1 题目要求的功能 1.1.2 扩展功能 1.2 界面需求 1.3 开发环境与运行需求 2 概要设计 2.1主要数据结构2.2程序总…

unicode字符大全可复制_说说Excel不可见字符的那些事

今天小伙伴问了个问题看上去啥也没有&#xff0c;为什么黏贴到记事本上前面那么多空白呢&#xff1f;典型的不可见字符惹出来的麻烦&#xff0c;这个往往是公司软件导出数据造成的我们今天就来细说说不可见字符的那些事拿上面的例子说明大部分不可见字符&#xff0c;这一步就能…

删除后别人的微信号变成wxid_微信偷偷更新:终于能改微信号,每年改一次

跟微信打交道多年&#xff0c;机哥可以说是六号线最熟知微信套路的人。比如&#xff0c;微信最喜欢在临近周末的时候&#xff0c;来一波悄悄更新。。难不倒我&#xff01;微信新动态&#xff0c;几乎每次都被机哥妙手抓住。掐指一算&#xff0c;今天周五。安卓版微信 7.0.15 更…

窗体自适应分辨率

窗口、控件以及字体大小均随分辨率而变化&#xff0c;让界面适应各种不同的分辨率。 var FWidth: Integer;begin inherited; if (Screen.width <> 1024) then begin FWidth : Self.width; Scaled : True; Font.Size : (Self.width DIV FWidth) * Font.Size; // 字体大小调…

android设置多个按钮,android代码中设置两个按钮之间位置

package com.example.helloworld01;//包名import java.util.ArrayList;import java.util.List;import android.os.Bundle;import android.app.Activity;import android.graphics.Color;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget…

linux 下查看程序依赖的库

查看arm程序的依赖库 # arm-linux-readelf hello -d Dynamic section at offset 0xf10 contains 25 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) …

cad管线交叉怎么画_高效设计!多种方式进行管线连接、伸缩

节点连接就是需要把一些节点连接起来&#xff0c;需要通过拖拽把他连接起来&#xff0c;类似CAD的延伸。管立得中的节点连接是可以创建他们的连接关系的&#xff0c;会进行管道的联动。下面是使用管线连接功能进行连接节点&#xff0c;以及管道伸缩的的操作方式。一、管线连接1…

淡入淡出轮播图效果

第一版本有很多限制&#xff0c;特以此做记录以待日后优化。模仿支付宝首页轮播图https://www.alipay.com/ <script> $(function(){var i1;var time;$("#J-slide").hover(function(){timewindow.clearInterval(time);//清除自动播放},function(){timesetInterv…

linux打开Firefox报错,Linux下安装Firefox-3.6.12.tar.bz2及libxul.so报错解决方案

在Linux下安装firefox-3.6.12.tar.bz2&#xff0c;解压后运行./firefox时报错&#xff1a;./firefox-bin: error while loading shared libraries: ./libxul.so: cannot restore segment prot after reloc: Permission denied解决方法如下&#xff1a;以如下命令运行你解压目录…

Ubuntu 12.04 静态ip的设置方法

1. 配置静态ip地址 $sudo vi /etc/network/interfaces 原有内容只有如下两行&#xff1a; auto lo iface lo inet loopback 向末尾追加以下内容&#xff1a; auto eth0 iface eth0 inet static address 192.168.0.33 gateway 192.168.0.1 netmask 255.255.255.0 net…