动态内存开辟

1.为什么会存在动态内存的分配呢

我们之前开辟的空间又是什么呢?

char a;

int arr[10];

这些都属于静态内存。

静态内存与动态内存的不同

1.空间开辟的大小是固定的(只有修改程序才可以该空间大小)
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

有时候我们需要在程序运行是才能确定内存大小需要多少,如果用静态内存的话,可能造成空间浪费,也可能造成空间不足,使数据丢失。为了避免这种现象,就有了动态内存的操作。

2.动态内存函数

2.1 malloc

void* malloc (size_t size);

malloc和声明在 stdlib.h头文件中。

这个函数的作用是向内存申请一个连续的空间,并且返回申请到空间的地址。

开辟空间不一定会成功,如果失败则返回NULL空指针。

返回类型为void*:因为malloc函数开辟空间并不知道你要开辟什么空间类型,自己用的时候加上强制类型转换就好

size表示开辟空间的大小(单位:字节)

如果参数size为0,malloc的行为是未定义(取决于编译器)

2.2 free

void free (void* ptr);

free声明在stdlib.h头文件中。

free是用来释放动态开辟的内存的。(动态内存开辟的空间在堆区)

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的.

如果参数 ptr 是NULL指针,则函数什么事都不做。

2.3 例子

#include <stdio.h>
#include <stdio.h>
#include<stdlib.h>
int main()
{
	int num = 0;
	scanf("%d", &num);
	int i = 0;
	
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));
	
	if (NULL != ptr)//判断ptr指针是否为空
	{
		for (i = 0; i < num; i++)
		{
			*(ptr + i) = i+1;
		}
	}

	for (i = 0; i < num; i++)
	{
		printf("%d ",*(ptr+i));
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//是否有必要?
	return 0;
}

比如上面这个程序,他需要在运行时才可以确定内存大小,所以我们需要用到动态内存,因为静态内存达不到。

还有有几个重点:

1.在用malloc开辟空间后,后面要跟一个判断它是否为NULL,如果为空则开辟失败(不能把一个空指针赋给其他变量去操作,养成一个好习惯动态内存开辟后用if判断是否为空)

2.将指针释放后,也要将他置为空,避免不小心在次用到这个指针(野指针会对程序造成严重问题)

2.4 calloc

void* calloc (size_t num, size_t size);

这个函数的作用也是是向内存申请一个连续的空间,并且返回申请到空间的地址。

他和malloc函数很像但是也有不同。

相同:都是开辟一段连续的内存空间(堆区),返回空间首地址

不同:参数不同,malloc一个参数,代表开辟大小(字节)

​ calloc两个参数一个是元素个数,一个是元素大小。空间大小 = 个数 * 大小

​ malloc开辟的空间没有初识化,calloc开辟的空间进行了初始化(空间内数据都为0)

例子:

请添加图片描述

这就是两者的不同。

同样用完后需要用free释放,将其置为空指针。

2.5 realloc

realloc函数的出现是动态内存更加的灵活了

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时

候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小

的调整。

void* realloc (void* ptr, size_t size);

ptr 是要调整的内存地址

size 调整之后新大小(单位为字节)

返回值为调整之后的内存起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

realloc调整内存有两种情况:

第一种 :原有内存太小

请添加图片描述

第二种 :原有内存太大

请添加图片描述

举个例子

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

int main()
{
	int* ptr = (int*)malloc(100);
	if (ptr == NULL)
	{
		return 1;
	}
	//扩展容量

	//不严谨书写
	ptr = (int*)realloc(ptr, 1000);//这样写是不好的,如果申请失败,返回的NULL赋给ptr则之前的数据也找不到了

	//正确
	int* p = NULL;
	p = realloc(ptr, 1000);
	if (p != NULL)
	{
		ptr = p;
	}
	//业务处理
	free(ptr);
	return 0;
}

3.动态内存常见错误

3.1 对NULL指针解引用操作呢

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

void test()
{
	int* p = (int*)malloc(INT_MAX*4 );
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

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

这里malloc申请失败,返回了NULL,将NULL赋值给p,再对p进行解引用就会出错。

3.2 对开辟的空间进行越界访问

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

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
}

int main()
{
	test();

	return 0;
}

这里之开辟了十个整型的空间,而你却访问第十一个整型空间会出现越界访问,程序会崩掉。

3.3 对非动态开辟内存进行free

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

void test()
{
	int a = 10;
	int* p = &a;
	free(p);//ok?
}

int main()
{
	test();

	return 0;
}

对非动态内存进行free也是非法的

3.4 使用free释放一块动态内存的一部分

#include<stdio.h>
#include<stdio.h>
#include<stdlib.h>
void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

int main()
{
	test();

	return 0;
}

释放动态内存必须用起始地址释放。

3.5对同一块内存进行多次释放

#include<stdio.h>
#include<stdio.h>
#include<stdlib.h>
void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
}
int main()
{
	test();

	return 0;
}

