[C语言学习笔记]--动态内存管理

[C语言学习笔记]--动态内存管理_第1张图片

为什么存在动态内存分配

在C语言中我们常见的内存开辟方式有

int n = 10;//在栈空间上开辟四个字节的空间
int arr[10] = {
      0 };//在栈空间上开辟40个字节连续空间

但是上述开辟空间的方式存在以下特点:

1.开辟空间的大小是连续的。
2.开辟的空间大小是固定的。 但是对于空间的需求,不仅仅只需要上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这个时候就需要动态内存开辟了。

动态内存函数

malloc函数

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

void* malloc(size_t size);
//返回值的类型是`void*`,所以使用者可以根据自己的需求来决定所开辟空间的类型。
//size为要开辟空间的大小单位为字节

这个函数在堆区申请一块连续可用的空间。

1.若开辟空间失败,则返回一个NULL指针。
2.若开辟成功,则返回一个指向开辟好的空间的指针。 因此,在应用malloc函数开辟空间时,我们需要对它的返回值进行检查,避免开辟空间失败的情况。

free函数

free函数用来释放动态开辟的内存。动态开辟的内存一定要主动释放,否则会出现内存泄露的问题。

注:
如果指针p指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果指针是NULL指针,则函数什么事情都不用做。

malloc函数与free函数的应用

int main()
{
     
	int arr[10];//这是在栈区开辟的内存空间
	
	int* p = (int*)malloc(sizeof(int)*10);//动态内存开辟的//malloc申请连续可用的空间。并返回指向该空间的指针
	if (p == NULL)//判断是否开辟成功
	{
     
		perror("main");
		return 1;
	}
 
	int i = 0;
	for (i = 0; i < 10; i++)
	{
     
		*(p + i) = i;
	}
 
	for (i = 0; i < 10; i++)
	{
     
		printf("%d ", p[i]);
	}
	//回收空间
	free(p);//只能释放动态开辟的空间和malloc成对出现
	p = NULL;//将p置为NULL指针的目的是为了防止被释放后的非法访问
	return 0;
}

calloc函数

calloc函数也是用来动态内存分配。原形如下:

void* calloc(size_t num,size_t size);

函数的功能是开辟num个空间大小为szie的空间,并会把空间的每个字节初始化为0;
函数的应用:

#include
int main()
{
     
	int* p = (int*)calloc(10, sizeof(int));
	if(NUll == p)
	{
     
		perror("main");
		return 1;
	}
	//使用空间
	
	//释放空间
	free(p);
	p = NULL;
	return 0 ;
}

realloc函数

在动态开辟内存时有时候我们会发现开辟的空间太小或者太大,为了合理的使用内存,就可以使用realloc函数对开辟的内存空间进行调整。
realloc函数的原型如下:

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

开辟空间时具有以下两种情况:
[C语言学习笔记]--动态内存管理_第2张图片

常见的动态内存错误

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

[C语言学习笔记]--动态内存管理_第3张图片

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

[C语言学习笔记]--动态内存管理_第4张图片

3、使用free释放非动态开辟的空间

[C语言学习笔记]--动态内存管理_第5张图片

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

[C语言学习笔记]--动态内存管理_第6张图片

5、对同一个动态内存地址多次释放

[C语言学习笔记]--动态内存管理_第7张图片

6、动态开辟的空间忘记释放

动态开辟的内存有两种释放方式:
1.主动free
2.程序结束
因此如果忘记释放空间会导致内存泄露的问题、因此要注意主动free掉动态开辟的空间。
[C语言学习笔记]--动态内存管理_第8张图片

经典笔试题

void GetMemory(char* p)
{
     
	p = (char*)malloc(100);
}
void Test(void)
{
     
	char* str = NULL;
	str = GetMemory(str);
	strcpy(str, "hello world");
	printf(str);//printf接收的就是字符串的地址
}
int main()
{
     
	Test();
	return 0;
}

str传给GetMemory函数是传值,所以形参p只是临时拷贝,在函数内申请的空间,存在p中,不会影响外边的str所以当函数返回之后 ,str依然是NULL所以strcpy会失败
当函数返回值时,p会销毁,会出现内存泄露

改正:放回p的地址,并主动free

char* GetMemory(char* p)
{
     
	p = (char*)malloc(100);
	return p;
}
void Test(void)
{
     
	char* str = NULL;
	str = GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
     
	Test();
	return 0;
}
char *GetMemory(void)
{
     
char p[] = "hello world";
return p;
}
void Test(void)
{
     
char *str = NULL;
str = GetMemory();
printf(str);
}

题中p是在栈区开辟的内存空间,当GetMemory函数结束时就会被释放因此导致非法访问。
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);
}

题中动态开辟的内存没有释放,出现内存泄露。
改正:主动free掉动态开辟的内存

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);
}

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

题中的问题是,释放完动态开辟的内存后,再次访问该内存,导致非法访问。
改正:每次释放内存之后将指针置为NULL指针

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

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

C/C++程序的内存开辟

[C语言学习笔记]--动态内存管理_第9张图片

栈区(satck):在执行函数时,函数的局部变量的存储空间就是在栈上创立,函数执行结束时这些存储单元就会被释放。栈区主要存放运行函数而分配的局部变量,函数参数,放回数据,返回地址等。
堆区(heap):动态开辟的存储空间就是在堆区开辟的。堆区一般又程序员分配、释放。若程序员不释放,程序结束时会被操作系统回收。
数据段(静态区):(static)存放全局变量和静态数据。程序结束后被系统释放。
代码段:存放函数体的二进制代码。

柔性数组

定义:在C99中,结构体中的最后一个元素允许是未知大小的数组,这就叫做【柔性数组】成员。例如:

struct MyStruct
{
     
	int n;
	int arr[];//该数组的大小是未知
};

柔性数组的特点

1、结构体中的柔性数组成员前至少有一个其他成员。
2、sizeof返回的这种结构体大小不包括柔性数组的大小。(如上例中的结构体大小就是整型数据n的大小,其大小为4个字节。)
3、包含柔性数组的结构体成员用malloc函数进行内存的动态分配,通常分配的内存大小应该大于结构体的大小。

分配方式如下

struct MyStruct
{
     
	int n;
	int arr[0];
};

int main()
{
     
	//期望arr的大小是10int后面数组的大小是可控的
	struct MyStruct* ps = malloc(sizeof(struct MyStruct) + 10 * sizeof(int));
	//使用空间
	//释放空间
	free(ps);
	ps = NULL;
	return 0;
}

[C语言学习笔记]--动态内存管理_第10张图片

你可能感兴趣的:(C语言,内存管理,数据结构,指针)