C/C++进阶——动态内存管理(超详解,建议收藏!!!)

【内存】——动态内存管理

目录

前言:

 一:动态内存函数介绍

二:动态内存函数malloc与free

三:动态内存函数 calloc

四:动态内存函数 realloc

五:常见的内存错误

5.1:对NULL指针的解引用操作

5.2:对动态开辟空间的越界访问

5.3:对非动态开辟内存使用的free释放

5.4:使用free释放一块动态开辟内存的一部分

5.5:对同一块动态内存多次释放

5.6:忘记释放

六:经典笔试题

七:C/C++中程序在内存中划分


前言:

        在以前我们学习了内存开辟方式有:int a = 10;    //在栈空间上开辟四个字节。char arr[10] = {0};    // 在栈空间上开辟了10个字节的连续空间。

        但是这种开辟空间的方式有两个问题:1.空间的开辟大小必须是固定的,2.数组在声明的时候必须指定数组的大小长度。在一些特定的场景这中方式是不便的,这就需要试一下动态内存开辟了。

 一:动态内存函数介绍

首先,在使用动态内存函数之前必须得包含其头文件(<stdlib.h>)

动态内存函数一共有四个函数:malloc函数  calloc函数  realloc函数  free函数

malloc内存函数:即内存开辟函数,开辟空间成功后其空间内的内容不被初始化。

calloc内存函数:内存开辟函数,开辟空间成功后其空间内的内容被初始化。

realloc内存函数:内存开辟/内存增容函数,可以开辟内存空间,又可增长内存空间。

 free内存函数:内存释放函数,将所开辟的内存释放掉。

二:动态内存函数malloc与free

  • malloc内存函数:即内存开辟函数,此函数开辟成功后,就会返回一个指向开辟好空间的指针,若开辟失败,则返回一个NULL指针,因此 malloc的返回值一定要做检查,并且malloc申请到空间后直接返回这块空间的起始地址,不会初始化空间的内容。
  • free内存函数:内存释放函数,将所开辟的内存释放掉。需要注意的是,free内存函数只能释放动态开辟的内存,如果它的参数指向的空间不是动态的,那么 free 函数的行为是未定义的,如果它的参数是NULL指针,则此函数什么事情都不用做。

malloc 函数与 free 函数使用形式:

void* malloc(size_t num);
// num:所开辟 num 个字节
// 若函数开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
// 返回值的类型是 void* ,所以malloc函数事先并不知道开辟空间的类型,在使用的时候需要我们自己来选择

void* free(void* str);
// str:被释放空间的起始地址

如何使用:

#include<stdio.h>
#include<stdlib.h>

// malloc 与free
int main()
{
	// malloc	开辟内存(含初始化)	void* malloc(size_t size)
	// free		内存释放				void* free(void* ptr)

	int arr[10] = { 0 };					// 在栈上申请了40个字节,出了作用域被销毁
	int* p = (int*)malloc(40);				// 使用malloc 在堆上申请了40个字节,出了作用域不会被销毁
	if (p == NULL)							// 检查是否开辟空间成功
	{
		perror("malloc fail");
		return ;
	}
	// 40个字节----10个整形
	for (int i = 0; i < 10; i++)        // 给开辟的空间赋值
	{
		p[i] = i * 2;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);								// 将开辟的内存释放
	p = NULL;
}


// 运行结果:
// 0 2 4 6 8 10 12 14 16 18

查看malloc 函数所开辟的动态空间是否被初始化:

发现所开辟的空间并没有被初始化,是随机值。 

三:动态内存函数 calloc

  • calloc内存函数:是内存开辟函数。
  • void* calloc(size_t num, size_t size);
  • 功能:为 num 个大小为 size 的元素开辟一块空间,并把空间的每个字节初始化为0
  • 注意:与 malloc 内存函数的区别只在于 calloc 内存函数会在返回地址之前把申请的空间的每个字节初始化为全0。

calloc 内存函数的使用形式:

void* calloc(size_t num , size_t size);

// 为num个大小为size的元素开辟空间
// 若开辟空间失败,就返回一个NULL指针,即calloc的返回值也一定要做检查

 使用情况:

#include<stdio.h>
#include<stdlib.h>

// calloc
int main()
{
	// calloc	开辟空间(含初始化)	void* calloc(size_t num,size_t size)

	// 为num个大小为size的元素开辟一块空间,并将空间内的每个字节初始化为0
	// 与 malloc不同的是:只是calloc 会在返回地址之前把申请到的空间的每个字节初始化全为0

	int* pc = (int*)calloc(10,4);        // 开辟十个大小为4个字节的空间--->40个字节
	if (pc == NULL)                      // 检查是否开辟空间成功
	{
		perror("calloc fail");
		return;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", pc[i]);
	}
	free(pc);
	pc = NULL;
    return 0;
}

