C/C++动态内存管理,malloc,calloc,realloc的区别,动态内存中的错误汇总

🎬 胖咕噜的稞达鸭个人主页
🔥 个人专栏 : 《数据结构《C++初阶高阶》
《Linux系统学习》
《算法日记》

⛺️技术的杠杆,撬动整个世界!


malloc,calloc和realloc的区别

malloc:

按字节数分配:void * malloc(size_t size);

返回的是未初始化的内存

失败之后返回的是nullptr;

cpp 复制代码
int *p = static_cast<int*>(std::malloc(10 * int));
if(p == nullptr)
{
    perror("malloc failed");
}
std::free(p);

calloc:

分配并清零:void* calloc(size_t n, size_t size);

分配 n * size 字节,并把这块内存 全部置 0

失败返回 nullptr

cpp 复制代码
int *p = static_cast<int*>(std::calloc(10, sizeof(int)));
if(p == nullptr)
{
    perror("calloc failed");
}
std::free(p);

realloc

调整已分配块大小:void* realloc(void* ptr, size_t newSize);

只适用于 malloc/calloc/realloc 得到的指针(或 nullptr)

可能原地扩/缩;也可能 搬家到新地址并复制旧内容

失败返回 nullptr,原指针仍然有效(不会被释放)

注意:realloc(p, 0) 行为实现相关(有的等价 free,有的返回可释放指针),不建议这么写。

cpp 复制代码
int *p = static_cast<int*>(std::calloc(10, sizeof(int)));
if(!p)return;
//此时calloc在申请内存的过程中成功了,原指针
//此时应该用realloc原地扩容,或者缩小
int *q = static_cast<int*>(std::realloc(p, 20 * sizeof(int)));
if(!q)
{
    //realloc失败了,calloc不会变;realloc成功了就得释放原指针p;
    std::free(p);
    return;
}
p = q;//覆盖p的指针
std::free(p);

三者核心区别总结

  1. 是否清零

malloc:不清零(未初始化)
calloc:清零
realloc:扩容时 新增部分不保证清零;缩容则截断

  1. 参数形式

malloc(size)

calloc(count, elemSize)(更不容易写错"元素个数 * 大小")

realloc(ptr, newSize)

  1. 是否改变地址

malloc/calloc:新分配,必然是新指针

realloc:可能返回同一地址,也可能返回新地址(旧指针失效)

  1. 失败语义

malloc/calloc:失败返回 nullptr

realloc:失败返回 nullptr,但 原内存块还在(所以要用临时指针接)

在 C++ 里用它们的"坑"(很重要)

不能和 new/delete 混用

malloc/calloc/realloc 分配的必须 free

new 分配的必须 delete(数组是 delete[])

C++ 更推荐的替代写法

数组/动态缓冲区:std::vector

字符串:std::string

智能指针:std::unique_ptr<T[]>

需要手动增长缓冲:std::vector 的 resize/reserve(代替 realloc 思路)

常见的动态内存的错误:

  1. 对NULL指针的解引用操作
  2. 对动态开辟空间的越界访问
  3. 对非动态开辟内存使用free释放

4.使用free释放一块动态开辟内存的一部分

  1. 对同一块动态内存多次释放

  2. 动态开辟内存忘记释放(内存泄漏)

cpp 复制代码
#include <cstdlib>
#include <cstdio>

static void case1_null_deref() {
  int* p = nullptr;
  // 1) 对 NULL 指针解引用:崩溃/未定义行为
  *p = 123;
}

static void case2_out_of_bounds() {
  int* p = static_cast<int*>(std::malloc(3 * sizeof(int)));
  // 2) 越界访问:未定义行为
  p[3] = 10; // 合法下标是 0,1,2
  std::free(p);
}

static void case3_free_non_heap() {
  int x = 7;
  int* p = &x;
  // 3) 对非动态开辟内存 free:未定义行为
  std::free(p);
}

static void case4_free_partial_block() {
  int* p = static_cast<int*>(std::malloc(10 * sizeof(int)));
  // 4) free 动态内存的一部分:未定义行为
  std::free(p + 3);
  // std::free(p); // 这里再 free 也不行:p 已经处于不可用状态
}

static void case5_double_free() {
  int* p = static_cast<int*>(std::malloc(4 * sizeof(int)));
  std::free(p);
  // 5) 重复释放同一块内存:未定义行为(常见是直接报错/崩溃)
  std::free(p);
}

static void case6_memory_leak() {
  int* p = static_cast<int*>(std::malloc(100 * sizeof(int)));
  p[0] = 1;
  // 6) 忘记释放:内存泄漏(程序结束才会被 OS 回收,但长期运行会越占越多)
  (void)p;
}

int main() {
  std::puts("Pick ONE case to run by uncommenting it.");

  // case1_null_deref();
  // case2_out_of_bounds();
  // case3_free_non_heap();
  // case4_free_partial_block();
  // case5_double_free();
  // case6_memory_leak();

  return 0;
}

