[C语言学习笔记]--动态内存管理
Posted 八只脚抠脚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C语言学习笔记]--动态内存管理相关的知识,希望对你有一定的参考价值。
为什么存在动态内存分配
在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<stdio.h>
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为调整之后的空间大小 返回值为内存的起始地址
开辟空间时具有以下两种情况:
常见的动态内存错误
1、对NULL指针的解引用操作
2、对动态开辟的内存的越界访问
3、使用free
释放非动态开辟的空间
使用free释放动态内存中的一部分
5、对同一个动态内存地址多次释放
6、动态开辟的空间忘记释放
动态开辟的内存有两种释放方式:
1.主动free
2.程序结束
因此如果忘记释放空间会导致内存泄露的问题、因此要注意主动free
掉动态开辟的空间。
经典笔试题
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++程序的内存开辟
栈区(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语言学习笔记]--动态内存管理的主要内容,如果未能解决你的问题,请参考以下文章