动态内存函数
free
动态内存释放函数,在指针释放内存之后,指针依旧指向动态内存块的首地址,所以要把NULL赋给指针,避免出现错误(野指针)。
如果prt指向的空间是非动态开辟的,则free操作时未被定义的
如果prt指针为null,则free函数什么都不做
malloc
开辟size个大小的内存块,单位为字节。
动态开辟函数,函数向内存申请一块连续的空间,成功则返回这片空间的指针,开辟失败则返回NULL指针,所以需要对malloc的返回值进行判断。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = 0;
scanf("%d", &num);
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));
if(NULL != ptr)
{
int i = 0;
for(i=0; i<num; i++)
{
*(ptr+i) = 0;
}
}
free(ptr);
ptr = NULL;
return 0;
}
注意:malloc返回值类型是void指针类型,在使用时要强转。
calloc
动态内存开辟函数,为num个大小为size的元素开辟空间,并且把空间的每个字节都初始化为0。
realloc
动态内存管理,prt时要调整开辟空间内存大小的地址,size是调整后的内存开辟的大小。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(100);
if (ptr != NULL)
{
}
else
{
exit(EXIT_FAILURE);
}
int* p = NULL;
p = realloc(ptr, 1000);
if (p != NULL)
{
ptr = p;
}
free(ptr);
return 0;
}
注意:realloc可能当空间不够时,会返回一个空值,所以需要判断realloc有没有成功开辟新空间。
常见的动态内存错误
对null指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;
free(p);
}
上面的代码没有对指针p进行判断,如果malloc开辟空间失败则会返回null,而后面的操作可能会对null进行解引用操作。
对动态开辟空间的越界访问
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;
}
free(p);
}
上面的代码中当i=10时,存在越界访问。
对非动态开辟内存使用free释放
void test()
{
int a = 10;
int* p = &a;
free(p);
}
free释放非动态内存操作存在问题,局部变量a被开辟在栈区,动态内存开辟则是在堆区。
使用free释放动态开辟内存的一部分
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);
}
指针p的位置发生变化没有指向动态内存的初始位置。
对同一块动态内存多次释放
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);
}
第一次free操作后没有把null赋给p,会出现p依旧指向某个地址但是这个地址指向的内存块已经被free掉了,使得p成为野指针,第二次操作就是在对野指针的解引用操作。
动态内存空间忘记释放即内存泄漏
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
忘记释放不再使用的动态开辟的空间则会造成内存泄漏。
动态内存的笔试题
运行Test函数会有什么结果?
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
程序崩溃
崩溃的主要原因是因为对空指针的解引用操作,在GetMemory函数中,str将null赋给形参p,然后开辟了一个100个字节的内存块将首地址赋给了p,然后函数返回void,函数并没有改变str,所以在strcpy中str依旧是空指针。
第二个明显的错误就是存在内存泄漏,并且在退出GetMemory函数后指针p也随之消失,无法在查询到动态开辟的内存块首地址了,也就无法在释放内存了。
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
打印乱码
在GetMemory函数中,在栈区开辟了一个临时的数组p,函数返回了p的首地址但是数组里面的内容随着函数的结束而被销毁,所以函数返回了p的地址但是p里面的内容可能被篡改了所以打印了乱码。
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
能正常打印,但是存在内存泄漏
开辟了一个动态内存的空间但是没有释放。
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
程序崩溃
在对动态内存块释放后,str依旧指向动态内存块的首地址,在if语句的判断中始终为真,所以进入语句,但是该地址指向的内存块已经不存在,str变为野指针,在strcpy中存在对野指针的解引用操作,导致程序崩溃。
程序的内存开辟
程序分配的几个区域
1、栈区:主要存放局部变量和函数参数等,其特点就是在栈区创造的变量出了作用域就会被销毁。
2、堆区:主要存放动态内存,需要程序员主动释放。
3、静态区(数据段):主要存放全局变量和静态数据,在程序结束后由系统释放。
4、代码段:主要存放类成员函数和全局函数。
柔性数组
在C99中,结构体中最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。
举例
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
特点
结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大
小,以适应柔性数组的预期大小。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
struct s
{
char j;
int i;
int a[];
}S;
int main()
{
struct s* p = (struct s*)malloc(sizeof(S) + 12);//给柔性数组成员a开辟了12个字节空间
if (p == NULL)
{
perror("malloc");
return 1;
}
p->j = 'a';
p->i = 64;
for (int i = 0; i < 3; i++)
{
p->a[i] = i;
}
for (int i = 0; i < 3; i++)
{
printf("%d", p->a[i]);
}
//扩容
struct s* ps = (struct s*)realloc(p, sizeof(S) + 32);
if (ps != NULL)
{
p = ps;
}
else
{
perror("realloc");
return 1;
}
//释放
free(p);
p = NULL;
return 0;
}
扩容前的柔性数组在内存中。