2

let's say I'm writing a function like strdup(), or any other function that uses malloc() for that matter. If I only call this function inside of printf() like this :

printf("%s", my_function(arg));

Is it possible to free the value returned from the function ? If yes, how ? To my knowledge, free() takes as argument a void pointer but how do you get that pointer if you never store de returned value in a variable ?

matteobu02
  • 53
  • 5
  • 3
    You can't. That's why any functions that allocate memory should return the address(es). – Adrian Mole Sep 17 '21 at 23:01
  • Yeah that's what I thought. Thanks! – matteobu02 Sep 17 '21 at 23:06
  • That's why it's generally considered bad form for functions to allocate memory and expect their callers to release it. Most libraries expect the caller to allocate memory, pass the size and address of that into a function (which should write only within those bounds), and then free it. – Lee Daniel Crocker Sep 17 '21 at 23:21

3 Answers3

3

No - you'd need to call it as:

char *p;

p = my_function(arg);
printf("%s", p);
free(p);

EDIT

If you're into code abuse you could do something like:

for(char *p = my_function(arg); p != NULL ; free(p), p = NULL)
  printf("%s", p);

But that's just hideous...

2

Is it possible to free the returned value of a function (only) called inside of printf (in c)?

No.


Alternative: "allocate" with a compound literal.

Instead of allocating in my_function(char *arg), determine size needed and pass the compound literal to my_function(char *arg, char *buf), which then returns buf.

Instead of allocating in my_function(char *arg), if the maximum size is not too large, pass the compound literal to my_function(char *arg, char *buf), which then returns buf.

#define MY_FUNCTION_SIZE 42
printf("%s", my_function(arg, char [MY_FUNCTION_SIZE]{0}));

The compound literal is valid until the end of the block of code. Then it is "unallocated". Neither malloc(), free() nor is an explicit named temporary object needed.

Example.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • What is the benefit here using a compound literal where you need to specify a max buffer size anyway? Just to provide a limited lifetime that goes out of scope when `printf()` completes instead of leaving a fixed size buffer on the stack if declared in the current scope? – David C. Rankin Sep 17 '21 at 23:34
  • @DavidC.Rankin It is a quesiton of 1) _max buffer size_ and 2) conveince (less error prone). When `size_neeeded(arg)` is less than _max buffer size_, then this approach works well. Consider (see example) of the ease of use of multiple calls with `printf("b2:%s b7:%s b36:%s ", TO_BASE(x, 2), TO_BASE(x, 7), TO_BASE(x, 36));` – chux - Reinstate Monica Sep 18 '21 at 00:22
  • 1
    I like the approach if there is a known range for buffer size, but since the size must be a compile time constant for the compound literal, for general buffer use, a fixed size buffer in the current scope would seem just as effective and arguably more readable than a combination of macros feeding into the compound literal. Though both this and the linked answers were very helpful. – David C. Rankin Sep 18 '21 at 00:34
  • @DavidC.Rankin "size must be a compile time constant for the compound literal," --> Oops. Now how to re-work this ...... – chux - Reinstate Monica Sep 18 '21 at 00:39
  • @matteobu02 Major re-work to this answer. – chux - Reinstate Monica Sep 18 '21 at 00:41
  • Yes, I ran into that limitation playing with a variation of your answer here. It would be the greatest thing since sliced bread if I would have worked a VLA into the compound literal -- but alas, the compiler reminded me otherwise ... `:)` – David C. Rankin Sep 18 '21 at 00:48
1

Hm.... you can do the following (save the pointer for later processing, like free()ing it)

char *p;
printf("%s", p = my_function(arg));
free(p);

or, even more readably

void *p = my_function(arg);
printf("%s", p);
free(p);

