第五十四天学习记录:C语言进阶:动态内存管理Ⅱ

常见的动态内存错误

1、对NULL指针的解引用操作

int* p=(int*)malloc(4);
//p进行相关的判断
*p=10;//malloc开辟空间失败,有可能对NULL指针解引用
free(p);
p=NULL;

2、对动态开辟的内存的越界访问

int* p=(int*)malloc(40);//10个int
if(p!=NULL)
{
	int i=0;
	//越界
	for(i=0;i<=10;i++)
	{
		*(p+i)=i;
	}
	free(p);
	p=NULL;
}

3、对非动态开辟内存的free

int a=10;
int* p=&a;
//...
//对非动态开辟内存的free
free(p);

4、使用free释放动态开辟内存的一部分

int* p=(int*)malloc(40);
if(p!=NULL)
{
	int i=0;
	for(i=0;i<10;i++)
	{
		*p++ = i;
	}
	free(p);
	p=NULL;
}

5、对同一块动态内存多次释放

int* p=(int*)malloc(40);
if(p!=NULL)
{
	//使用
	free(p);
	//...
	free(p);
}

6、动态开辟内存忘记释放(内存泄漏)

#define _CRT_SECURE_NO_WARNINGS 1

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

int main()
{
	int i = 0;
	while (1)
	{
		++i;
		printf("%d\n", i);
		malloc(1);
	}
	return 0;//死机警告!!!不要轻易尝试
}

问:realloc的第一个参数的指针地址必须是malloc或calloc创建的在堆上的地址吗?
ChatAI答:
是的,realloc 的第一个参数必须是由 malloccallocrealloc 函数返回的指针,且该指针必须指向堆上申请的内存空间。如果该指针不合法,也就是不是由上述函数返回的指针,或者指向的不是堆上的内存空间,那么 realloc 可能会导致未定义的行为。

问:realloc的第一个参数的指针也可以为NULL
ChatAI答:
是的,realloc 函数的第一个参数也可以是 NULL 指针。如果第一个参数是 NULL,那么 realloc 的行为和 malloc 的行为是等价的,如果分配内存成功,realloc 将会返回一个指向新内存块的指针。

例如下面的代码可以在堆上分配一段初始化为 0 的内存空间:

int *p = NULL;
p = realloc(p, sizeof(int) * 10);
if (p == NULL) {
    // 分配内存失败的处理
    printf("realloc failed");
} else {
    // 分配内存成功的处理,继续使用 p 指向的内存空间
    printf("realloc succeeded");
}

在函数调用后,如果 realloc 成功了,那么 p 将会指向一个大小为 10 个整数的内存块,并且内存块中的每个整数都将会被初始化为 0。如果 realloc 调用失败了,那么 p 会继续指向 NULL,程序会打印 “realloc failed”。

问:多次free(NULL);也不会报错吗?
ChatAI答:
在 C 语言中,多次调用free(NULL)不会导致程序崩溃或错误。因为在 C 标准中,对 NULL 指针调用 free 函数是被允许的,且不会影响程序的执行。

值得注意的是,在多次释放同一个内存指针时,可能会导致不可预测的错误,这应该避免这种情况的发生,通常情况下在free 之后将指针置空,避免出现重复释放的问题。

例题1:
请问以下代码会有什么样的结果?

#define _CRT_SECURE_NO_WARNINGS 1

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

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

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

int main()
{
	Test();
	return 0;
}

1、运行代码程序会出现崩溃的现象
2、程序存在内存泄漏的问题
str以值传递的形式给p
p是GetMemory函数的形参,只能函数内部有效
等GetMemory函数返回之后,动态开辟内存尚未释放
并且无法找到,所以会造成内存泄漏

改进1:

#define _CRT_SECURE_NO_WARNINGS 1

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

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

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

int main()
{
	Test();
	return 0;
}

改进2:

#define _CRT_SECURE_NO_WARNINGS 1

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

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

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

int main()
{
	Test();
	return 0;
}

例题2:
请问以下代码会有什么样的结果?

#define _CRT_SECURE_NO_WARNINGS 1

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

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

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

int main()
{
	Test();
	return 0;
}

非法访问。
p的内容已被回收,但地址返回了。

例题3:

#define _CRT_SECURE_NO_WARNINGS 1

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

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

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

int main()
{
	Test();
	return 0;
}

内存泄漏。

例题4:

#define _CRT_SECURE_NO_WARNINGS 1

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

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

int main()
{
	Test();
	return 0;
}

非法访问。
篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str成为野指针。if (str != NULL)不起作用。

在这里插入图片描述
图片转自比特科技

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

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量除了作用域就销毁。
但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值