1

When programming in C, distinguishing between pointers that point to stack memory and those that point to heap memory is of vital importance (e.g. one cannot call free with a reference to stack-allocated memory). Despite this, the C language itself provides no base facilities to qualify pointers to make the aforementioned distinction explicit.

Are there any programming patterns in which people use an empty macro definition called heap (i.e. #define heap) or something similar for use as a pointer qualifier?

The utiltiy of such a definition becomes obvious when looking at the type signatures of the functions in the following (admittedly contrived) example:

int *stack_add(int *a, const int *b)
{
    *a += *b;

    return a;
}

int *heap heap_add(const int *a, const int *b)
{
    int *heap sum = malloc(sizeof *sum);

    if (sum == NULL) {
        return NULL;
    }

    *sum = *a + *b;

    return sum;
}

Despite the usefulness of such an idiom, I cannot recall ever having seen this before.

William Ryman
  • 231
  • 2
  • 9
  • 3
    IMHO a better idiom is [RAII in C](https://stackoverflow.com/questions/368385/implementing-raii-in-pure-c). – Corvus Jul 16 '23 at 00:48
  • 4
    When you code in C, you don’t design functions like that and use both indiscriminately. You learn to keep track of heap-allocated data and ensure you release it appropriately, and you learn to keep track of stack-allocated data and you don’t free it explicitly. If your code doesn’t know whether the data pointed to is heap-allocated or not, it has no business trying to free it; that’s a task for code which does know how it is allocated. – Jonathan Leffler Jul 16 '23 at 01:06
  • 2
    C doesn't come with training-wheels -- it's all up to you. With great power comes great responsibility... (almost sounds like a movie line...) – David C. Rankin Jul 16 '23 at 03:27
  • @DavidC.Rankin I agree with that sentiment. The main reason I thought to ask this was because the [`FormatMessage`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessage) function in the Win32 API has a terrible type signature: despite the `lpBuffer` parameter being of type `LPTSTR` (basically `char *`), it's actually a pointer to a string buffer (so, more technically, `char **`). I was just thinking that a type of `char *dyn*` would make it super clear what the actual function is doing instead of blatanly misleading you as the actual type signature does. – William Ryman Jul 16 '23 at 03:34
  • 1
    There is nothing wrong with asking -- there are no dumb questions -- except the one you fail to ask. All of this goes to understanding what the boundaries are regarding what the language provides, doesn't provide or leaves up to the compiler to provide. I don't recommend it, but you can look at the man page for `sbrk` (a system call for manually adjusting the size of available allocated memory). You can find the bottom of the heap and in a round-about way, suss-out where the heap allocations lie.. (but just like writing your own `malloc` - don't do it -- other than for education) – David C. Rankin Jul 16 '23 at 03:39

1 Answers1

0

There is no such construct in C because it would be completely useless. It would be non-typed and therefore non-enforceable. It would, therefore, be just as useful as comments, or "systems" hungarian notation. (https://en.wikipedia.org/wiki/Hungarian_notation)

Even if such a construct was somehow typed, (a language keyword rather than a pre-processor macro, acting like a qualifier, like const,) it would still be useless, because it would be introducing two different kinds of pointers, that cannot be mixed, for no good reason.

The problem with C is not the lack of some mechanism for distinguishing between heap pointers and non-heap pointers in the source code. The problem is lack of any runtime checking for what kind of pointer you are attempting to pass to free(), and the continued lack of any such runtime checking even after the invention of ASSERT(), combined with people's narrow-minded insistence on not using ASSERT() and the broader concept of debug-build-only runtime checks.

You can add such a runtime checking facility yourself, by replacing the built-in malloc() and free() with your own, (on debug builds only,) which returns blocks of memory wrapped in slightly larger blocks of memory that contain special markers so that free() can do some sanity checking before mindlessly attempting to free anything that you pass to it.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • What is `ASSERT` (as opposed to `assert`, presumably)? – Jonathan Leffler Jul 16 '23 at 02:20
  • I don't necessarily disagree with your points, but to play devil's advocate: C has the `restrict` qualifier which is unenforceable and acts as a compiler annotation for optimization. If the `heap` qualifier were to be added, it might act like a human-readable annotation, in the same vein as Python's type annotations. – William Ryman Jul 16 '23 at 02:26
  • Also, `heap` could be implemented where `T *` ⊑ `T *heap`. In this case `T *` and `T *heap` are not necessarily distinct; one is a superset of the other. This way it could be enforceable and backwards compatible. In cases where you don't care about how an object is allocated, then use `T *`. If you do care (as in the case of `realloc` and `free`), then use `T *heap`. `heap` still isn't truly enforceable in this scenario (I agree that basically the only way to fundamentally guarantee the correctness of memory allocation is with regions), but it is still better, in my opinion, than C without it. – William Ryman Jul 16 '23 at 02:33