我们之前开辟的空间,大小固定,且在申明数组的时候,必须指定数组的长度。但是有时候我们需要的空间大小在程序运行的时候才知道,这就得动态内存开辟出马了。
目录
1.malloc和free
2.calloc
3.realloc
4.常见动态内存错误
5.经典笔试题
//两种动态内存传参
//非法访问(返回栈空间问题)
6.C/C++程序的内存开辟
7.柔性数组
1.malloc和free
头文件:#include<stdlib.h>
格式:void* malloc ( size_t size )
size为申请的字节大小;
功能:向内存申请一块连续可用的空间,并返回指向这块空间的指针。
没有初始化,不赋初值时默认为随机值;
如果开辟成功,则返回一个指向开辟好空间的指针;
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查;开辟空间过大时,也会开辟失败,Not enough space。
申请到的空间没有初始化,直接返回起始地址。
头文件:#include<stdlib.h>
格式:void free (void* ptr);
功能:释放动态开辟的空间。
如果参数ptr指向的空间不是动态开辟的,那么free的行为是未定义的;
如果参数ptr是NULL指针,那么free函数什么都不做。
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main()
{
//申请40个字节,用来存放10个整型
int* p = (int*)malloc(40);
//开辟失败,用strerror报错
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//开辟成功,存放1~10
int i = 0;
for (i = 0; i < 10; i++)
{
//赋值
*(p + i) = i + 1;
//打印
printf("%d ", *(p + i));
}
//用free来释放申请的空间
free(p);
p = NULL;//手动将p置为空指针
return 0;
}
2.calloc
头文件:#include<stdlib.h>
格式:void* calloc (size_t num, size_t size);
num是开辟的元素个数,size是每个元素的大小;
功能:申请空间后会把空间初始化为0,然后返回起始地址。
开辟内存成功或失败同malloc。
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
//开辟失败
if (p == NULL )
{
printf("%s\n", strerror(errno));
return 1;
}
//开辟成功
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
3.realloc
头文件:#include<stdlib.h>
格式:void* realloc (void* ptr, size_t size);
功能:对动态开辟的内存进行调整。
情况1:后面有足够的空间可以扩容,直接在后面续上新的空间,返回旧空间的起始地址;
情况2:后面没有足够的空间可以扩容,找一个满足空间大小的新的连续空间,把旧的空间里的数据拷贝到新的空间,并且把旧的空间释放,返回新空间的地址;
情况3:开辟失败,返回NULL。
若传的是空指针NULL,则相当于malloc。
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
if (NULL == p)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = 1;
}
//不够,要增加5个整型的空间
int* ptr = (int*)realloc(p, 5 * sizeof(int));
if (ptr != NULL)//开辟成功
{
p = ptr;
ptr = NULL;//更安全
}
//继续使用空间
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
p = NULL;
return 0;
}
4.常见动态内存错误
1> 对NULL指针的解引用操作:返回值一定要检查;
2> 对动态开辟内存的越界访问:没开辟的内存不能访问;
3> 对非动态开辟内存使用free释放:free只能释放动态开辟内存;
4> 使用free释放一块动态开辟内存的一部分:释放的p指针必须指向起始地址;
5> 对同一块内存多次释放:避免重复释放或在每次释放后牢记将p指针置为NULL;
6> 动态开辟内存忘记释放(内存泄漏):free与内存函数一定要成对使用。
5.经典笔试题
这几个题目原题(出处:《高质量的C/C++编程》)是给出错误的代码让我们改错,这里有的就直接放上正确的代码了哈~
//两种动态内存传参
char* GetMemory1()//传值,返回地址
{
char* p = (char*)malloc(100);
return p;
}
void test1()
{
char* str = NULL;
str = GetMemory1();
strcpy(str, "hello world!");
printf(str);//哦吼吼还可以这样用
free(str);//一定不能忘!
str = NULL;
}
void GetMemory2(char** p)//传地址
{
*p = (char*)malloc(100);
}
void test2()
{
char* str = NULL;
GetMemory2(&str);
strcpy(str,"hello world!");
printf(str);
free(str);//一定不能忘!
str = NULL;
}
int main()
{
test1();
test2();
return 0;
}
//非法访问(返回栈空间问题)
char* GetMemory()
{
char p[] = "hello world!";
return p;
}
void test()
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
test();
return 0;
}
上述代码存在问题,打印结果是一堆乱码(随机值)。
解释:进入GetMemory函数后,内存为p数组开辟了一段空间存放hello world, 并返回首元素地址p,但是在出GetMemory函数后该内存就被还给了操作系统,该数组就被销毁了,此时再利用str访问p指向的空间就会形成非法访问,打印乱码。
将 char p[] 改为 char* p 或在前面加上 static 即可成功打印。
6.C/C++程序的内存开辟
7.柔性数组
定义:在C99标准下,结构体中的最后一个元素允许是未知大小的数组,这就是『柔性数组』成员。
特点:1> 结构中的柔性数组成员前面至少有一个其他成员;
2> sizeof 返回的这种结构体大小不包括柔性数组的内存;
3> 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<errno.h>
struct S
{
int n;
char arr[];//或arr[0]
//表示数组大小未知,不是大小无限大
};
int main()
{
printf("%d\n", sizeof(struct S));//4
//创建空间
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(char));//多开辟空间适应柔性数组大小
ps->n = 100;
int i = 0;
//赋值
for (i = 0; i < 10; i++)
{
ps->arr[i] = 'c';
}
//打印
for (i = 0; i < 10; i++)
{
printf("%c", ps->arr[i]);//cccccccccc
}
//增容
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(char));
if (ptr != NULL)
{
ps = ptr;
}
else
{
perror("realloc");
return 1;
}
//使用
printf("%d\n", sizeof(struct S));//还是4
//释放
free(ps);
ps = NULL;
return 0;
}
结束!!明天考C啦,Best wishes to me !