查看结果:

 发现calloc内存函数开辟成功后就直接将所开辟的空间初始化为0了。

四:动态内存函数 realloc

  • realloc函数:内存开辟/调整内存空间的函数
  • void* realloc(void* ptr , size_t size);
  • realloc函数的出现使得动态内存管理更加灵活,有时当我们发现所开辟的空间太大或者太小时,为了合理的使用内存,就定会对内存的大小进行灵活的调整,那么realloc内存函数就可以做到对动态开辟内存大小的调整。
  • ptr:要调整的内存地址;size:调整之后的新大小;返回值为调整之后的内存起始地址。
  • 功能1——内存开辟:当ptr==NULL时,此时realloc内存函数得功能就与malloc内存函数得功能一模一样。
  • 功能2——调整内存大小。

realloc内存函数的使用形式:

void* realloc(void* ptr , size_t size);
// ptr:要调整的内存地址;
// size:调整之后的新大小;
// 返回值为调整之后的内存起始地址。

 如何使用:

#include<stdio.h>
#include<stdlib.h>


// realloc
int main()
{
	// realloc	增长空间(开辟空间)	void* realloc(void* ptr,size_t size)

	// 对申请到的空间进行调整其大小,ptr是被调整的起始地址,size是调整后的新大小
	int* pr = (int*)malloc(40);            // 刚开始给pr开辟了40个字节(10个整形)
	if (pr == NULL)
	{
		perror("malloc fail");
		return;
	}
	for (int i = 0; i < 10; i++)
	{
		pr[i] = i * 2;
		printf("%d ", pr[i]);
	}
	printf("\n");
	int* tmp = (int*)realloc(pr, 80);      // 发现不太够时,先创建一个变量给其增容,增容到80个字节(20个整形)
	if (tmp == NULL)                       // 仍然检查其是否开辟空间成功
	{
		perror("realloc fail");
		return;
	}
	pr = tmp;                              // 再将tmp指针变量赋给pr变量
	for (int i = 0; i < 20; i++)           // 此时 pr的空间大小就是80个字节
	{
        pr[i] = i * 2;
		printf("%d ", pr[i]);
	}

	free(pr);            // 将内存释放回收
	pr = NULL;
}

运行结果:

五:常见的内存错误

5.1:对NULL指针的解引用操作

在使用内存函数(malloc,calloc,realloc)开辟空间之后,检查是否开辟空间成功。即检查其是否为空指针。

错误实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);
    
    for(int i = 0; i<10; i++)
    {
        printf("%d ",p[i]);
    }
    
    free(p);
    p = NULL;

}

正确实例: 

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)                // 检查是否开辟空间成功
	{
		perror("malloc fail");
		return;
	}
    for(int i = 0; i<10; i++)
    {
        printf("%d ",p[i]);
    }
    
    free(p);
    p = NULL;

}

5.2:对动态开辟空间的越界访问

不能超过内存函数所开辟的空间的大小

错误实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);    // 内存函数共开辟了40个字节,即10个整形数据
    if (p == NULL)                // 检查是否开辟空间成功
	{
		perror("malloc fail");
		return;
	}
    for(int i = 0; i<20; i++)    // 数据越界(指针使用了20个整形数据,造成了数据越界)
    {
        printf("%d ",p[i]);
    }
    
    free(p);
    p = NULL;

}

正确实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);    // 内存函数共开辟了40个字节,即10个整形数据
    if (p == NULL)                // 检查是否开辟空间成功
	{
		perror("malloc fail");
		return;
	}
    for(int i = 0; i<10; i++)
    {
        printf("%d ",p[i]);
    }
    
    free(p);
    p = NULL;

}

5.3:对非动态开辟内存使用的free释放

使用free函数释放的空间必须是由动态内存函数(malloc calloc realloc)所开辟的动态内存空间。

错误实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int a = 10;
	int* pa = &a;
	printf("%d ", *pa);
	free(pa);            // free函数不能释放非动态内存开辟的空间
	pa = NULL;
}

5.4:使用free释放一块动态开辟内存的一部分

使用free释放动态内存空间时,一定要将动态内存空间的起始地址传给free,否则就释放空间不完全。

错误实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);    // 内存函数共开辟了40个字节,即10个整形数据
    if (p == NULL)                // 检查是否开辟空间成功
	{
		perror("malloc fail");
		return;
	}
  	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;					// 注意:此时p已不是指向起始位置了
	}
	free(p);					// 此时p只是释放的一部分动态内存开辟的空间
	p = NULL;

}

