动态内存分配的存在意义 我们掌握的开辟空间的方式有: int val =20;char arr[10]={0}; 但是这种方法开辟的空间有两个特点 空间开辟的大小是固定的 数组在声明的时候必须指定数组的长度,
- 动态内存分配的存在意义
我们掌握的开辟空间的方式有:
int val =20;
char arr[10]={0};
但是这种方法开辟的空间有两个特点
- 空间开辟的大小是固定的
- 数组在声明的时候必须指定数组的长度,它所需要的内存在编译时分配。
C语言是可以创建变长数组的,在C99中增加了这一项,但是,许多编译器并不支持这一方法,所以变长数组不普遍
有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟的空间的方式就不能满足了,因此我们需要动态内存分配,而动态内存分配是在堆上开辟的空间。
- 动态内存函数的介绍
- malloc
#include <stdlib.h>
void* malloc(size_t size);
- 如果创建成功返回一个指针,该指针指向开辟空间的初始位置
- 如果开辟失败则返回空指针,因此malloc的返回值一定要做检查
- 返回类型的void*,所以malloc并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
- 如果size为0,malloc的行为标准是未定义的,取决于编译器
- free
C语言提供的的一个free函数,是专门用来做动态内存的释放和回收的,函数原型如下
void free(void* ptr);
free函数用来释放函数开辟的内存
- 如果ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果ptr是空指针,则free什么都不做
- 但是使用free函数之后,被指定的指针还是能找到那块位置,所以需要将改指针手动置为空指针
- calloc
void* calloc(size_t num.size_t size);
- 函数的功能是为num个大小为size的元素开辟空间,并把空间的每个字节初始化为0
- 与malloc的区别只在于calloc会返回地址把之前申请的空间的每个字节初始化为全0
- realloc
void* realloc(void* ptr,size_t size);
- ptr是要调整的内存地址
- size是调整之后的新大小
- 返回值为调整之后的内存地址的起始位置
- 这个函数调整原内存空间的基础上,还会将原内存中的数据拷贝到新内存中
情况1:原有空间之后有足够大的空间----要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化
情况2:原有空间之后没有足够大的空间----在堆空间上另找一个合适大小的空间来使用,这样函数返回的是一个新的内存地址
- 常见的动态内存错误
- 对空指针进行解引用操作是违法的----一定要判断动态内存开辟(malloc,calloc,realloc)的返回值是否为空指针
- 对动态开辟内存的越界访问
- 对非动态开辟内存的空间进行free
- 使用free释放动态开辟内存的一部分
int main() {
int* p = malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
int i = 0;
for (i = 1; i < 5; i++)
{
*p++ = i;
}
}
//free(p);---这是不允许的,因为p的位置已经变化了,free不能释放动态内存开辟的空间的一部分
//p = NULL;
return 0;
}
- 对同一块内存空间多次释放---可以准寻谁开辟谁释放的原则
- 对动态开辟的内存忘记释放(内存泄漏)
- 几个经典的笔试题
第一道
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void test(void)
{
char* ptr = NULL;
GetMemory(ptr);
strcpy(ptr, "hello,world");
printf(ptr);
}
int main() {
test();
return 0;
}
- 代码运行程序会出现崩溃的现象
- 程序存在内存泄漏的问题---str以值传递的形式给p,p是getmemory函数的一个形参,只在函数内部有效,等GetMemory函数返回之后,动态开辟的内存尚未释放,并且无法访问,所以会造成内存泄漏
C/C++内存布局
- 柔性数组
定义:C99结构体中最后一个元素允许是未知大小的数组,这就是柔型数组
特点:
- 结构中的柔型数组成员前面必须至少一个其他成员。
- sizeof返回的这种结构大小不包括柔性数组的内存。
- 包含柔型数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔型数组的预期大小
优势:
- 方便内存释放
- 这样有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片(也没多高,因为少不了偏移量的加法寻址)
局部性原理:当你访问一个数据的时候,接下来80%概率访问的是它周边的数据
结构体指针指向的结构体类型中的指针类型,获取的是指针的值,而不是地址,指向的数组类型,获取的是地址
struct test{
int i;
short c;
char *p;
char s[10];
};
int main(){
struct test *pt=NULL;
printf("&s = %x\n", pt->s); //等价于 printf("%x\n", &(pt->s) );
printf("&i = %x\n", &pt->i); //因为操作符优先级,我没有写成&(pt->i)
printf("&c = %x\n", &pt->c);
printf("&p = %x\n", &pt->p);
return 0;
}