C++: Remove element from dynamic struct array and shift other elements

一笑奈何 提交于 2019-12-13 08:28:15

问题


I have an array of structs. I am trying to delete a list of elements from that array and shift other elements to the left. After shifting the elements I am trying to delete/free the memory at the end of the array which we don't require anymore. I have the following code:

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

But delete [] array[j+1]; is causing an exception:

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

I don't understand what's causing this. As many people have suggested in other forums, I am using the 'new' operator to create a new,dynamic element.

EDIT:

I made the following changes:

I changed for(j=index;j<numofelements-2;j++){ to for(j=index;j<numofelements-1;j++){

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

I removed delete [] array[j+1] put delete array[numofelements+1] outside both the for loops. Though I had used delete only on one element, It dealloced memory for the other redundant elements as well, which is interesting. This is the final code:

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

I got it working using this code. But I am going to use std::vector as many of you suggested.


回答1:


Apart from the obvious errors in memory management, the approach in general could have been made simpler if you first sorted the removelist array, and then work backwards in that array starting from the last entry going toward the first entry.

Doing this would have changed the way the array was being resized in that you would have been doing the resizing (shifting elements) on entries that will no longer be affected. In your current code, you are shifting entries, and in subsequent iterations of the loop, you need to revisit those shifted entries with a now "invalid" removelist set of indices to remove.

See mayaknife's and user2079303 answers to illustrate the issue of invalid entries after removing each item (going from lowest entry to highest entry in the removelist array). As pointed out, even a usage of std::vector would not have helped you, since this issue points out the flaw in the basic logic being used to remove the elements.

Here is how you might have addressed this in your current code if you were to work going backwards in the removelist array ( I say "might have addressed", since this is not fully tested, but it illustrates more or less the point being made):

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

Thus on each iteration, the removelist index will still be valid, since you're going from highest entry to lowest entry in the entries to delete. Work this out on paper and you see if you reversed the way you iterated through the removelist array, you should see how this works, as opposed to going forward through the removelist array.


You also have other issues with the code, such as mixing malloc with delete[]. Doing so is undefined behavior -- never mix allocation / deallocation methods like this in a C++ program.

Having said this, here is another version of your program, but not using manual memory management:

#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

Note the usage of the reverse iterators rbegin() and rend(), thus mimicking the backwards traversal of the removelist container.




回答2:


You used delete[] expression on a pointer that was not returned by new[] expression. Therefore the behaviour of the program is undefined.

Anything that was allocated with new must be deallocated with delete. delete[] will not do.


Even if you had used the correct expression, there's another bug:

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

After the first iteration of the outer loop, array[4] has been deleted. Note that since removelist[i] == 1, I suspect that array[4] wasn't supposed to be deleted in the first place.

During the second iteration, array[4] will be deleted again. Since this points to the already deleted object, the behaviour is undefined.

Furthermore, copies of the deleted pointer remain in the array due array[j] = array[j+1] in the inner loop, while some of the pointers will be overwritten and therefore their memory will be leaked. Simple fix to your algorithm: Delete the pointer at index first, and shift elements after deletion.

Even more: If your loop worked as you intended, first 2 iterations would have each removed an element of the array, thus reducing numofelements to 3. Then, you'd be removing an element at index 4 of an array that has valid pointers in indices 0..2. Presumably the indices to be removed must be sorted; In that case, this can be fixed by deleting the index removelist[i] - i to acount for the shifts. Another clever strategy is to remove the indices from high to low, as suggested by Paul.


Other things to consider:

  • The program leaks the memory allocated for array. That might not be a problem to this trivial program, but it would be a good idea to have a habit of deallocating all memory that was allocated lest you forget to do so when it matters.
  • It's a bad idea to use malloc unless one has specific and reasonable justification to do so. One usually doesn't have a reasonable justification to use malloc.
  • It's a bad idea to allocate dynamic memory at all without using a RAII container. The bugs in this program would have been trivially been avoided if std::vector had been used.



回答3:


This line:

delete [] array[j+1];

deletes the array of elements pointed to by 'array[j+1]'. But 'array[j+1]' was initialized by this line:

array[i] = new element;

which only allocates a single element, not an array of elements, so the deletion should only delete a single element as well. E.g:

delete array[j+1];

The main problem, however, is that the wrong elements are being deleted. To see why, let's assume that the loop which initializes 'array' assigns it pointers to five 'element' structures which we will refer to as A, B, C, D and E.

Before the call to removeelements(), 'array' contains the following pointers:

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

'numofelements' is 5.

Inside removeelements(), the first element to be removed is 1 and the inner loop looks like this:

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

This will result in the contents of 'array[2]' being copied into 'array[1]' and 'array[3]' being copied into 'array[2]. After that 'array' contains the following:

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

At this point 'j' contains 3 so 'delete array[j+1]' will delete the element pointed to by 'array[4]', which is 'E'.

'numofelements' is then decremented to 4.

The second element to be removed is 3. Because 'numofelements' is now 4, the inner loop will look like this:

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

'j' will be initialized to 3. That is greater than 2 so the body of the loop won't execute and 'array' will be left unchanged.

Since 'j' is 3 'delete array[j+1]' will again delete 'array[4]', which still points to E. So E is deleted a second time, resulting in the error that you are getting.

Were the program to continue, 'numofelements' would be decremented to 3 and we'd move on to the third element to be removed, which would be 4. This would give an inner loop like this:

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

'j' would be initialized to 4 and once again the body of the loop would not be executed. 'delete array[j+1]' would attempt to delete the element pointed to by 'array[5]', which is beyond the bounds of 'array' and would result in an exception.

As others have suggested, the best way to handle this is to use std::vector. However, the way your code is structured even std::vector will fail to give you the results you want because as soon as you delete one element from 'array' the indices of all of those which follow it will change, meaning that the remaining indices in 'removelist' will no longer be correct.

I suggest that whatever changes you make, you manually step through the code, as I have above, tracking the contents of the array and relevant variables so you can understand exactly what your code is doing.



来源:https://stackoverflow.com/questions/48039629/c-remove-element-from-dynamic-struct-array-and-shift-other-elements

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!