【问题标题】:C++: Remove element from dynamic struct array and shift other elementsC ++:从动态结构数组中删除元素并移动其他元素
【发布时间】:2018-06-10 21:35:16
【问题描述】:

我有一个结构数组。我正在尝试从该数组中删除元素列表并将其他元素向左移动。移动元素后,我试图删除/释放我们不再需要的数组末尾的内存。我有以下代码:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
void removeelement(int*);
void displayelements();

typedef struct {
   int n;
}element;
element** array;

int numofelements=5;

int main() {
   array = (element**)malloc(5*sizeof(element*));
   for(int i=0;i<5;i++){
       array[i] = new element;
       array[i]->n=i;
   }
   int removelist[3] = {1,3,4};
   removeelement(removelist);
   displayelements();
   return 0;
}
void removeelement(int* removelist){
    for(int i=0;i<3;i++){
        int index = removelist[i];
        int j;
        for(j=index;j<numofelements-2;j++){
            array[j] = array[j+1];
        }
        delete [] array[j+1];
        numofelements--;
    }
}
void displayelements(){
    int i=0;
    while(i<numofelements){
        printf("%d\n",array[i]->n);
        i++;
    }
}

但是delete [] array[j+1]; 导致异常:

*** Error in `main': double free or corruption (fasttop): 0x0000000001861cb0 ***

我不明白是什么原因造成的。正如许多人在其他论坛中所建议的那样,我正在使用“new”运算符来创建一个新的动态元素。

编辑:

我做了以下更改:

我把for(j=index;j&lt;numofelements-2;j++){改成了for(j=index;j&lt;numofelements-1;j++){

int index = removelist[i]int index = removelist[i]-i

我删除了 delete [] array[j+1]delete array[numofelements+1] 放在两个 for 循环之外。 虽然我只在一个元素上使用了 delete,但它也为其他冗余元素释放了内存,这很有趣。 这是最终代码:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
void removeelement(int*);
void displayelements();

typedef struct {
   int n;
}element;
element** array;

int numofelements=5;

int main() {
   array = (element**)malloc(5*sizeof(element*));
   for(int i=0;i<5;i++){
       array[i] = new element;
       array[i]->n=i;
   }
   int removelist[3] = {1,3,4};
   removeelement(removelist);
   displayelements();
   return 0;
}
void removeelement(int* removelist){
    for(int i=0;i<3;i++){
        int index = removelist[i]-i;
        int j=index;
        for(;j<numofelements-1;j++){
            array[j] = array[j+1];
        }
        numofelements--;
    }
    delete array[numofelements+1];
}
void displayelements(){
    int i=0;
    while(i<5){
        printf("%d\n",array[i]->n);
        i++;
    }
}

我使用此代码使其工作。但我将按照你们许多人的建议使用 std::vector。

【问题讨论】:

  • 你似乎对C++有不少误解。你应该退后一步,从一本好书中系统地学习语言。
  • 那些建议使用new 的人不喜欢你。
  • 张贴模糊的cmets有什么用?谁能确切地告诉我我做错了什么?
  • 主要是delete[] 暗示new[],而不是new。但请改用std::vectornew 不应该使用,尤其是初学者。
  • 您真正应该做的是标记要删除的元素,然后删除标记的元素。您不应该在删除每个数组的同时移动数组,因为这会使 removelist 条目不再指向每次迭代的有效条目。是的,您的很多代码都可以使用vector,但主要问题是您的删除逻辑,无论您使用的是new 还是vector

标签: c++ malloc free delete-operator


【解决方案1】:

您在new[] 表达式未返回的指针上使用了delete[] 表达式。因此程序的行为是不确定的。

new 分配的任何东西都必须用delete 释放。 delete[] 不行。


即使你使用了正确的表达方式,还有另一个错误:

int numofelements=5;

//...
for(int i=0;i<3;i++){
    int index = removelist[i];
    int j;
    for(j=index;j<numofelements-2;j++){
        array[j] = array[j+1];
    }
    delete [] array[j+1];
    numofelements--;
}

在外循环的第一次迭代之后,array[4] 已被删除。请注意,由于removelist[i] == 1,我怀疑array[4] 一开始就不应该被删除。

在第二次迭代中,array[4] 将再次被删除。由于 this 指向已删除的对象,因此行为未定义。

此外,由于内部循环中的array[j] = array[j+1],已删除指针的副本仍保留在数组中,而某些指针将被覆盖,因此它们的内存将被泄漏。对算法的简单修复:首先删除索引处的指针,然后在删除后移动元素。

更重要的是:如果您的循环按预期工作,前 2 次迭代将分别删除数组的一个元素,从而将 numofelements 减少到 3。然后,您将删除数组索引 4 处的元素在索引 0..2 中有有效指针。据推测,要删除的索引必须进行排序;在这种情况下,可以通过删除索引removelist[i] - i 来计算班次来解决此问题。另一个聪明的策略是按照 Paul 的建议从高到低移除索引。


其他需要考虑的事项:

  • 程序泄漏分配给array 的内存。对于这个简单的程序来说,这可能不是问题,但最好养成释放所有已分配内存的习惯,以免在重要时忘记这样做。
  • 使用malloc 是个坏主意,除非有具体且合理的理由这样做。使用malloc 通常没有合理的理由。
  • 在不使用 RAII 容器的情况下分配动态内存是个坏主意。如果使用std::vector,这个程序中的错误将被轻松避免。

【讨论】:

  • 虽然array[4]被复制到array[3]actually这是array[4],被删除了两次。
  • @O'Neil 感谢您的指出。我读错了程序。如果removelist[1] 小于 3,我描述的场景就会发生。
【解决方案2】:

除了内存管理中的明显错误之外,如果您首先对 removelist 数组进行排序,然后在该数组中从最后一个条目开始向第一个条目反向工作,则该方法通常可以变得更简单。

这样做会改变array 调整大小的方式,因为您将一直在对不再受到影响的条目进行调整大小(移动元素)。在您当前的代码中,您正在移动条目,并且在循环的后续迭代中,您需要使用现在“无效”的 removelist 一组要删除的索引重新访问那些已移动的条目。

请参阅 mayaknife 和 user2079303 的答案,以说明删除每个项目后无效条目的问题(从 removelist 数组中的最低条目到最高条目)。正如所指出的,即使使用 std::vector 也对您没有帮助,因为这个问题指出了用于删除元素的基本逻辑中的缺陷。

如果您要在 removelist 数组中向后工作,您可能会在当前代码中解决这个问题(我说“可能已经解决”,因为这没有经过全面测试,但它或多或少地说明了正在提出的观点):

void removeelement(int* removelist)
{
    for(int i = 2; i >= 0 ; --i)
    {
        int index = removelist[i];
        array* elementToDelete = array[index];
        for(j=index; j < numofelements -2; j++)
        {
            array[j] = array[j+1];
        }
        delete [] elementToDelete;
        numofelements--;
    }
}

因此,在每次迭代中,removelist 索引仍然有效,因为您要从要删除的条目中的最高条目到最低条目。在纸上解决这个问题,如果您颠倒了遍历 removelist 数组的方式,您应该会看到它是如何工作的,而不是继续遍历 removelist 数组。


代码还有其他问题,例如将mallocdelete[] 混合使用。这样做是未定义的行为——永远不要在 C++ 程序中混合这样的分配/释放方法。

话虽如此,这是您程序的另一个版本,但没有使用手动内存管理:

#include <vector>
#include <algorithm>
#include <iostream>
#include <array>

struct element {
   int n;
};

int main() 
{
    std::vector<element> arr(5);

    for (int i = 0; i < 5; ++i)
       arr[i].n = i;

    std::array<int, 3> removelist = {1,3,4};
    // sort the list 
    std::sort(removelist.begin(), removelist.end());

    // work backwards, erasing each element
    std::for_each(removelist.rbegin(), removelist.rend(),[&](int n){arr.erase(arr.begin() + n);});

    // output results
    for( auto& v : arr)
       std::cout << v.n << '\n';
}

Live Example

注意反向迭代器rbegin()rend() 的使用,从而模仿removelist 容器的反向遍历。

【讨论】:

    【解决方案3】:

    这一行:

    delete [] array[j+1];
    

    删除 'array[j+1]' 指向的元素数组。但是 'array[j+1]' 是由这一行初始化的:

    array[i] = new element;
    

    它只分配一个元素,而不是一个元素数组,所以删除也应该只删除一个元素。例如:

    delete array[j+1];
    

    然而,主要问题是删除了错误的元素。要了解原因,我们假设初始化“数组”的循环将指针分配给五个“元素”结构,我们将其称为 A、B、C、D 和 E。

    在调用 removeelements() 之前,'array' 包含以下指针:

    array[0] -> A
    array[1] -> B
    array[2] -> C
    array[3] -> D
    array[4] -> E
    

    'numofelements' 是 5。

    在removeelements()里面,第一个被移除的元素是1,内部循环是这样的:

    for(j=1;j<3;j++){
        array[j] = array[j+1];
    }
    

    这将导致 'array[2]' 的内容被复制到 'array[1]' 和 'array[3]' 被复制到 'array[2]。之后,“数组”包含以下内容:

    array[0] -> A
    array[1] -> C
    array[2] -> D
    array[3] -> D
    array[4] -> E
    

    此时'j'包含3,所以'delete array[j+1]'将删除'array[4]'指向的元素,即'E'。

    'numofelements' 然后递减到 4。

    要删除的第二个元素是 3。因为 'numofelements' 现在是 4,所以内部循环将如下所示:

    for(j=3;j<2;j++){
        array[j] = array[j+1];
    }
    

    'j' 将被初始化为 3。它大于 2,因此循环体不会执行,'array' 将保持不变。

    由于'j'是3'delete array[j+1]'将再次删除'array[4]',它仍然指向E。所以E被第二次删除,导致你得到的错误.

    如果程序继续运行,'numofelements' 将递减为 3,然后我们将继续处理要删除的第三个元素,即 4。这将产生一个像这样的内部循环:

    for(j=4;j<1;j++){
        array[j] = array[j+1];
    }
    

    'j' 将被初始化为 4 并且循环体将再次不会被执行。 'delete array[j+1]' 会尝试删除 'array[5]' 指向的元素,这超出了 'array' 的范围,会导致异常。

    正如其他人所建议的,处理此问题的最佳方法是使用 std::vector。但是,您的代码的结构方式甚至 std::vector 都无法为您提供所需的结果,因为一旦您从“数组”中删除一个元素,其后面的所有元素的索引就会发生变化,这意味着剩下的'removelist' 中的索引将不再正确。

    我建议无论您进行什么更改,您都可以手动单步执行代码,就像我在上面所做的那样,跟踪数组的内容和相关变量,以便您可以准确了解您的代码在做什么。

    【讨论】:

      猜你喜欢
      • 2018-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多