正确实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);    // 内存函数共开辟了40个字节,即10个整形数据
    if (p == NULL)                // 检查是否开辟空间成功
	{
		perror("malloc fail");
		return;
	}
    int* tmp = p;               // 将起始地址保存下来
  	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;					// 注意:此时p已不是指向起始位置了
	}
    p = tmp;                    // 再将起始地址tmp赋给p指针变量
	free(p);					// 此时p释放的全部的动态内存开辟的空间
	p = NULL;

}

5.5:对同一块动态内存多次释放

错误实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)                // 检查是否开辟空间成功
	{
		perror("malloc fail");
		return;
	}
    for(int i = 0; i<10; i++)
    {
        printf("%d ",p[i]);
    }
    
    free(p);
    free(p);        // 对同一块空间进行多次释放
    p = NULL;

}

5.6:忘记释放

忘记释放动态内存空间,就会导致内存泄漏的问题。

错误实例:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)                // 检查是否开辟空间成功
	{
		perror("malloc fail");
		return;
	}
    for(int i = 0; i<10; i++)
    {
        printf("%d ",p[i]);
    }
    

}

六:经典笔试题

问运行下列main函数会有怎样的结果?

6.1:笔试题一

#include<stdio.h>
#include<stdlib.h>

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

int main()
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "abcd");
	printf(str);
    return 0;
}

结果:此程序会报错 !!!

分析:在 GetMemory 函数中创建的动态内存函数返回的指针是 p,而p指针在出了GetMemory函数作用域之后就被销毁了,并不会使指针str空间开辟。所以直到程序结束,指针str指向的一直是NULL。

改正:

#include<stdio.h>
#include<stdlib.h>

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

int main()
{
	char* str = NULL;
	GetMemory(&str);                // 传二级指针
	strcpy(str, "abcd");
	printf(str);
    return 0;
}

6.2:笔试题二

#include<stdio.h>
#include<stdlib.h>

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


int main()
{
    char* ptr = NULL;
	ptr = GetMemory_1();
	printf(ptr);
    return 0;
}

结果:乱数据

分析:通过 GetMemory_1 函数返回一个指向 "hello world" 的指针,但是数组p出了此函数作用域就会被销毁了,并不会将这个正确的指针返回到ptr中。

改正:

#include<stdio.h>
#include<stdlib.h>

char* GetMemory_1()
{
	static char p[] = "hello world";
	return p;
}


int main()
{
    char* ptr = NULL;
	ptr = GetMemory_1();
	printf(ptr);
    return 0;
}

6.3:笔试题三

#include<stdio.h>
#include<stdlib.h>

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


int main()
{
    char* xtr = NULL;
	GetMemory_2(&xtr, 100);
	strcpy(xtr, "hello world");
	printf(xtr);
    return 0;
}

这一笔试题出现的唯一问题是:没有内存空间的释放。

改正:

#include<stdio.h>
#include<stdlib.h>

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


int main()
{
    char* xtr = NULL;
	GetMemory_2(&xtr, 100);
	strcpy(xtr, "hello world");
	printf(xtr);
    free(xtr);
    xtr = NULL;
    return 0;
}

6.4:笔试题四

#include<stdio.h>
#include<stdlib.h>


int main()
{
    char* atr = (char*)malloc(100);
	strcpy(atr, "hello world");
	free(atr);
	if (atr != NULL)
	{
		strcpy(atr, "hehe");
		printf(atr);
	}

    return 0;
}

// 打印结果:hehe

 这一笔试题出现的唯一问题是:在释放完内存空间之后没有对其置空NULL,在开辟空间之后没有检查是否开辟成功。

改正:

#include<stdio.h>
#include<stdlib.h>


int main()
{
    char* atr = (char*)malloc(100);
    if(atr==NULL)
    {
        perror("malloc fail");
        return 1;
    }
	strcpy(atr, "hello world");
	free(atr);
    atr = NULL;

    return 0;
}

七:C/C++中程序在内存中划分

在计算机程序中,大致可以将内存分配划分为几个区域:内核区,栈区,内存映射区,堆区,数据区,代码段区。其中内核区暂时不详解。

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统(OS)回收 。分配方式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

看具体分析图: 

当我理解了这些变量在内存中的划分和使用时,这很有助于我们理解程序中的分析和情况。


总结:

        今天我们首先了解了动态内存函数的基本信息和如何使用的,接着又指明了使用动态内存函数的常见几个问题。紧接着了解了一些经典的笔试题型,最后学习了C/C++的各种变量在程序内存中的划分等。         

        如果这篇文章对大家真的有帮助的话,可以的话动动小手点个赞关注一下吧亲

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值