动态内存管理

目录

1. 为什么要有动态内存分配

2. malloc和free

2.1 malloc

2.2 free

3. calloc和realloc

3.1 calloc

3.2 realloc

4. 常见的动态内存的错误

4.1 对NULL指针的解引用操作

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

4.3 对非动态开辟内存使用free释放

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

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

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

5. 动态内存例题

5.1 例题1:

5.2 例题2:

5.3 例题3:

5.4 例题4:

6. 柔性数组

6.1 柔性数组的特点


1. 为什么要有动态内存分配

常用的内存开辟方式有:

int a = 10;//在空间上开辟4个字节
char arr[10] = {0};//在空间上开辟10个字节的连续空间

但是,上述的开辟方式有两个特点:

(1)空间开辟的大小是固定的;

(2)数组在声明的时候必须指定数组的长度,数空间一旦确定了大小就不能调整。

有的时候我们需要的空间大小在程序运行的时候才知道,那么数组在编译时开辟的方式就不能满足需求了。

所以,C语言引入了动态内存开辟,让程序员自己可以申请和释放空间。

2. mallocfree

2.1 malloc

C语言提供了一个动态内存开辟的函数: 

void* malloc (size_t size);

 返回void* 是因为malloc函数只知道要申请多大的空间,但不知道返回什么类型的数据。

开辟出的空间里的值是随机的,例如:

这个函数像内存申请一块连续可用的空间,并且返回指向这块空间的指针。

如果开辟成功,则返回一个指向开辟空间的指针;

如果开辟失败,则返回一个NULL指针(所以malloc的返回值一定要检查)

返回值的类型是 void* ,开辟空间的具体的类型在使用的时候使用者自己决定。

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

2.2 free

 C语言提供了一个动态内存释放回收的函数: 

void free (void* ptr);

 free函数用来释放动态开辟的内存。

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

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

mallocfree都声明在 stdib.h 头文件中。

3. callocrealloc

3.1 calloc

void* calloc (size_t num, size_t size);

这个函数的作用是为 num 个大小为 size 的元素开辟出一块空间并且将空间的每个字节初始化为0。

calloc 和 malloc 的区别在于:

(1)malloc 只传一个参数,可以将个数乘好后传进去,只需要总的大小,而calloc需要两个参数:元素个数和元素大小;

(2)malloc函数只负责开辟空间,不对空间初始化,而calloc在开辟好空间的同时将里面的每个字节全部初始化为0。

所以,当我们需要对申请的空间有初始化的要求,calloc就十分方便。

3.2 realloc

为了合理使用内存,在编译的时候一定会对使用内存的大小做灵活的调整。

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

realloc调整空间失败,则返回NULL;

realloc调整空间成功,则分两种情况: 

假如想要扩大使用空间

情况1:在已经开辟好的空间的后面,没有足够大的空间用于直接进行空间的扩大

             这时,realloc函数会在内存的堆区重新找一块满足所需大小的新空间,同时把旧的数据拷               贝到新的空间,释放旧的空间,同时返回新空间的起始位置。

情况2:在已经开辟好的空间的后面,有足够的空间,直接进行扩大

扩大空间后,直接返回旧的空间的起始地址。

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int*p = (int*)calloc(10, sizeof(int));

	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}

	//空间不够,想要扩大空间,20个整型
	int* ptr = (int*)realloc(p, 20*sizeof(int));
	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	
	//释放空间
	free(p);
	p = NULL;

	return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int*p = (int*)realloc(NULL, 40);//等价于malloc

	//释放空间
	free(p);
	p = NULL;

	return 0;
}

4. 常见的动态内存的错误

4.1 对NULL指针的解引用操作

int main()
{
	int*p = (int*)malloc(100);
	*p = 20;
	free(p);
	return 0;
}

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	int i = 0;
	for (i = 0; i < 11; i++)//越界访问
	{
		*(p + i) = i;
	}
	return 0;
}

4.3 对非动态开辟内存使用free释放

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//使用
	//...
	p = &a;//p指向的空间就不再是堆区上的空间
	free(p);
	p = NULL;
	//....

	return 0;
}

 malloc/calloc/realloc申请的空间如果不主动释放,出了作用域是不会销毁的。

释放的方式:

(1)free主动释放;

(2)直到程序结束,才由操作系统回收。

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

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}	
	//使用
	p++;

	//释放
	free(p);
	p = NULL;

	return 0;
}

这里经过p++后,p指向的不再是起始位置。

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

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//使用

	//释放
	free(p);
	//加上p = NULL;则正常

	free(p);
	p = NULL;

	return 0;
}
 

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


void test()
{
    int* p = (int*)malloc(100);
    if (NULL != p)
    {
        *p = 20;
    }
    //free(p);
    //p = NULL;
}

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

malloc函数申请的空间没有在最后释放,而while(1)无限循环,那么malloc申请的空间将永远没有机会释放,同样别的操作也用不上这块空间,从而导致内存泄漏。

5. 动态内存例题

5.1 例题1:

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


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

结果为程序崩溃。

修改后: 

第一种:

第二种: 

5.2 例题2:

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

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

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

 修改后:

5.3 例题3:

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

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

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

修改后:

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

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

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

5.4 例题4:

void Test(void)
{
    char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);

    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

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

修改后:

6. 柔性数组

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

例如:

struct St
{
	int i;
	int arr[0];//柔性数组成员
};

6.1 柔性数组的特点

(1)结构体中的柔性数组成员前面必须至少有一个成员

(2)sizeof 返回的结构体的大小不包括柔性数组的内存

(3)包含柔性数组成员的结构用 malloc()函数进行动态内存分配,且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。

6.2 柔性数组的优势 

对比以下两个方案: 

//方案一(柔性数组)
struct St
{
	char c;
	int n;
	int arr[0];
};


int main()
{
	struct St* ps = (struct St*)malloc(sizeof(struct St) + 10 * sizeof(int));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->c = 't';
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}

	//数组空间不够
	struct St* ptr = realloc(ps, sizeof(struct St) + 15 * sizeof(int));
	if (ptr != NULL)
	{
		ps = ptr;
	}
	else
	{
		perror("realloc");
		return 1;
	}

	//释放
	free(ps);
	ps = NULL;

	return 0;
}

//方案二
struct St
{
	char c;
	int n;
	int* arr;
};


int main()
{
	struct St* ps = (struct St*)malloc(sizeof(struct St));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->c = 'w';
	ps->n = 100;

	ps->arr = (int*)malloc(10 * sizeof(int));
	if (ps->arr == NULL)
	{
		perror("malloc-2");
		return 1;
	}

	//数组空间不够
	int* ptr = (int*)realloc(ps->arr, 15 * sizeof(int));
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		ps->arr = ptr;
	}

	//释放
	free(ps->arr);
	ps->arr = NULL;

	free(ps);
	ps = NULL;

	return 0;
}

显然,方案一更好。 

上述的两种方案都是在堆区上开辟空间。

方案一的整个过程中,就使用了一次malloc,一次realloc,最后释放。

而在方案二中,结构体使用了一次malloc,arr使用了一次malloc,使用的次数变多,出错的概率也会变大。

但是,方案二有利于访问速度。因为连续的空间有利于减少内存碎片。

  • 28
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YMLT花岗岩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值