C++中单个动态数组供两个结构体对象使用引发的内存错误排查
问题描述
在多个编译器中运行代码后得到不同错误结果:
- 出现退出码
-1073741819 (0xC0000005) - 触发错误提示
free(): double free detected in tcache 2 - 调试时触发信号
SIGTRAP (Trace/breakpoint trap)和SIGSEGV (Segmentation fault)
我99%确定问题和两次释放ships数组有关,但为什么不对两个玩家都调用func(),或者修改代码其他部分时就不会报错?具体问题是什么?该如何修复?
补充说明
我使用动态数组而非vector是为了练习学习;我知道这种内存分配方式会产生内存碎片,但此处不会修改矩阵大小,所以不应有问题。
代码
struct point_t { int x; int y; point_t(); }; point_t::point_t() { this->x = 0; this->y = 0; } struct ship_t { int size; point_t end_coords[2]; }; struct player_t { int **map; int map_size; ship_t *ships; int ships_count; player_t(int, ship_t *, int); ~player_t(); }; player_t::player_t(int map_size, ship_t *ships, int ships_count) { this->map = nullptr; this->map_size = map_size; this->ships = ships; this->ships_count = ships_count; } player_t::~player_t() { // Free map memory // Free each column (sub-array) for(int i = 0; i < map_size; i++) { delete[] map[i]; } // Free each row (array of pointers) delete[] map; map = nullptr; map_size = 0; delete[] ships; ships = nullptr; ships_count = 0; } void func(player_t &player) { player.map = new int *[player.map_size]; for(int i = 0; i < player.map_size; i++) { player.map[i] = new int[player.map_size]; for(int j = 0; j < player.map_size; j++) { player.map[i][j] = 0; } } } int main() { ship_t *ships = new ship_t[(5 * 5) / 2]; for(int i = 0; i < 5; i++) { ships[i].size = i + 1; } player_t player1 = player_t(5, ships, 5); player_t player2 = player_t(5, ships, 5); func(player1); func(player2); return 0; }
问题分析与修复
具体问题
- 重复释放同一内存块:
main中只分配了一次ships数组,然后把这个指针同时传给player1和player2。程序结束时,两个player_t对象的析构函数会先后执行delete[] ships,导致同一内存块被释放两次——这就是double free等错误的核心原因。 - 未调用func时不报错的偶然性:如果不调用
func,player.map始终是nullptr,对nullptr执行delete是C++标准允许的安全操作,程序不会立刻崩溃。但重复释放ships的问题依然存在,只是内存管理机制的偶然性让错误没被触发;调用func后,map被正常分配并释放,之后执行delete[] ships时,第二次释放就会直接触发内存校验错误,所以报错更明显。
修复方案
根据需求不同,有两种常见解决方式:
方式1:明确内存所有权
给player_t添加所有权标志,只有拥有所有权的对象才负责释放ships:
struct player_t { int **map; int map_size; ship_t *ships; int ships_count; bool owns_ships; // 添加所有权标志 player_t(int, ship_t *, int, bool owns = false); ~player_t(); }; player_t::player_t(int map_size, ship_t *ships, int ships_count, bool owns) { this->map = nullptr; this->map_size = map_size; this->ships = ships; this->ships_count = ships_count; this->owns_ships = owns; } player_t::~player_t() { // 释放map逻辑不变 for(int i = 0; i < map_size; i++) { delete[] map[i]; } delete[] map; map = nullptr; map_size = 0; // 仅拥有所有权时释放ships if (owns_ships) { delete[] ships; ships = nullptr; ships_count = 0; } } // main中初始化时指定所有权 int main() { ship_t *ships = new ship_t[(5 * 5) / 2]; for(int i = 0; i < 5; i++) { ships[i].size = i + 1; } player_t player1 = player_t(5, ships, 5, true); // player1拥有ships所有权 player_t player2 = player_t(5, ships, 5); // player2不拥有 func(player1); func(player2); return 0; }
方式2:深拷贝ships数组
让每个player_t对象复制一份独立的ships数组,各自释放自己的内存:
player_t::player_t(int map_size, ship_t *ships, int ships_count) { this->map = nullptr; this->map_size = map_size; this->ships_count = ships_count; // 深拷贝ships数组 this->ships = new ship_t[ships_count]; for (int i = 0; i < ships_count; i++) { this->ships[i] = ships[i]; // ship_t是POD类型,直接赋值即可完成拷贝 } } // 原析构函数的delete[] ships逻辑保持不变
这种方式下main不需要修改,每个玩家拥有独立的ships副本,析构时释放各自的内存,不会出现重复释放问题。
内容的提问来源于stack exchange,提问作者ashamedgap
相关产品推荐
相关产品推荐

