1.动态内存函数
(1)malloc
void* malloc (size_t size);
malloc是向内存的堆区申请一片空间(局部变量申请的空间是堆区),库函数中size是申请空间的大小,返回值是这一片空间的第一个字节的地址,返回时我们要强制类型转换,如果返回是NULL,那么就是开辟失败。下面是使用示例
#define _CRT_SECURE_NO_WARNINGS
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
//创建
int* p =(int*)malloc(40);
//检验
if (p==NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
int j = 0;
for (j = 0; j < 10; j++)
{
printf("%d ", *(p + j));
}
//销毁
free(p);
p = NULL;
return 0;
}
使用mallco时我们要进行检查,确认是否返回的值是否是NULL;我们在结束使用时,为了防止内存丢失,我们要及时释放内存,防止形成内存泄漏,在我们释放内存后为了防止形成野指针,我们要将其赋成NULL。
(2)calloc
calloc的用法与malloc类似,返回值相同。
void* calloc (size_t num, size_t size);
calloc开辟的空间是num和size的乘积,此外,calloc在开辟空间是时顺便进行初始化,相当于malloc+memset的结果。
(3)realloc
realloc可以实现动态内存管理,realloc是修改malloc和calloc开辟的空间。
void* realloc (void* ptr, size_t size);ptr是要调整的空间的首元素的地址,size是调整后空间的字节大小。返回值是内存调整后的内存起始位置。有两种情况:
1.调整的空间后面满足调整所需的空间大小,就直接将后面的空间开辟,返回原来的地址
2.如果调整的空间不满足所需要的空间,就会子啊内存中寻找空间符合的条件。返回的是新内存空间地址,原来的空间就会返回给系统。
使用实例如下
{
int* p = (int*)calloc(12, 3);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
int* p1 = (int*)realloc(p, 40);
if (p1 != NULL)
p = p1;
int j = 0;
for (j = 0; j < 9; j++)
{
printf("%d ", *(p + j));
}
//销毁
free(p);
p = NULL;
return 0;
}
2.常见动态内存错误
(1)对NULL指针的解引用操作
对于空指针,我们不能直接进行解引用操作,否则就会报错。我们应该先将指针赋值后在进行解引用,如下。还有我们在释放malloc开辟的空间时,我们一定要将其赋成空指针,防止形成野指针。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int *)malloc(40);
*p = 10;
return 0;
}
编译器就会报警告:
我们应该改成
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int *)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
*p = 10;
free(p);
p = NULL;
return 0;
}
(2)对动态开辟空间的越界访问
动态内存时我们使用时可能会超出开辟空间的范围,如下
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int *)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i < 12; i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
(3)对非动态开辟内存使用free释放
对于非动态开辟的空间进行free释放是不能对其操作的编译器会报错,这是由于二者在内存中存放的位置是不同的原因,前者存放在栈区,后者存放在堆区。
(4)4 使用free释放一块动态开辟内存的一部分
这个是由于我们更改了我们存放开辟空间的地址,使它不指向开辟的空间的第一个地址。如下
实际上编译器在运行时会报错,所以我们要修改成
(5)对同一块动态内存多次释放和动态开辟内存忘记释放(内存泄漏)
我们对一个由动态开辟的空间使用两次free时会报错,我们在一次free后进行赋值成空指针,可以防止这种情况,这就由于free在释放空指针时什么事不会发生。
动态开辟时忘记释放,这个空间就会将不会还给操作系统,这个空间就不会被其他人使用,这块空间就被消失了。
3.柔性数组
柔性数组指的是,结构体成员的最后一个设置成不指定数组大小的形式就是不指定数组,大小是未知的;格式是:
struct fl
{
int a;
int b[];
};
上面结构体中数组b就是柔性数组。结构体大小并不将柔性数组纳入计算范围,柔性数组的大小我们可以使用malloc和calloc进行开辟空间,用ralloc进行调整空间大小。使用实例如下:
我们同样使用地址来实现同样的效果:
二者是有本质上的区别,前者是p指针指向的就是一片空间,这个空间int *后面可以调整;而后者p指针指向这个空间就是 包含了这个int和int*指针来操控,这个int*指针就是指向了那个可调整的空间,有点像指针套指针。
此外,使用前者加快访问速度和方便了内存释放。