动态内存管理

分配内存的函数

malloc

void* malloc(size_t size);

如果开辟成功,则返回一个指向开辟好空间的指针
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
如果参数size为0,malloc的行为是标准是未定义的,取决于编译器

free

void free(void* ptr);

如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
如果参数ptr是NULL指针,则函数什么事都不做

calloc

void* calloc(size_t num, size_t size);

函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0

realloc

void* realloc(void* ptr, size_t size);

如果size比原来小,则前size个字节不变,后面的数据被截断,如果size比原来大,则原来的数据全部保留,这里存在两种情况:
情况1:原有空间之后有足够大的空间,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化
情况2:原有空间之后没有足够大的空间,在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址

如果参数ptr是空指针,则类似malloc,返回指向开辟好空间的指针
如果参数size为0,则类似free,返回空指针

alloca

void* alloca(size_t size);

参数size是请求分配的字节数,返回所分配内存空间的首地址,如果size太大导致栈空间耗尽,结果是未定义的,alloca函数不是在堆上分配空间,而是在调用者函数的栈帧上分配空间,类似于C99的变长数组,当调用者函数返回时自动释放栈帧,所以不需要free。这个函数不属于C标准库,而是在POSIX标准中定义的

定义一个不易发生错误的内存分配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// alloc.h
#pragma once
#include <stdlib.h>
#define malloc 不要直接调用malloc!
#define MALLOC(num, type) (type*)alloc(num * sizeof(type))
extern void* alloc(size_t size);

// alloc.c
#include <stdio.h>
#include "alloc.h"
#undef malloc
void* alloc(size_t size) {
void* new_mem;
new_mem = malloc(size);
if (!new_mem) {
printf("Out of memory!\n");
exit(1);
}
return new_mem;
}

// main.c
#include "alloc.h"
int main() {
int* new_memory;
new_memory = MALLOC(25, int);
/* ... */
return 0;
}

柔性数组

C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员

1
2
3
4
typedef struct st_type {
int i;
int a[0]; //柔性数组成员,有些编译器会报错无法编译可以改成:int a[];
} type_a;

柔性数组的特点

结构中的柔性数组成员前面必须至少一个其他成员
sizeof返回的这种结构大小不包括柔性数组的内存
包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

柔性数组的优点

  1. 方便内存释放,把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉,不用担心结构体内的成员也需要free
  2. 利于访问速度,连续的内存有益于提高访问速度,也有益于减少内存碎片

new/delete操作符

操作:new data-type;
data-type可以是包括数组在内的任意内置类型数据,也可以是自定义的任何数据类型
如:
int* p = new int(3); delete p;在堆上申请空间,创建对象int并初始化为3
int* p = new int[3]; delete[] p在堆上申请3个int大小空间,创建3个对象
new
new底层调用先operator new函数申请空间,然后执行构造函数,完成对象的构造
operator new底层调用malloc,当malloc申请空间成功时直接返回;申请空间失败尝试执行应对措施,若无应对措施则抛异常

delete
先在空间上执行析构函数,完成对象中资源的清理工作,调用operator delete函数释放对象的空间

new T[N]
先调用operator new[](在operator new[]中实际调用operator new函数完成N个对象空间的申请)然后在申请的空间上执行N次构造函数

delete[]
先在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
然后调用operator delete[]释放空间(在operator delete[]中调用operator delete来释放空间)

定位new表达式

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
Type* ptr = (Type*)malloc(sizeof(Type)); new(ptr) Type;如果构造函数有参数,需传参