C 语言中的内存分配与释放

在使用 C 语言编程的过程中,不可能所有的变量都直接分配在栈上。并且栈上的空间是十分有限的。这个时候就需要手动的在堆上分配和管理内存。

主要的几个函数为:

  • void *malloc(size_t size);
  • void *calloc(size_t num, size_t size);
  • void *realloc(void* ptr, size_t new_size);
  • void free(void *ptr);

别忘记引入头文件stdlib.h

内存分配

malloc

1
2
void* malloc(size_t size);
// memory allocation
  • 此函数会在堆空间上分配一片 连续的 size 个字节大小 的内存块。此函数 不会对内存块中的数据进行初始化,内存块中的数据是随机未定义的。
  • 如果 分配成功 ,此函数会返回指向该内存块地址(首字节地址) 的指针。注意返回的指针类型是void 指针,在操作之前需要进行转换。
  • 如果 分配失败 ,此函数会返回一个 空指针(NULL)。

calloc

1
2
3
4
5
6
/**
* num 表示要分配的元素数量
* size 表示每个元素的内存字节大小
*/
void* calloc(size_t num, size_t size);
// cleared allocation
  • 此函数也会在堆空间上分配一片连续的内存空间,但不同的是,它基于元素的个数以及每个元素的大小来进行内存分配,所以calloc 常用于在堆上分配数组的内存空间
  • 初始化为零 :calloc 最重要的特性之一是它会自动将分配的内存初始化为零。 这意味着不仅仅是分配内存,它还清零所有内存。
  • 返回值在分配成功和失败时,和 malloc 是一致的。

使用 malloc 与 calloc 的建议

  • malloc 由于不需要初始化 0 值,性能可能会更好一些。所以在特别在意性能以及内存确实手动初始化时,优先选择用 malloc 函数。
  • calloc 的优点是安全。如果使用 malloc 函数分配内存,那么内存块中的所有元素都只有一个随机值,此时若忘记初始化直接使用这些随机值就会产生未定义行为,这是非常危险的。而这个危险,可以通过使用 calloc 函数解决。
  • 在实际应用中,特别是当程序安全和正确性是首要考虑时,在两个函数都可用时,那么请选择使用 calloc 函数。

realloc

1
2
3
4
5
6
/**
* ptr 指向原来已分配内存的内存块
* new_size 新的内存块大小
*/
void* realloc(void* ptr, size_t new_size);
// reallocation
  • 如果 ptr 指针是一个空指针,那么该函数的行为和 malloc 一致。
  • 如果 new_size 的取值为 0,那么该函数的行为和 free 一致。
  • 当 new_size 的取值和已分配的内存块大小一致时,此函数不会做任何操作。
  • 当 new_size 的取值比已分配的内存块小时,会在旧内存块的尾部 (高地址) 截断,被截断抛弃的内存块会被自动释放。
  • * 当 new_size 的取值比已分配的内存块大时,会尽可能地尝试原地扩大旧内存块(这样效率高);如果无法原地进行扩大,则会在别处申请空间分配 new_size 大小的新内存块,并将旧内存块中的数据全部复制进去后,将旧内存块自动释放。*
  • 不管采用哪种方式扩展旧内存块,新扩展部分的内存区域都不会初始化,仍只具有随机值。
  • 如果 realloc 函数分配内存空间成功,它会返回指向新内存块的指针。
  • 若失败,仍会返回空指针,且不会改变旧内存块。

内存释放

free

1
void free(void *ptr);
  • 参数必须是堆上申请内存块的地址 ( 首字节地址 ),不能传递别的指针, 否则会引发未定义行为
  • free 函数并 不会修改它所释放的内存区域中存储的任何数据 。free 的作用 仅仅是告诉操作系统这块内存不再被使用 了,可以将其标记为可用状态,以供将来的内存分配请求使用。
  • 释放后的内存区域中的数据一般仍然会继续存在,直到被下一次的内存分配操作覆盖。当然即便 free 前的原始数据一直存在未被覆盖,这片内存区域也不再可用了,因为你不知道什么时候数据就会被覆盖掉了。
  • free 函数不会修改传入指针指向的内容,更不会对实参指针本身做任何修改。