Home > Blockchain >  How to properly free malloced variable passed into variadic function?
How to properly free malloced variable passed into variadic function?

Time:02-04

Consider the following example

char** make_copy(int argc, char** argv){   

    char** new_argv = malloc((argc 1) * sizeof *new_argv);
    for(int i = 0; i < argc;   i)
    {
        size_t length = strlen(argv[i]) 1;
        new_argv[i] = malloc(length);
        memcpy(new_argv[i], argv[i], length);
    }
    new_argv[argc] = NULL;

    return new_argv;

}

Above function malloc memory to make copies


void free_space(**args, ...){

   // how to do ?
}
int main(){

   char** string1 = make_copy(); # make a copy of some string
   char** string2 = make_copy(); # make a copy of another string
   char** string3 = make_copy(); # make a copy of third string

   free_space(string1, string2, string3, NULL);

}

without make any assumption of number of passing variable, what is the most efficient way of freeing malloced space?

CodePudding user response:

A macro can send a dummy first parameter and a final NULL, then just use a va_list inside your function:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
 
#define free_all(...) free_all(0, __VA_ARGS__, NULL)

void (free_all)(int dummy, ...) 
{
    va_list args;
    va_start(args, dummy);
    while (1)
    {
        char **ptr = va_arg(args, char **);   

        if (ptr == NULL)
        {
            break;
        }
        while (*ptr != NULL)
        {
            free(*ptr);
            ptr  ;
        }
    }
    va_end(args);
}
 
int main(void) 
{
    char *a[] = {strdup("abc"), strdup("123"), NULL};
    char *b[] = {strdup("bcd"), strdup("456"), NULL};
    char *c[] = {strdup("cde"), strdup("789"), NULL};    

    free_all(a, b, c);
}

or if your prefer you can pass a compound literal to a function expecting an array of pointers (with the advantage that the compiler can do type checking):

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

void free_all(char **list[]) 
{
    while (*list != NULL)
    {
        char **ptr = *list;

        while (*ptr != NULL)
        {
            free(*ptr);
            ptr  ;
        }
        list  ;
    }
}
 
int main(void) 
{
    char *a[] = {strdup("abc"), strdup("123"), NULL};
    char *b[] = {strdup("bcd"), strdup("456"), NULL};
    char *c[] = {strdup("cde"), strdup("789"), NULL};

    free_all((char **[]){a, b, c, NULL});
    return 0;
}

if you want to hide the construction of the compound literal at the cost of obfuscating the code with a macro:

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

#define free_all(...) free_all(((char **[]){__VA_ARGS__, NULL}))

void (free_all)(char **list[]) 
{
    while (*list != NULL)
    {
        char **ptr = *list;

        while (*ptr != NULL)
        {
            free(*ptr);
            ptr  ;
        }
        list  ;
    }
}
 
int main(void) 
{
    char *a[] = {strdup("abc"), strdup("123"), NULL};
    char *b[] = {strdup("bcd"), strdup("456"), NULL};
    char *c[] = {strdup("cde"), strdup("789"), NULL};

    free_all(a, b, c);
    return 0;
}

CodePudding user response:

What you want is to use a sentinel value to mark the end of the variable arguments list:

void free_one(void *ptr) {
    // logic to free a pointer
}

void free_all(void *ptr, ...) {
    // short-circuit free_all(NULL)
    if (!ptr) return;

    va_list args;

    free_one(ptr);

    va_start(args, ptr);

    while (true) {
        void *arg = va_arg(args, void *);

        // stop on the first NULL
        if (!arg) break;

        free_one(arg);
    }

    va_end(args);
}

Usage:

free_all(NULL); // no-op
free_all(string1, string2, string2, NULL);

The only caveat is that you can't pass a NULL pointer this way, since NULL denotes the end of the variable arguments list. So:

free_all(string1, NULL, string2);

Will not free string2.

CodePudding user response:

You have to iterate over arguments and free them one by one. I would do just:

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

char** make_copy(int argc, char** argv) {
    char** new_argv = malloc((argc   1) * sizeof *new_argv);
    for (int i = 0; i < argc;   i) {
        size_t length = strlen(argv[i])   1;
        new_argv[i] = malloc(length);
        memcpy(new_argv[i], argv[i], length);
    }
    new_argv[argc] = NULL;

    return new_argv;
}
void free_one(char** ptr) {
    for (char** i = ptr; *i;   i) {
        free(*i);
    }
    free(ptr);
}

void free_space(char** ptr, ...) {
    va_list va;
    va_start(va, ptr);
    while (ptr) {
        free_one(ptr);
        ptr = va_arg(va, char**);
    }
    va_end(va);
}

int main() {
    char** string1 = make_copy(1, (char*[]){"1a"});
    char** string2 = make_copy(2, (char*[]){"2a", "2b"});
    char** string3 = make_copy(3, (char*[]){"3a", "3b", "3c"});
    free_space(string1, string2, string3, NULL);
}
  •  Tags:  
  • c
  • Related