有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
我们之前常用的内存开辟一般是变量和数组,他们都是一次开辟固定大小的内存空间,不够灵活
本次介绍的内存开辟函数有三个malloc、callc、realloc
开辟后的内存在使用结束后要释放free这个函数用于释放内存
所介绍的内存函数声明都在**
void* malloc (size_t size);
这个函数的作用是向内存申请一块连续可用的空间,并返回指向这块空间的指针。
这个函数返回值是void*,一般需要强转成需要的类型,并拿指针接收申请的内存
函数的 **size_t size **参数是申请字节的个数
注意:如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己 来决定。
如果参数 size为0,malloc的行为是标准是未定义的,取决于编译器。
void free (void* ptr);
本函数只有一个参数** void* ptr **是指向生气空间首位的指针
free函数用来释放动态开辟的内存。用完的内存一定要记得释放!!!
#include
int main()
{
int* sp = (int*)malloc(40);
if (sp == NULL)//申请失败打印错误原因并跳出函数
{
perror("malloc");
return 1;
}
//给申请的内存赋值
for (int i = 0; i < 9; i++)
{
sp[i] = i + 1;
}
//打印放入的值
for (int i = 0; i < 9; i++)
{
printf("%d ", sp[i]);
}
free(sp);//用完后把内存释放
sp == NULL;
return 0;
}
以上是很标准的内存使用形式,加上判断语句和最后的内存释放很关键,要内化成习惯,不然程序的运行容易出现问题
calloc和malloc很相像都是用于初始化内存的calloc初始化的内存里放的是0
void* calloc (size_t num, size_t size);
其中返回值和mallc一样
有两个参数 size_t num 是开辟多少空间
size_t size 是每个空间占多少内存
realloc这个函数让内存管理更加灵活,可以调整之前分配的内存。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
void* realloc (void* ptr, size_t size);
函数有两个参数
- realloc在调整内存空间分有两种情况,如原有空间内存大小够用则返回原地址,反之则另外开辟。
int main()
{
int* sp = (int*)malloc(40);
if (sp == NULL)//申请失败打印错误原因并跳出函数
{
perror("malloc");
return 1;
}
//写入内存
for (int i = 0; i < 10; i++)
{
sp[i] = i+1;
}
//内存用满需要扩容
int* tmp = realloc(sp, 400);
if (tmp != NULL)
{
sp = tmp;
tmp == NULL;
}
else
{
perror("realloc");
return 1;
}
free(sp);//用完后把内存释放
sp == NULL;
return 0;
}
这样我们就介绍完了用于内存申请的三个主要函数下面是我们在使用的时候经常会遇到的问题,值得注意。
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分 配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员
typedef struct S
{
char x;
int a[0];//柔性数组成员
}S;
上面那种形式有些编译器会报错可以写成下面这种
typedef struct S
{
char x;
int a[];//柔性数组成员
}S;
如上的 int a[0] 就是柔性数组
例如:
typedef struct S
{
char x;
int a[];//柔性数组成员
}S;
int main()
{
printf("%d", sizeof(S));
return 0;
}
这段代码的输出结果为size没有计算柔性数组的空间
typedef struct S
{
char x;
int a[];//柔性数组成员
}S;
int main()
{
//申请空间包括前面的成员加上柔性数组所需的空间
S* p = (S*)malloc(sizeof(S) + 100 * sizeof(int));
for (int i = 0; i <= 100; i++)
{
p->a[i] = i;
}
free(p);
p = NULL;
return 0;
}
这样柔性数组成员a,相当于获得了100个整型元素的连续空间。
其实柔性数组的代码也可以设计为用int*代替数组:
typedef struct S
{
char x;
int* a;
}S;
int main()
{
//申请空间包括前面的成员加上柔性数组所需的空间
S* p = (S*)malloc(sizeof(S));
p->a = (int*)malloc(sizeof(int) * 100);
for (int i = 0; i <= 100; i++)
{
p->a = i;
}
free(p->a);
p->a = NULL;
free(p);
p = NULL;
return 0;
}
上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有好处:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给 用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
以上就把整个动态内存管理做了介绍,现阶段学习的还比较浅,如果有什么不对的地方欢迎指正。