对同一块空间进行多次释放也会出错。

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

忘记释放不再使用的动态开辟的空间会造成内存泄漏。

切记: 动态开辟的空间一定要释放,并且正确释放 。

(尽管程序结束时会自己释放,但是如果程序运行时间过长就会使程序一直吃内存)

#include<stdio.h>
#include<stdio.h>
#include<stdlib.h>
void test()
{
	int* p = (int*)malloc(100);

}
int main()
{
	while (1)
		test();

	return 0;
}

这个程序会一直吃你的内存,知道吃完为止。

4.经典的试题

来看看自己是否学会了,检验一下自己

4.1 题目1:

void GetMemory(char* p) 
void GetMemory(char* p) 
{
	p = (char*)malloc(100);
}
void test() 
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

请添加图片描述

这个程序是运行不过去的

这个相当于值传递,p相当于临时拷贝了str的内容,然后进行动态内存的开辟,将地址存放再p中,改变p并不会改变str

相当与下面这个程序

请添加图片描述

题目1如何修改就正确了呢?

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

GetMemory(&str);

这样就正确了

4.2题目2:

char* GetMemory(void) 
char* GetMemory(void) 
{
	char p[] = "hello world";
	return p;
}
void test() 
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	test();

	return 0;
}

这个会出现非法访问,有些编译器可以打印但也是乱码

因为你开辟了一个数组在栈区,当你退出这个函数是这块空间就已经还给操作系统。如果你再访问就属于非法访问,因为空间不是你的了。

4.3题目3:

void GetMemory(char** p, int num)
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;
}

这个函数有一个问题(仔细看)

问题:没有free释放掉动态内存

4.4 题目4:

#include<stdio.h>
#include<stdio.h>
#include<stdlib.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释放后没有置空NULL,再次对一块不是你的内存进行访问,内存的非法访问

5.C程序的内存开辟

请添加图片描述

  • 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结

    束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是

    分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返

    回地址等。

  • 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分

    配方式类似于链表。

  • 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

  • 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

6.柔性数组

也许你从来没有听说过柔性数组这个概念,但是它确实是存在的。

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如:

struct S
struct S
{
	int i;
	int a[];
};

struct S1
{
	int i;
	int a[0];
};

这两种都对,有些编译器支持第一种,有些支持第二种。

6.1 柔性数组的特点

  • 结构中的柔性数组成员前面必须至少一个其他成员。

  • sizeof 返回的这种结构大小不包括柔性数组的内存。

  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大

    小,以适应柔性数组的预期大小。

请添加图片描述

这里计算结构体大小时,柔性数组并不计算在内。

6.2 柔性数组的使用

直接看代码

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

struct S
{
	int i;
	int a[];
};

int main()
{
	int i = 0;
	struct S* p = (struct S*)malloc(sizeof(struct S) + 100 * sizeof(int));

	p->i = 100;
	for (i = 0; i < 100; i++) 
	{
		p->a[i] = i;
	}
	free(p);

	return 0;
}

这便是柔性数组的使用

这里的a相当于再堆区开辟了一个100个整型的连续空间

6.3柔性数组的优点

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

struct S
{
	int i;
	int *a;
};

int main()
{
	struct S* p = (struct S*)malloc(sizeof(struct S));
	p->i = 100; 
	p->a = (int*)malloc(p->i * sizeof(int));
	//业务处理
	for (int i = 0; i < 100; i++) {
		p->a[i] = i;
	}
	//释放空间
	free(p->a);
	p->a = NULL;
	free(p);
	p = NULL;

	return 0;
}

有人会说用动态内存也可以完成上面柔性数组的任务

为什么要用柔性数组呢?

第一个好处是 :方便内存释放

柔性数组只需要释放一次

动态开辟需要释放两次

第二个好处是 :这样有利于访问速度.

连续的内存有益于提高访问速度,也有益于减少内存碎片。

柔性数组是将结构体所有元素放在一块连续的空间

而动态开辟的将所有元素放在两块连续空间内

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

struct S
{
	int i;
	int *a;
};

int main()
{
	struct S* p = (struct S*)malloc(sizeof(struct S));
	p->i = 100; 
	p->a = (int*)malloc(p->i * sizeof(int));
	//业务处理
	for (int i = 0; i < 100; i++) {
		p->a[i] = i;
	}
	//释放空间
	free(p->a);
	p->a = NULL;
	free(p);
	p = NULL;

	return 0;
}

有人会说用动态内存也可以完成上面柔性数组的任务

为什么要用柔性数组呢?

第一个好处是 :方便内存释放

柔性数组只需要释放一次

动态开辟需要释放两次

第二个好处是 :这样有利于访问速度.

连续的内存有益于提高访问速度,也有益于减少内存碎片。

柔性数组是将结构体所有元素放在一块连续的空间

而动态开辟的将所有元素放在两块连续空间内

访问没有柔性数组快

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值