Home > Software design >  Howto manage freeing single pointers from a double-pointer block
Howto manage freeing single pointers from a double-pointer block

Time:07-12

I have a block of pointers to some structs which I want to handle (i.e. free) separately. As an example below there is an integer double-pointer which should keep other pointers to integer. I then would like to free the second of those integer pointers (in my program based on some filterings and calculations). If I do so however, I should keep track of int-pointers already set free so that when I iterate over the pointers in the double-pointer I do not take the risk of working with them further. Is there a better approach for solving this problem (in ANSI-C) without using other libs (e.g. glib or alike)? Here is a small simulation of the problem:


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

int main() {
    
    int **ipp=NULL;
    for (int i = 0; i < 3; i  ) {
        int *ip = malloc(sizeof (int));
        printf("%p -> ip %d\n", ip, i);
        *ip = i * 10;
        if ((ipp = realloc(ipp, sizeof (int *) * (i   1)))) {
            ipp[i] = ip;
        }
    }
    printf("%p -> ipp\n", ipp);
    for (int i = 0; i < 3; i  ) {
        printf("%d. %p %p %d\n", i, ipp   i, *(ipp i), **(ipp   i));
    }
    // free the middle integer pointer
    free(*(ipp 1));
    printf("====\n");
    for (int i = 0; i < 3; i  ) {
        printf("%d. %p %p %d\n", i, ipp   i, *(ipp i), **(ipp   i));
    }
    return 0;
}

which prints something like

0x555bcc07f2a0 -> ip 0
0x555bcc07f6f0 -> ip 1
0x555bcc07f710 -> ip 2
0x555bcc07f6d0 -> ipp
0. 0x555bcc07f6d0 0x555bcc07f2a0 0
1. 0x555bcc07f6d8 0x555bcc07f6f0 10
2. 0x555bcc07f6e0 0x555bcc07f710 20
====
0. 0x555bcc07f6d0 0x555bcc07f2a0 0
1. 0x555bcc07f6d8 0x555bcc07f6f0 0
2. 0x555bcc07f6e0 0x555bcc07f710 20

Here I have freed the middle int-pointer. In my actual program I create a new block for an integer double-pointer, iterate over the current one, create new integer pointers and copy the old values into it, realloc the double-pointer block and append the new pointer to it, and at the end free the old block and all it's containing pointers. This is a bit ugly, and resource-consuming if there is a huge amount of data, since I have to iterate over and create and copy all the data twice. Any help is appreciated.

CodePudding user response:

Re:

"This is a bit ugly, and resource-consuming if there is a huge amount of data, since I have to iterate over and create and copy all the data twice. Any help is appreciated."

First observation: It is not necessary to use realloc() when allocating new memory on a pointer that has already been freed. realloc() is useful when needing to preserve the contents in a particular area of memory, while expanding its size. If that is not a need (which is not in this case) malloc() or calloc() are sufficient. @Marco's suggestion is correct.

Second observation: the following code snippet:

    if ((ipp = realloc(ipp, sizeof (int *) * (i   1)))) {
        ipp[i] = ip;
    }   

is a potential memory leak. If the call to realloc()_ fails, the pointer ipp will be set to null, making the memory location that was previously allocated becomes orphaned, with no way to free it.

Third observation: Your approach is described as needing:

  • Array of struct
  • dynamic memory allocation of a 2D array
  • need to delete elements of 2D array, and ensure they are not referenced once deleted
  • need to repurpose deleted elements of 2D array

Your initial reaction in comments to considering using an alternative approach notwithstanding, Linked lists are a perfect fit to address the needs stated in your post.

  • The fundamental element of a Linked List uses a struct
  • Nodes (elements) of a List are dynamically allocated when created.
  • Nodes of a List are not accessible to be used once deleted. (No need to track)
  • Once the need exists, a new node is easily created.

Example struct follows. I like to use a data struct to contain the payload, then use an additional struct as the conveyance, to carry the data when building a Linked List:

typedef struct {//to simulate your struct
    int dNum;
    char unique_name[30];
    double fNum;
} data_s;

typedef struct Node {//conveyance of payload, forward and backward searchable
    data_s data;
    struct Node *next; // Pointer to next node in DLL
    struct Node *prev; // Pointer to previous node in DLL
} list_t;  

Creating a list is done by creating a series of nodes as needed during run-time. Typically as records of a database, or lines of a file are read, and the elements of the table record (of element of the line in a file) are read into and instance of the data part of the list_s struct. A function is typically defined to do this, for example

void insert_node(list_s **head, data_s *new)
{
    list_s *temp = malloc(sizeof(*temp));
    //insert lines to populate 
    temp.data.dNum = new.dNum;
    strcpy(temp.data.unique_name, new.unique_name);
    temp.fNum = new.fNum
    //arrange list to accomdate new node in new list 
    temp->next = temp->prev = NULL;
    if (!(*head))
        (*head) = temp;
    else//...or existing list
    {
        temp->next = *head;
        (*head)->prev = temp;
        (*head) = temp;
    }
}

Deleting a node can be done in multiple ways. It the following example method a unique value of a node member is used, in this case unique_name

void delete_node_by_name(list_s** head_ref, const char *name)
{
    BOOL not_found = TRUE;
    // if list is empty 
    if ((*head_ref) == NULL)
        return;
  
    list_s *current = *head_ref;
    list_s *next = NULL;
  
    // traverse the list up to the end 
    while (current != NULL && not_found) 
    {
        // if 'name' in node...
        if (strcmp(current->data.unique_name, name) == 0) 
        {
            //set loop to exit
            not_found = FALSE;
            //save current's next node in the pointer 'next' /
            next = current->next;
            // delete the node pointed to by 'current'
            delete_node(head_ref, current);
            // reset the pointers
            current = next;
        }
  
        // increment to next node
        else
        {
            current = current->next;
        }
    }
}

Where delete_node() is defined as:

void delete_node(list_t **head_ref, list_t *del)
{
    // base case 
    if (*head_ref == NULL || del == NULL)
        return;
  
    // If node to be deleted is head node 
    if (*head_ref == del)
        *head_ref = del->next;
  
    // Change next only if node to be deleted is NOT the last node 
    if (del->next != NULL)
        del->next->prev = del->prev;
  
    // Change prev only if node to be deleted is NOT the first node 
    if (del->prev != NULL)
        del->prev->next = del->next;
  
    // Finally, free the memory occupied by del
    free(del);
}
     

This link is an introduction to Linked Lists, and has additional links to other related topic to expand the types of lists that are available.

CodePudding user response:

You could use standard function memmove and then call realloc. For example Let's assume that currently there are n pointers. Then you can write

free( *(ipp   i ) );

memmove( ipp   i, ipp   i   1, ( n - i - 1 ) * sizeof( *pp ) );

*( ipp   n - 1 ) = NULL; // if the call of realloc will not be successfull
                         // then the pointer will be equal to NULL  

int **tmp = realloc( ipp, ( n - 1 ) * sizeof( *tmp ) );

if ( tmp != NULL )
{
    ipp = tmp;
    --n;
}
else
{
    // some other actions
} 
  • Related