2

I often would like to use a hypothetical free_if_heap(void *ptr) function, as this would let me return either malloc'd, static or stack objects without caring. e.g.

char *foo(int arg) {
    if (arg < 0) {
        return "arg is negative";
    }

    if (arg > 0) {
        size_t size = snprintf(NULL, 0, "%i is an invalid value", arg);
        char *ret = malloc(size + 1); // FIXME: handle failure
        sprintf(ret, "%i is an invalid value", arg);
        // I use a varargs macro for the three lines above, to avoid format string duplication errors.
    }

    return NULL;
}

void main(void) {
    for (int i = -1; i < 2; ++i) {
        char *err = foo(i);
        if (err) {
            printf("Error: %s\n", err);
            free_if_heap(err);
        }
    }
}

Obviously such a function must not be a good idea, as it has never even made it as far as malloc libraries, let alone the C standards.

Why is free_if_heap(void *ptr) a bad idea?


Update:

The function foo is just an example of a function which can return either a pointer to malloc'd data or to static/global data. It's not a serious function with a purpose.


Update:

Needing to know whether a pointer (of an otherwise known type, like char *, points to the heap is different from needing to know what type of data a void * pointer is pointing to.

free_if_heap(void *ptr) takes a void * argument to avoid having free_if_heap_char(char *ptr), free_if_heap_int(int *ptr) and a hundred others variants.

fadedbee
  • 42,671
  • 44
  • 178
  • 308
  • @Yunnosch The `size` of the returned string is calculated using `snprint` to not write characters, but to just use its return value. The required `size` is then `malloc`'d and the error written into it using a`sprintf` with the same format string. I do this in a macro, so that I only have to write the format string once, avoiding errors. – fadedbee Aug 14 '20 at 07:48
  • Oh I see. You are not showing an implementation, you are showing how you want to use it. Please explain more about it, in addition to the given examples. – Yunnosch Aug 14 '20 at 07:51
  • @Yunnosch To pass a variably sized object of any type back to a caller requires malloc. Having a `free_if_heap` just allows fixed-sized objects to not require pointless mallocs. – fadedbee Aug 14 '20 at 07:52
  • Not a duplicate answer, but please have a look at the discussion here https://stackoverflow.com/a/58281061/7733418 (admittedly one of my answers). I would like to make sure that we agree on the meaning of void pointers. – Yunnosch Aug 14 '20 at 07:56
  • 1
    To be honest, I think the problem is caused by functions which potentially return pointers which need freeing or pointers which don't. That is a design mistake in my opinion. Reasons see the link I provided above. – Yunnosch Aug 14 '20 at 07:59
  • @Yunnosch I've used `void *` as that is what `malloc` returns and `free` accepts. `void *` is the correct type to use, in some circumstances. – fadedbee Aug 14 '20 at 07:59
  • Using `void *` is not the problem. Expecting the caller to know what it points to is the mistake. – Yunnosch Aug 14 '20 at 08:00
  • 1
    `Why is free_if_heap(void *ptr) a bad idea?` because the overhead needed to determine if a pointer points to heap is not needed. A C programmer can do it himself. A C programmer will want to do it himself, so that no magic additional code is executed. Isn't this question opinion based? – KamilCuk Aug 14 '20 at 08:02
  • Do I understand correclty that you do not have an idea of how to implement `free_if_heap(void *ptr)`? Or are you referring to an implementation which has to be provided along with the corresponding malloc()? Because that might actually have access to info for finding out whether the pointer was malloced. If you mean it otherwise I think my link **is** in fact to a duplicate... – Yunnosch Aug 14 '20 at 08:02
  • My point is that a lot of my C code would be better if `free_if_heap` existed. I can't use the pattern in my question because I don't know whether the `char *` must/can be freed. Because of this I have to occassionally `strdup` and `free` literal strings, just to keep the option of returning malloc'd strings. – fadedbee Aug 14 '20 at 08:04
  • @KamilCuk I think you imply that C programmers always need to know what a pointer points to. I very much agree (I linked the reasons). But the idea here seems to be that this need can be avoided. However, I think that the need does not exist with correctly designed interfaces. The shown example function could easily be changed to ALWAYS return a pointer which needs freeing. – Yunnosch Aug 14 '20 at 08:04
  • To me the solution is NOT to design functions which return to-maybe-freed pointers. Always return to-be-freed pointers or pointers which do not need freeing. – Yunnosch Aug 14 '20 at 08:05
  • Please provide an example, with reasoning, of a function which has to return a potentially free-needing pointer. I cannot think of anything. The given example does not help, because it can be changed to always return a to-be-freed pointer. – Yunnosch Aug 14 '20 at 08:07
  • 2
    @Yunnosch Then this makes it one of the unanswerable questions "why is something not in C", doesn't it? It's not, because it was never implemented. Because it's not there. – KamilCuk Aug 14 '20 at 08:08
  • I really am convinced that the answer is that C is designed on the assumption that void pointers increase the need to know what they are pointing to. Anything trying to go around that is a design mistake. Especially functions which prevent the caller from knowing. So I close-vote as duplicate of a question discussing this. (trusting in not having the hammer...) – Yunnosch Aug 14 '20 at 08:13
  • Does this answer your question? [How to verify if a void pointer (void \*) is one of two data types?](https://stackoverflow.com/questions/58280538/how-to-verify-if-a-void-pointer-void-is-one-of-two-data-types) – Yunnosch Aug 14 '20 at 08:13
  • I offer to make (in several hours) that a tailored answer here, just let me know. – Yunnosch Aug 14 '20 at 08:15
  • @Yunnosch, thanks for your offer, but I have just realised how I could do this myself with a wrapper around `malloc`. `my_malloc` would store the address it returns in a tree, and `free_if_heap` would check that tree to see if it was originally malloc'd. More hackily, I could just compare the pointer with static, heap and stack sentinels. – fadedbee Aug 14 '20 at 08:40
  • *`free_if_heap` would check that tree to see if it was originally malloc'd* You need to lock your tree **and** block `malloc()`/`calloc()`/etc while you're doing your check. *I could just compare the pointer with static, heap and stack sentinels.* No, you **can't**. Threads have stacks allocated via `mmap()`, and many heap implementations will use `mmap()` for large allocations. – Andrew Henle Aug 14 '20 at 14:42

1 Answers1

2

The function void free_if_heap(void *ptr) cannot be implemented portably* but it might be possible on selected targets, depending on the local implementation of malloc().

If you cannot or do not want to deal with allocated objects' life cycle, you should use a different programming language with a garbage collector. There are many efficient alternatives to C, such as Go. You could also try and use a conservative garbage collector for C such as the Boehm–Demers–Weiser garbage collector.

Note however that tracking memory allocated objects and properly disposing of them after use is not so difficult, but requires consistent conventions: for example your foo function should always return an allocated string or a null pointer:

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

char *foo(int arg) {
    if (arg < 0) {
        return strdup("arg is negative");
    }

    if (arg > 0) {
        size_t size = snprintf(NULL, 0, "%i is an invalid value", arg);
        char *ret = malloc(size + 1);
        if (ret != NULL) {
            snprintf(ret, size + 1, "%i is an invalid value", arg);
            return ret;
        }
    }
    return NULL;
}

int main(void) {
    for (int i = -1; i < 2; ++i) {
        char *err = foo(i);
        if (err) {
            printf("Error: %s\n", err);
            free(err);
        }
    }
    return 0;
}

(*) a portable implementation would be trivial and ineffective: void free_if_heap(void *ptr) {}

chqrlie
  • 131,814
  • 10
  • 121
  • 189