内存分配的四个例子

原文在是:有关内存的思考题 在这篇基础上扩展了些知识,以做记录。

第一个例子:

char *GetMemory(char * p) {
    p = (char *)malloc(100);
    return p;
}

void Test(void) {
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}

请问运行Test函数会有什么样的结果?

答:程序崩溃。

这是函数形参的问题,函数GetMemory的输入参数char * p是一个形式参数。

形式参数在函数定义时并不占用内存中的存储单元;

形式参数在函数调用时被分配内存单元(在栈上),并且其初始值与被赋的实参相同,调用以后形参与实参之间没有任何联系,它们是物理地址不同的两个变量。因此在函数内部对于形参所做的改变,是不能在实参上得到体现的;

形式参数在函数结束时被释放,它的值将被丢弃。

Test函数中的 str一直都是 NULL。strcpy(str, "hello world");将使程序崩溃。

函数改成:

void Test(void) {
    char *str = NULL;
    str = GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}

就对了,但是malloc的内存没有释放,有内存泄露问题。

第二个例子:

char *GetMemory(void) {
    char p[] = "hello world";
    return p;
}

void Test(void) {
    char *str = NULL;
    str = GetMemory();
    printf(str);
}

请问运行Test函数会有什么样的结果?

答:可能是乱码。

这是个内存分配问题,char p[] = "hello world";这句话可以理解为:a.在栈上分配长度为12的内存,p指向这个内存地址;b.把"hello world"拷贝到p指向的栈内存里面,再加上一个结束字符‘\0‘(这也是为什么11个字符,长度为12的原因)。p和p指向的地址都在栈上面。当函数退出的时候,释放使用的栈空间给其他的函数使用,因此该地址上的值有可能被其他函数重写,这样打印出来的可能是乱码。由于栈释放只是把栈顶指针移位,并没有把释放的数据段初始化,因此释放后的数据还有保留,如果又没有其他函数重写这部分栈空间,那么打印出来的数据也有可能是对的。

函数改为:

char *GetMemory(void) {
    char *p = "hello world";
    return p;
}

其他不变,这个Test函数运行的结果恒定为"hello world"。

这是因为:char *p = "hello world"; p是在栈上分配的,"hello world"定义在文字常量区。一个栈上的变量p指向文字常量区的一个字符串。函数返回后str指向的也是这个文字常量区,因此打印的结果也是对的,而且文字常量区是由编译器自动创建释放,不存在内存泄露问题。

类似引申一个问题

    char *p0 = "hello world";
    char p1[] = "hello world";
    *p0='W';   // 可以修改,因为p0指向的空间在栈上
    *p1='W';   // 系统错误,因为试图修改文字常量区内容。

这就是这个问题的本质区别。

第三个例子:

void GetMemory(char **p, int num) {
    *p = (char *)malloc(num);
}

void Test(void) {
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello world");
    printf(str);
}

请问运行Test函数会有什么样的结果?

答:

(1)能够输出hello world

(2)存在内存泄漏

GetMemory输入实参是char *str的地址(指针的地址),在函数里面改变了这个地址指向的空间,因此返回后可以带出修改后的数据值。但是str空间没有被释放,所以有内存泄露。如果不是字符串,这个问题似乎更容易理解。

int get_x(int x, int *y) {
   x++;
   *y = x+1;
   return x;
}

void Test(void) {
   int x = 100;
   int y = 200;
   int z = get_x(x, &y);
   printf("x=%d y=%d z=%d\n", z, y,z);
}

输出结果为:x=100 y=102 z=101。

第四个例子:

void Test(void) {
    char *str = (char *) malloc(100);
    strcpy(str, “hello”);
    free(str);
    if(str != NULL) {
      strcpy(str, “world”);
      printf(str);
    }
}

请问运行Test函数会有什么样的结果?

答:篡改动态内存区的内容,后果难以预料。

因为调用free(str)之后,str成为悬垂指针,它还指向原来的堆地址空间(因此str!=NULL 返回为1),但是那个空间已经被系统回收,它已经不是一个系统可知长度的独立的被标识为已用的内存空间段。如果这时候还强行向其中写数据,也不一定会出错。因为该地址真实存在,但是这是不安全的,1.可能写到下一个数据空间,写花别人;2.可能系统把包含这个数据段的空间重新分配给其他指针,然后那个指针赋值后,就写花了自己。这两种情况都是不可预知的,因此要尽量避免这种情况出现。避免的方式很简单 free(str); str=NULL;这两句话不要分开。

