从指针数组中删除元素 - C++

thi*_*ame 3 c++ arrays algorithm pointers

所以我有一个结构指针数组。它看起来像这样:

index                 [0   1   2   3   4]
value of structure     21  7   42  30  NULL
Run Code Online (Sandbox Code Playgroud)

我试图删除存储在索引 2 (42) 处的值。我以为既然这个数组的每个元素都是一个结构的指针,那么为了删除42,我必须先调用deletearr[2],然后我会说arr[2] = arr[3] 然后delete在 arr[3] 上,则 arr[3] = arr[4]。那是行不通的,所以我决定在没有delete关键字的情况下尝试它,只需执行 arr[2] = arr[3] 和 arr[3] = arr[4],它就成功了。所以我的问题是,为什么我不必使用delete关键字来执行此操作。我在想,如果我只是将 arr[2] 设置为 arr[3],那么 arr[2] 指向的结构就会丢失,并且会出现内存泄漏。不是这样吗?

cod*_*edd 6

我以为既然这个数组的每个元素都是一个指向结构的指针,那么为了删除42,我必须先在arr[2]上调用delete

是的,您将使用deleteonarr[2]以释放arr[2]指向的内存。

那么我会说 arr[2] = arr[3]

到现在为止还挺好。

然后在 arr[3] 上删除

这就是问题所在。假设你有这样的代码:

int arr_cnt = 5;
int *arr[arr_cnt];
for(int i = 0; i < arr_cnt; ++i)
    arr[i] = new int(i+arr_cnt);  // using ints for simplicity, but concept is the same
Run Code Online (Sandbox Code Playgroud)

你的数组arr现在看起来像这样:

idx   *arr[]     values in heap, due to using 'new' operator
    +-----+
0   | a---|----> 5
    +-----+
1   | b---|----> 6
    +-----+
2   | c---|----> 7
    +-----+
3   | d---|----> 8
    +-----+
4   | e---|----> 9
    +-----+
Run Code Online (Sandbox Code Playgroud)

其中字母代表new.返回的不同内存地址。

这意味着,要正确删除索引 2 处的元素,您需要:

  1. 使用deleteonarr[2]避免内存泄漏,
  2. arr[2]用其他一些仍然有效的地址覆盖(arr[2]在此步骤之前使用会触发段错误)
  3. 无效的数组位置复制到arr[2](见下文)
  4. 减少数组的长度(例如arr_cnt--;

换句话说:

delete arr[2];      // step 1, address 'c' no longer valid
arr[2] = arr[4];    // step 2, arr[2] is now 'e', which points to 9 just like arr[4]
arr[4] = NULL;      // step 3, arr[4] is now invalid
--arr_cnt;          // step 4
Run Code Online (Sandbox Code Playgroud)

该图现在看起来像这样:

idx   *arr[]     values in heap, due to using 'new' operator
    +-----+
0   | a---|----> 5
    +-----+
1   | b---|----> 6
    +-----+
2   | e---|-----------+   // address 'e' used to be in arr[4]
    +-----+           |
3   | d---|----> 8    |
    +-----+           |
4   | nil |      9 <--+
    +-----+
Run Code Online (Sandbox Code Playgroud)

然后 arr[3] = arr[4]。那不起作用

如果您按照图表进行操作,您现在可能已经注意到,同时使用delete两者意味着您使两个条目无效。换句话说,如果我们跳过第二个图表并尝试你的delete arr[2]; arr[2] = arr[3]; delete arr[3]逻辑,你最终会得到:

delete arr[2];
    +-----+
0   | a---|----> 5
    +-----+
1   | b---|----> 6
    +-----+
2   | c---|----> ?  // invalidated
    +-----+
3   | d---|----> 8
    +-----+
4   | e---|----> 9
    +-----+

arr[2] = arr[3];
    +-----+
0   | a---|----> 5
    +-----+
1   | b---|----> 6
    +-----+
2   | d---|-+
    +-----+ |
3   | d---|-+--> 8  // both have the same address, so point to the same place
    +-----+
4   | e---|----> 9
    +-----+

delete arr[3];
    +-----+
0   | a---|----> 5
    +-----+
1   | b---|----> 6
    +-----+
2   | d---|-+
    +-----+ |
3   | d---|-+--> ?  // both invalid now, but not set to null
    +-----+
4   | e---|----> 9
    +-----+
Run Code Online (Sandbox Code Playgroud)

所以我决定在没有 delete 关键字的情况下尝试它,只执行 arr[2] = arr[3] 和 arr[3] = arr[4],它奏效了。

但是现在你有内存泄漏。你总是要delete每一个new在C ++中,就像你总是有free每个malloc在C.

所以我的问题是,为什么我不必使用 delete 关键字来执行此操作。

必须使用它。问题是您使无效的次数比您想象的要多,并最终尝试使用已释放的内存。当程序尝试像这样访问内存时,会导致分段错误。我不记得 Windows 将显示的确切消息,但它可能是某种未处理的异常消息。(分段错误往往是 GNU/Linux 术语。)

我在想,如果我只是将 arr[2] 设置为 arr[3],那么 arr[2] 指向的结构就会丢失,并且会出现内存泄漏。不是这样吗?

你在这里是对的。问题不在于您对new/delete关系的理解,而在于您描述的分配是拷贝,并且您最终删除的内容超出预期。