面试:柔性数组(Flexible Array Member, C99)

  • 是什么 :结构体最后一个成员写 T data[];,长度运行时决定;sizeof(结构体) 不包含这段尾部数据。
  • 怎么用 :一次分配"头 + 数据"连续内存:malloc(sizeof(Struct) + n),用 len 记录实际长度,free(p) 一次释放。
  • 用在哪:变长消息/协议包(header 固定、payload 可变)、序列化二进制块、变长字符串缓冲。
  • 好处 :一次 malloc/free(更少碎片/更少开销)、内存连续(cache 友好,拷贝/发送方便)。
  • 注意 :必须放在结构体最后;不要 free(p->data)sizeof(data) 得不到运行时长度;C++ 标准不支持(部分编译器可扩展支持)。
c 复制代码
typedef struct {
  size_t len;
  unsigned char data[];
} Packet;

Packet* p = (Packet*)malloc(sizeof(Packet) + n);
if (!p) return;
p->len = n;
// memcpy(p->data, src, n);
free(p);

C/C++中的内存分配

1.选择题:

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

globalVar在哪里?__C__全局变量在数据段

staticGlobalVar在哪里?_C_静态全局变量在静态区

staticVar在哪里?__C__静态局部变量在静态区

localVar在哪里?__A__局部变量在栈区

num1 在哪里?__A__局部变量在栈区

char2在哪里?__A__局部变量在栈区

*char2在哪里?__A__数组在栈区

pChar3在哪里?__A__局部变量在栈区

*pChar3在哪里?D *pChar3得到的是字符串常量字符在代码段

ptr1在哪里?__A__局部变量在栈区

*ptr1在哪里?B*ptr1得到的是动态申请空间的数据在堆区

2.填空题:

sizeof(num1) = 40 ; //数组大小,10个整形数据一共40字节

sizeof(char2) = 5 ; //包括\0的空间 strlen(char2) = 4 ;//不包括\0的长度

sizeof(pChar3) = 4 ; //pChar3为指针 strlen(pChar3) = 4 ;//不包括\0的长度

sizeof(ptr1) = 4;//ptr1是指针

总结:

  1. 局部变量一般在栈上。栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
  2. 动态申请出来的内存在堆上。
  3. 全局变量、静态变量在静态区/数据段。程序结束后由系统释放。
  4. malloc/free 是 C 的动态内存管理方式。
  5. new/delete 是 C++ 的动态内存管理方式。
  6. new/delete 比 malloc/free 多了"构造和析构"这层能力。

问题:C++中malloc申请的内存可以用delete来释放吗?

malloc 申请的内存不能用 delete释放。malloc和 delete分别属于C标准库和C++的内存管理机制,它们的实现原理不同,混用会导致未定义行为。

正确的用法是使用free释放由malloc申请的内存,delete释放new申请的内存,delete[]释放new[]申请的内存。

深入理解这个问题,需要从内存管理的角度来看mallocdelete的本质区别。

  1. malloc是C语言中的函数,用于分配一块未初始化的内存。它只是简单地分配内存,不调用构造函数。所以,用malloc分配的内存只能用free来释放。
  2. new是C++中的运算符,用于分配内存并调用构造函数初始化对象。与之对应,delete既会调用析构函数来清理对象,又会释放内存。这也是为什么用new 分配的内存必须用delete释放的原因。
  3. C++提供了类型安全和自动管理内存的方式。例如,智能指针(如 std::unique_ptrstd::shared_ptr)能够帮助管理动态分配的内存,防止内存泄漏,推荐使用智能指针来管理内存,而不要使用裸指针。
相关推荐
charlie1145141912 小时前
嵌入式C++教程实战之Linux下的单片机编程(6):从点亮第一盏LED开始 —— 我们为什么要用现代C++写STM32
linux·c语言·开发语言·c++·stm32·单片机
沐知全栈开发2 小时前
Ruby 哈希(Hash)
开发语言
linux开发之路2 小时前
C++实现Whisper+Kimi端到端AI智能语音助手
c++·人工智能·llm·whisper·openai
艾莉丝努力练剑2 小时前
【Linux系统:多线程】线程概念与控制
linux·运维·服务器·c++·后端·学习·操作系统
AIminminHu2 小时前
OpenGL渲染与几何内核那点事-项目实践理论补充(二-1-(2):当你的CAD学会“听话”:从鼠标点击到自然语言命令)
c++·人工智能
徒 花2 小时前
Python知识学习03
开发语言·python·学习
我命由我123452 小时前
Vue3 开发中,字符串中的 <br\> 标签被直接当作文本显示出来了,而不是被解析为 HTML 换行标签
开发语言·前端·javascript·vue.js·html·ecmascript·html5
运维行者_2 小时前
MSP网络管理破局者:IPAM+SPM插件终结IP冲突与安全威胁
运维·服务器·开发语言·网络·安全·web安全·php
人间打气筒(Ada)2 小时前
「码动四季·开源同行」go语言:如何处理 Go 错误异常与并发陷阱?
开发语言·后端·golang·defer·panic·errors·并发陷阱