Delete an item from a "list"

4

Good morning,

I am learning to work with C pointers and I have a question about an algorithm that I am implementing that makes extensive use of malloc , realloc and free , this is the code:

List.h

#ifndef _LIST_H_
#define _LIST_H_

typedef struct {
    int    Count;
    void **Elements;
} List;

List *list_new();
void  list_insert(List *l, void *elem);
void *list_getelem(List *l, int offset);
void  list_removeoffset(List *l, int elem);
void  list_free(List *l);
int   list_get_count(List *l);

#endif

List.c

#include <stdio.h>
#include <stdlib.h>
#include "List.h"

List *list_new() {
    List *ptr = malloc(sizeof(List));
    ptr->Elements = malloc(sizeof(void*));
    ptr->Count = 0;
    return (ptr) ? ptr : NULL;
}
void list_insert(List *l, void *elem) {
    if (l->Count == 0) 
        l->Elements = realloc(l->Elements, sizeof(void*));
    else 
        l->Elements = realloc(l->Elements, (sizeof(void*) * l->Count));
    l->Elements[l->Count] = elem;
    l->Count++;
}
void *list_getelem(List *l, int offset) {
    if (offset < 0 || offset >= l->Count) return NULL;
    return l->Elements[offset];
}
void list_removeoffset(List *l, int elem) {
    if (elem < 0 || elem >= l->Count) return;
    l->Elements[elem] = NULL; 
    l->Count--; 
    l->Elements = realloc(l->Elements, sizeof(void*) * l->Count);   
}
void list_free(List *l) {
    if (l) {
        free(l->Elements); free(l);
    }
}
int list_get_count(List *l) {
    return l->Count;
}

My problem arises within the declaration of the method list_removeoffset(...) I can not eliminate an element in a position offset in the array of pointers.

main() :

List *MyList = list_new();

list_insert(MyList, (int*)3416); printf("List Count Now: %d\n", list_get_count(MyList));
list_insert(MyList, (char*)"hello world"); printf("List Count Now: %d\n", list_get_count(MyList));
list_insert(MyList, (int*)5); printf("List Count Now: %d\n", list_get_count(MyList));

This is the result I get with the previous code:

List Count Now: 1
List Count Now: 2
List Count Now: 3
Element at position 1: hello world

Effectively it works, but when trying with the following code to remove the elements:

list_removeoffset(MyList, 1); // Intento remover el elemento en la posicion 1.

It seems to be deleted, but in the same way it frees the memory of the other elements that are after it and shows me as a result 0 .

How can I delete or release only one element and not all of the elements that are starting from this?

    
asked by NaCl 11.10.2016 в 20:50
source

3 answers

2

First of all, your implementation is quite inefficient in computational time for the amount of realloc that you are using. Note that in the worst case realloc copies memory blocks. With which, an operation that in a linked list is O(1) in your list ends up being O(n) . I recommend keeping a physical size and a logical one in your list and only resize the vector "up" when the logical size is very close to the physical and "down" when the logical size is very small with respect to the physical.

Moving to your question, realloc reduces the size of the memory block it uses but only that. It is your responsibility as implementer to "advance" the elements of the list that remain before the liberated space. Schematically, something like this happens:

Original Vector:

------------------------
0: ptr to 3416
1: ptr to "hello world"
2: ptr to 5
------------------------

After reassigning l->Count and l->Elements[1] :

------------------------
0: ptr to 3416
1: NULL
2: ptr to 5
------------------------

After realloc :

------------------------
0: ptr to 3416
1: NULL
------------------------
2: ptr to 5 <<< Fuera del bloque, y la memoria ya no le pertenece a tu programa

In short, what happens is not that all the data of the vector from which you are deleting is released, but that the last one is lost. In your particular case, since the vector has only three elements, the last element is also "all the elements from the deletion".

What you should do is, instead of assigning NULL to Elements[1] , copying the content of Elements[2] (and [3] to [2] ,% [4] to [3] and so on ). In this way, before the realloc you would have the vector like this:

------------------------
0: ptr to 3416
1: ptr to 5 <<< Copiado de [2]
2: ptr to 5
------------------------

And then the realloc :

------------------------
0: ptr to 3416
1: ptr to 5
------------------------
2: ptr to 5
    
answered by 11.10.2016 / 21:28
source
1

When doing the elimination you are putting the element elem to NULL and then you do realloc, with which the last element is lost. This is what happens with list_removeoffset(MyList, 1);

Count = 3                                 Count = 2
Elem 0 : (void*) 3416                     (void*) 3416
Elem 1 : (void*) "hello world"   =====>   (void*) NULL
Elem 2 : (void*) 5                        

And this is what you want to get:

Count = 3                                 Count = 2
Elem 0 : (void*) 3416                     (void*) 3416
Elem 1 : (void*) "hello world"   =====>   (void*) 5
Elem 2 : (void*) 5                     

To do this you have to reassign all the elements from elem+1 to the end one lower position of the array:

void list_removeoffset(List *l, int elem) {
    int i;
    if (elem < 0 || elem >= l->Count) return;
    --(l->Count);
    for( i=elem; i<l->Count; ++i ) {
      l->Elements[i] = l->Elements[i+1];
    }
    l->Elements = realloc(l->Elements, sizeof(void*) * l->Count);   
}

Note that list_insert has an error. When Count is worth 1 it is reserved size for an element but it would be necessary to reserve for two; 1 for the one that already exists and another for the new one. The same goes for higher values. The correct thing would be:

void list_insert(List *l, void *elem) {
    l->Elements = realloc(l->Elements, sizeof(void*)*(l->Count+1));
    l->Elements[l->Count] = elem;
    ++l->Count; 
}
    
answered by 11.10.2016 в 21:38
0

I think the problem is that you're doing a realloc at a smaller size. This function of C is low level and does not take into account the "null", so it is simply eliminating things from the end of the array. By the way, reducing the size does not have a defined behavior, so you could also remove the elements from the beginning.

What you want to achieve, which is to use just memory, is achieved with linked lists. The only bad thing is that iterating over them is slower by not being in contiguous memory locations (cache failures). But more "spectacular" approaches can be made that reserve memory by blocks.

    
answered by 11.10.2016 в 21:32