A second approach is to design my_function() to operate on a preallocated (I'll explain it later) buffer.

char *my_function(some_type arg, char *buffer, size_t buff_sz)
{
    /* use buffer as a work buffer */
    /* ... */
    return buffer;
}

and then use it with some buffer, allocated in an auto variable:

    if (something) {
        char buffer[1000];
        printf("%s", my_function(arg, buffer, sizeof buffer));
    }
    /* buffer is gone here */

This is a very flexible way of working, once you have mastered it.

Once I wrote a routine that saved a series of allocated pointers to free() (or possibly postprocess the pointers) them all once work had finished. In order to allow expressions like the one you proposed, the function took a pointer (the allocated pointer) and saved it in a structure, the function returned its argument, so the construction above was possible

It is composed of only a header file and a simple file that contains two functions:

  1. mf_save(), to save a pointer for later freeing.
  2. mf_free(), to post-process (and free) all the pointers saved in one shot. This routine also free()s the allocated memory for saving the pointers, so the postprocessing is done only once, after that all saved pointers are lost, so you have better to free() them in the post-processing routine pointer you pass to it. A common use it to pass it just free.

mf.h

/* mf.h -- types and definitions for mf.c.
 * Author: Luis Colorado <luiscoloradourcola@gmail.com>
 * Date: Sun Sep 19 12:16:18 EEST 2021
 * Copyright: (c) 1992-2021 Luis Colorado.  All rights reserved.
 * License: BSD.
 */
#ifndef _MF_H
#define _MF_H

#define MF_F(_fmt) "%s:%d:%s: "_fmt, __FILE__, __LINE__, __func__

typedef struct mf_s *mf_t;

void *mf_save(mf_t *safe_ref, void *ptr);

void  mf_free(mf_t *safe_ref, void (*proc)(void *));

#endif /* _MF_H */

mf.c

This is the core of the module.

/* mf.c -- a module to save pointers for batch free()ing, after
 * work is done.
 * Author: Luis Colorado <luiscoloradourcola@gmail.com>
 * Date: Sun Sep 19 11:54:37 EEST 2021
 * Copyright: (c) 1992-2021 Luis Colorado.  All rights reserved.
 * License: BSD.
 */

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

#include "mf.h"

#define MF_SIZE     (3)
#define THIS    (*safe_ref)

struct mf_s {
    size_t  mf_n;           /* num of saved in this block */
    mf_t    mf_nxt;         /* nxt chunk pointer */
    mf_t    mf_vec[MF_SIZE];/* saved pointers */
};

static mf_t
mf_alloc(mf_t prev)
{
    /* call calloc, so the block is zeroed */
    mf_t ret_val = calloc(1, sizeof * ret_val);
    assert(ret_val != NULL);
    ret_val->mf_nxt = prev;
    return ret_val;
}

void *
mf_save(mf_t *safe_ref, void *ptr)
{
    if (THIS == NULL || THIS->mf_n == MF_SIZE) { /* full chunk */
        printf(MF_F("full chunk, allocating new [%p]"), THIS);
        THIS = mf_alloc(THIS);
        printf(" -> [%p]\n", THIS);
    }
    mf_t s = THIS;
    s->mf_vec[s->mf_n++] = ptr;
    printf(MF_F("SAVED [%p]\n"), ptr);
    return ptr;
}

void
mf_free(mf_t *safe_ref, void (*proc)(void *))
{
    while(THIS) {
        int ix;
        for (ix = 0; ix < THIS->mf_n; ++ix) {
            void *p = THIS->mf_vec[ix];
            printf(MF_F("processing [%p]\n"), p);
            proc(p);
        }
        mf_t temp = THIS;
        THIS = THIS->mf_nxt;
        printf(MF_F("freeing chunk @ [%p] -> [%p]\n"), temp, THIS);
        free(temp);
    }
}

The good news with this approach is that it uses dynamic memory only, and the safe only occupies the size of a pointer in the stack. Another good think is that it was written in 1992, and so it doesn't require any compiler novelties.


test_mf.c

This is a test routine that allocates 10 pointers (randomly sized, but with space to store in them the allocated size ---the rest is not used) and then deallocates them by calling mf_free().

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

#include "mf.h"

/* this is a wrapper to malloc() to store the number of bytes in
 * the allocated memory, so the processing() routine has the
 * possibility of printing the amount of allocated memory */
static void *
malloc_wrapper(size_t n)
{
    int *ret_val = malloc(n);
    printf(MF_F("allocated %zd bytes @ %p\n"), n, ret_val);
    *ret_val = n;
    return ret_val;
}

/* this is a wrapper to free() to printf the number of allocated
 * bytes that are being freed. */
void
free_wrapper(void *p)
{
    int *q = p;
    printf(MF_F("freeing %d bytes @ %p\n"), *q, p);
    free(p);
}

/* main program, allocate 1000 random sized buckets of memory,
 * then deallocate them. */
int
main()
{
    int i;
    mf_t safe = NULL; /* initialize to NULL always */
    for (i = 0; i < 10; i++) { /* several allocations */
        int size = rand() & 1000 + sizeof(int);
        printf("Allocating %d bytes @ %p\n", size, mf_save(&safe, malloc_wrapper(size)));
    }
    /* all are freed at the end of the work */
    mf_free(&safe, free_wrapper);
}

(Note: I've written wrappers for malloc() and free() to insert traces on the amount of memory allocated/deallocated, but you can use malloc() and free() in their place.)


Makefile

This Makefile allows you to build the test program.

targets     = test_mf

test_mf_objs = test_mf.o mf.o
to_clean = test_mf $(test_mf_objs)

all: $(targets)
clean:
        $(RM) $(toclean)

test_mf: $(test_mf_deps) $(test_mf_objs)
        $(CC) $(CFLAGS) $(LDFLAGS) $($@_ldfl) -o $@ $($@_objs) $($@_libs)

The routines here are copyrighted, but you are free to use them under the BSD license.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
  • Yes, assigning the function's return value to a variable would be the most standard and readable way to it, but my question was about the situation where you DON'T use variables, and thus, where it's impossible to track the location of the allocated memory, which makes it impossibleto free it. – matteobu02 Sep 19 '21 at 09:09
  • I suggest you to check at the included code, as it make an alternative that allows you to _almost_ achieve your target with dynamically allocated mem. – Luis Colorado Sep 19 '21 at 10:34