You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题分析与修复

具体问题

  1. 重复释放同一内存块:main中只分配了一次ships数组,然后把这个指针同时传给player1和player2。程序结束时,两个player_t对象的析构函数会先后执行delete[] ships,导致同一内存块被释放两次——这就是double free等错误的核心原因。
  2. 未调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:07:08