数据结构拓展:详解realloc(C++)
前言
在C++中,realloc
是C标准库提供的一个内存管理函数,用于动态调整已分配内存块的大小。尽管C++更推荐使用 new
/delete
或智能指针,但在某些场景(如与C代码交互或底层内存操作)中仍可能用到 realloc
。以下是详细分析:
一、realloc
的核心行为
void* realloc(void* ptr, size_t new_size);
- 功能:调整
ptr
指向的内存块大小(原内存块由malloc
/calloc
/realloc
分配)。 - 返回值:
- 成功:返回指向新内存块的指针(可能与原指针不同)。
- 失败:返回
NULL
,原内存块保持不变。
- 底层逻辑:
- 若
new_size
为0且ptr
非空:等价于free(ptr)
,返回NULL
。 - 若
ptr
为NULL
:等价于malloc(new_size)
。 - 若当前内存块后有空闲空间:直接扩展内存,原数据保留。
- 若空间不足:分配新内存块 → 复制原数据 → 释放旧内存块。
- 若
二、realloc
在C++中的风险
1. 不适用于非POD类型
- 问题:
realloc
不会调用构造函数或析构函数,可能导致对象生命周期管理错误。
struct NonPOD {
int* ptr;
NonPOD() { ptr = new int(42); } // 构造函数分配内存
~NonPOD() { delete ptr; } // 析构函数释放内存
};
NonPOD* arr = (NonPOD*)malloc(10 * sizeof(NonPOD));
// 错误!构造函数未被调用
arr = (NonPOD*)realloc(arr, 20 * sizeof(NonPOD));
// 若内存地址改变,旧内存被释放,但析构函数未执行 → 内存泄漏
2. 返回值处理不当导致内存泄漏
- 错误示例:
int* ptr = (int*)malloc(100);
ptr = (int*)realloc(ptr, 200); // 若 realloc 失败,ptr 被设为 NULL,原内存泄漏!
正确做法:使用临时变量保存结果。
int* tmp = (int*)realloc(ptr, 200);
if (tmp) {
ptr = tmp; // 成功后再赋值
} else {
// 处理失败,原 ptr 仍有效
}
三、C++中的替代方案
1. 使用 new
/delete
和手动复制
int* old_arr = new int[10];
int* new_arr = new int[20];
std::copy(old_arr, old_arr + 10, new_arr);
delete[] old_arr; // 正确释放旧内存
2. 使用 std::vector
- 推荐方案:自动处理内存分配、复制和释放。
std::vector<int> vec(10);
vec.resize(20); // 自动重新分配内存并复制数据
3. 智能指针 + realloc
替代方案
- 若必须使用
realloc
,可结合unique_ptr
自定义删除器:
auto deleter = [](int* ptr) { free(ptr); };
std::unique_ptr<int[], decltype(deleter)> ptr((int*)malloc(100), deleter);
// 调整大小
int* tmp = (int*)realloc(ptr.get(), 200);
if (tmp) {
ptr.release(); // 解除原有指针管理
ptr.reset(tmp); // 接管新指针
}
四、realloc
的适用场景
场景 | 是否推荐 | 原因 |
---|---|---|
处理POD类型(如基本类型) | 谨慎使用 | 需手动管理内存,但无构造/析构函数风险 |
非POD类型(类对象) | 禁止使用 | 破坏对象生命周期,导致未定义行为 |
高性能动态数组 | 有限使用 | 可能比 new + 复制更快,但需确保内存连续且类型简单 |
五、realloc
的优缺点
优点 | 缺点 |
---|---|
可能原地扩展内存,减少复制开销 | 不适用于C++对象(无法处理构造/析构) |
语法简洁 | 返回值处理不当易导致内存泄漏 |
兼容C代码 | 内存分配失败需手动回退 |
需显式类型转换(C++中强制要求) |
六、总结
- 在C++中尽量避免直接使用
realloc
,优先选择std::vector
、std::unique_ptr
或手动new
/delete
。 - 若必须使用:
- 仅用于POD类型(如基本类型、结构体无构造/析构函数)。
- 严格检查返回值,避免内存泄漏。
- 考虑与智能指针结合,增强安全性。
扩展:代码(初阶)
SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
//定义动态顺序表结构
typedef int SLDataType;
struct SeqList {
int* arr;
int capacity;
int size;
};
typedef struct SeqList SL;
//初始化
void SLInit(SL* ps);
//销毁
void SLDestory(SL* ps);
//插入数据
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
#include"SeqList.h"
void SLTest01() {
SL s;
SLInit(&s);
}
int main() {
SLTest01();
return 0;
}
SeqList.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
#include"SeqList.h"
//初始化
void SLInit(SL* ps) {
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//销毁
void SLDestory(SL* ps) {
if (ps->arr != NULL) {
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//插入数据
void SLPushBack(SL* ps, SLDataType x) {
//判断空间是否充足
if (ps->size == ps->capacity) {
//增容
ps->arr=realloc
}
ps->arr[ps->size] = x;
ps->size++;
}