时间: 2025-01-02 15:59:17

内存分配的四个例子的相关文章

垃圾收集器与内存分配策略(四)之垃圾收集器

垃圾收集器与内存分配策略(四)--垃圾收集器 收集算法是内存回收的方法论,垃圾收集器则是内存回收的具体实现. 垃圾收集器介绍 在垃圾收集器的层面上对并行与并发的解释: 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户现场仍处于等待状态. 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但并不一定是并行的,可能会交替执行),用户程序仍在继续执行,而垃圾收集程序运行于另一个CPU上. 对于不同的厂商,不同的版本的虚拟机都可能有很大的差别.此处讨论的是jdk1.7之后的

简单理解动态内存分配和静态内存分配的区别

在涉及到内存分配时,我们一般都要考虑到两种内存分配方式,一种是动态内存分配,另一种是静态内存分配,我们该怎么理解这两者的区别呢? 在我看来,静态内存分配和动态内存分配比较典型的例子就是数组和链表,数组的长度是预先定义好的,在整个程序中是固定不变的,所以他在内存分配时是以静态内存分配的方式进行的.而链表,它的信息有可能会随时更改,内存的分配取决于我们实际输入的数据,这样就用到了动态内存分配的方式. 静态内存分配是在程序编译或者运行过程中,按事先规定的大小分配内存空间的分配方式,他的前提的必须事先知

最简单例子图解JVM内存分配和回收

一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分为Eden区(圣经中的伊甸园).和两个Survivor区.新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到年老区. 简单讲,就是生命期短的对象放在一起,将少数生命期长的对象放在一起,分别采用不同的回收

C++ 动态内存分配(6种情况,好几个例子)

1.堆内存分配 : C/C++定义了4个内存区间: 代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或自由存储区(free store). 堆的概念: 通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间.这种内存分配称为静态存储分配: 有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分

数往知来C#之接口 值类型与引用类型 静态非静态 异常处理 GC垃圾回收 值类型引用类型内存分配<四>

C# 基础接口篇 一.多态复习 使用个new来实现,使用virtual与override    -->new隐藏父类方法 根据当前类型,电泳对应的方法(成员)    -->override重写 无论什么情况,都是执行新的方法(成员) 继承是实现多态的一个前提,没有继承多态是不能实现的 父类与子类实现多态 抽象类与子类实现 抽象类不能实例化 抽象类中的抽象方法没有方法体 抽象类的成员有哪些   ->包含非抽象成员   ->不能实例化   ->子类必须实现父类的 抽象方法,除非子

【C语言天天练(二四)】内存分配

引言: 对于C语言程序,了解它执行时在内存中是怎样分配的对于我们理解它的执行机制是很实用的.以下就总结一下C语言程序的一些内存分配知识. 一 一段C程序.编译连接后形成的可运行文件一般有代码段.数据段.堆和栈等几部分组成.当中数据段又包含仅仅读数据段.已初始化的读写数据段和未初始化的BSS段.例如以下图所看到的: 文本段:存放程序运行的代码. 数据段: 1>仅仅读数据段: 仅仅读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,因为这些变量不须要更改,因此仅仅须要放置在

memcached学习——memcached的内存分配机制Slab Allocation、内存使用机制LRU、常用监控记录(四)

内存分配机制Slab Allocation 本文参考博客:https://my.oschina.net/bieber/blog/505458 Memcached的内存分配是以slabs为单位的,会根据初始chunk大小.增长因子.存储数据的大小实际划分出多个不同的slabs class,slab class中包含若干个等大小的trunk和一个固定48byte的item信息.trunk是按页存储的,每一页成为一个page(默认1M). 1.slabs.slab class.page三者关系: sl

【转】深入JVM系列(一)之内存模型与内存分配

http://lovnet.iteye.com/blog/1825324 一.JVM内存区域划分 大多数 JVM 将内存区域划分为 Method Area(Non-Heap),Heap,Program Counter Register, Java Method Stack,Native Method Stack 和Direct Memomry(注意 Directory Memory 并不属于 JVM 管理的内存区域).前三者一般译为:方法区.堆.程序计数器.但不同的资料和书籍上对于后三者的中文译

C++内存管理(超长,例子很详细,排版很好)

[导语] 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能.本期专题将从内存管理.内存泄漏.内存回收这三个方面来探讨C++内存管理问题