1. 动态内存函数—malloc
void malloc(size_t size);
malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
①开辟空间成功,则返回一个指向开辟好的空间的指针
②开辟空间失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
③返回值的类型是void*,所以malloc函数并不知道开辟空间放的数据的类型,具体在使用的时候使用者自己决定
④如果size为0,malloc函数的行为是标准未定义的,取决于编译器。
malloc申请的内存空间,当程序退出时还给操作系统,当程序不退出,申请的动态空间不会主动释放,因此就需要释放空间的函数free
2. 释放动态内存函数—free
void free (void* ptr);
①如果参数ptr只想的空间不是动态开辟的,那么free函数的行为是未定义的
int main()
{
int a = 10;
int* p = &a;
free(p);//错误
return 0;
}
②如果参数ptr是NULL,函数什么都不做。
使用malloc和free函数的例子
#include<stdio.h>
#include<stdlib.h>//动态内存函数的使用要包含头文件
int main()
{
int num = 0;
scanf("%d", &num);
int* ptr = NULL;
ptr = (int*)malloc(num * sizeof(int));//开辟num个大小为int类型的空间
if (NULL == ptr)//判断开辟动态内存是否成功,不成功打印错误原因
{
perror("malloc");
return 1;//开辟失败,返回1,结束程序,与下面返回0区别开,
}
int i = 0;
for (i = 0; i < num; i++)
{
printf("%d\n", *(ptr + i));//malloc函数开辟空间后,直接返回指向该空间的指针,不会初始化空间的内容,因此打印出来是随机值
}
for (i = 0; i < num; i++)
{
*(ptr + i) = i;//赋值
printf("%d ", *(ptr + i));//打印
}
free(ptr);//释放空间
ptr = NULL;//将ptr置为空指针,不然ptr就变成野指针了
return 0;
}
3. 动态内存函数—calloc
void * calloc(size_t num, size_t size);
①函数的功能是为num个大小为size的元素开辟空间,并且把空间的每个字节初始化为0
例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)calloc(10, sizeof(int));
if (NULL == ptr)
{
perror("calloc");
return 1;
}
free(ptr);
ptr = NULL;
return 0;
}
4. 动态内存调整函数—realloc
realloc函数用于对动态开辟空间大小的调整
void * realloc(void * ptr, size_t size);
①ptr是要调整的内存的地址
②size是调整后的内存大小
③返回值为调整后的内存的起始地址
④realloc函数在调整原内存大小的基础上,还会将原来内存上存储的数据移动到新开辟的内存上
⑤realloc函数在调整内存大小的时候存在两种情况
●情况1:原有空间后面还有足够的未使用的空间可以用来扩大内存
例1:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);//先开辟40个字节的空间
if (NULL == ptr)
{
perror("malloc");
return 1;
}
printf("%p\n", ptr);打印动态内存空间的起始地址
int* p = NULL;
p = realloc(ptr, 60);//调整内存需要先用新的指针接收,不能用ptr接收,因为如果用ptr接收,调整内存失败,返回NULL指针,ptr无法指向原来开辟的空间,造成内存泄漏
if (NULL == p)
{
perror("realloc");
return 1;
}
ptr = p;
printf("%p\n", ptr);
return 0;
}
原空间地址和新开辟空间的地址相同
●情况2:原有空间后面的没有足够大的空间用来扩大内存
realloc函数执行的操作包括:
1. 开辟新的适合大小的空间
2. 将旧的空间的数据拷贝到新空间
3. 释放旧的空间
4. 返回新空间的起始地址
例1:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);
if (NULL == ptr)
{
perror("malloc");
return 1;
}
printf("%p\n", ptr);
int* p = NULL;
p = realloc(ptr, 800);//调整后的内存太大,原空间后面没有足够的地址,因此,realloc重新开辟了空间,并返回一个新的指向该空间的地址
if (NULL == p)
{
perror("realloc");
return 1;
}
ptr = p;
printf("%p\n", ptr);
free(ptr);
ptr = NULL;
return 0;
}
原空间地址和新空间地址不同
例2:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);
if (NULL == ptr)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
int* p = NULL;
p = realloc(ptr, 80);
if (NULL == p)
{
perror("realloc");
return 1;
}
ptr = p;
for (i = 0; i < 20; i++)
{
printf("%d\n", *(ptr + i));
}
free(ptr);
ptr = NULL;
return 0;
}
新开辟出来的多的空间并不会被初始化
例3:减小动态内存
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);//申请10个整型大小的空间
if (NULL == ptr)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
int* p = NULL;
p = realloc(ptr, 20);//调整为5个整型大小的空间
if (NULL == p)
{
perror("realloc");
return 1;
}
ptr = p;
for (i = 0; i < 10; i++)//还是访问了10个整型,越界访问
{
printf("%d\n", *(ptr + i));
}
free(ptr);
ptr = NULL;
return 0;
}
缩小内存后,原内存后面不要的空间会被释放并被其他程序使用
5. 常见的动态内存错误
5.1 对NULL指针的解引用操作
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);
*ptr = 20;//错误,如果开辟内存失败,则对NULL进行解引用操作,应该对ptr进行判断
free(ptr);
ptr = NULL;
return 0;
}
5.2 对动态开辟空间的越界访问
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);
if (NULL == ptr)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i <= 10; i++)
{
*(ptr + i) = i;//i = 10时造成越界访问,程序出错
}
free(ptr);
ptr = NULL;
return 0;
}
5.3 对非动态开辟内存的释放
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a = 10;
int* ptr = &a;
free(ptr);
ptr = NULL;
return 0;
}
5.4 使用free函数释放一块动态开辟内存的一部分
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);
if (NULL == ptr)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i <= 10; i++)
{
*ptr++ = i;//++操作使得ptr不再指向动态开辟内存的起始位置
}
free(ptr);//释放时会出错
ptr = NULL;
return 0;
}
5.5 对同一块动态内存的多次释放
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(40);
free(ptr);
free(ptr);//重复释放,出错
ptr = NULL;
return 0;
}
5.6 动态开辟内存忘记释放(内存泄漏)
动态内存申请的空间不会出了作用域就自动销毁,只能①free函数释放②程序退出自动归还给系统
#include<stdio.h>
#include<stdlib.h>
void test()
{
int* p = (int*)malloc(40);
if (NULL == p)
{
perror("maollc");
return;
}
*p = 20;//使用后忘记释放内存
}
int main()
{
test();//出了test函数,局部指针变量p销毁,无法找到动态开辟内存的位置,造成内存泄漏
malloc申请的40个字节再也无法使用,直到程序退出
return 0;
}
6. 经典笔试题解析
6.1 传值和传址
void GetMemory(char *p)
{
p = (char *)malloc(100);//函数形参是实参的临时拷贝,p保存的动态内存的地址并不会返回给str
}
void Test(void)
{
char *str = NULL;
GetMemory(str);//传值,并不会改变实参
strcpy(str, "hello world");//对空指针的解引用
printf(str);//没有释放动态内存
}
正确写法:
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);//传递str的地址,str就存储了动态内存的起始地址
strcpy(str, "hello world");
printf(str);
free(str);//释放内存
str = NULL;
}
int main()
{
Test();
return 0;
}
6.2 返回栈空间地址的问题
char *GetMemory(void)
{
char p[] = "hello world";
return p;//出了GetMemory函数,保存helloworld字符串的数组被销毁,还给操作系统,
}
void Test(void)
{
char *str = NULL;
str = GetMemory();//str指向的空间已经还给操作系统,str变成野指针
printf(str);//对野指针的解引用,非法访问
}
6.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);
}
正确写法:
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;
}
6.4 释放空间后要将指针置为空
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);//释放内存后要及时将指针置为空
if(str != NULL)//指针没有置为空,判断为真,执行语句
{
strcpy(str, "world");
printf(str);
}
}
正确写法:
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
str = NULL;//!!!!!!!!!
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
7. 柔性数组
C99中结构体最后一个元素允许是未知大小的数组,这个数组就叫做结构体的【柔性数组】成员
7.1 柔性数组的定义
#include<stdio.h>
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
typedef struct st_type1
{
int i;
int a[];//柔性数组成员
}type_b;
int main()
{
printf("%d\n", sizeof(type_a));//4,结构体的大小不包括柔性数组的大小
printf("%d\n", sizeof(type_b));//4
return 0;
}
7.2 柔性数组的特点
1. 结构体中的柔性数组前面必须至少有一个其他成员
2. sizeof返回的这种结构体的大小不包括柔性数组的内存
3. 包含柔性数组的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小
7.3 柔性数组的使用
typedef struct st_type1
{
int i;
int a[];
}type_a;
int main()
{
int i = 0;
type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
//柔性数组a获得了100个整型大小的空间
p->i = 100;
for (i = 0; i < 100; i++)
{
p->a[i] = i;
}
free(p);
p = NULL;
return 0;
}
完结,撒花!🌸🌸🌸