Memory management in C

I have started a habit (possibly a bad one, I don't know) of avoiding using malloc/free in my C code. I have found that by doing so the number of problems I encounter when developing something has drastically reduced. When using malloc/free, as the project increases in complexity, I find myself debugging segfaults, double-free and other pointer related errors - despite my best efforts to keep a handle on them.

I tend to break my project up into separate files, where each file contains code related to a struct, or a set of structs. In this sense the code end up being a little bit object-oriented-ish. However I never for example store function pointer "methods" in my structs or anything like that. Lets call them "modules". When I do that I put the struct definition in the header file so my structs are never "private", and any other code with the header can know how big the struct is.

I follow a pattern where I define the struct (without a typedef) in the header like this:

struct some_feature {
    ...
};

Then in the .c file I will usually create an init function:

int some_feature_init(struct some_feature* sf) {
    ...
}

Where I cleanly initialize the struct and fill in default values, etc. I then make it the responsibility of the caller/user of that module to manage the memory, and I usually do that by declaring stack-allocated variables:

int main() {
    struct some_feature sf;
    some_feature_init(&sf);
    return 0;
}

So the allocation of sf is done on the stack, and when the function returns that is magically cleaned (or at least overwitten on the next function call). Of course I never return a pointer to a stack-allocated struct, however I do frequently pass pointers to these "down the call stack", eg:

void setup(struct some_feature* sf) {
    some_feature_do_frob(sf, 21);
}

int main() {
    struct some_feature sf;
    some_feature_init(&sf);
    setup(&sf);
    return 0;
}

Which I believe to be safe - at least I have never encountered any issues with this.

What about when you need to handle a variable number of these struct?

I have found that very often it's possible to know exactly how many of a thing I need - if not at compile time, than at least at run-time before I need to allocate memory. In more extreme cases I might provide a function in a module that will return a count of how many structs I need, for example:

int some_feature_count_items(int nr_battens) {
    // usually more complex than this, but just as an example
    return nr_battens * 2;
}

And then make use of run-time allocation via a variable-length array:

int main() {
    int nr_structs = some_feature_count_items(22);
    struct some_feature sf[nr_structs];
    for (int i = 0; i < nr_structs; i++) {
        some_feature_init(&sf[i]);
        setup(&sf[i]);
    }
    return 0;
}

One way realloc can trip you

Let's say you malloc a blob of memory that you intend to realloc later on to resize it. If you take pointers into that original blob and store them somewhere in variables or whatever, and then you realloc the blob to change it's size, those pointers you stored could now be pointing to a chunk of memory that was free'd when the reallocation happened.

Stack blowout

One argument I have seen against using the stack to store large amounts of data is that when you exceed the maximum stack size there is no way to detect that in the code. There is nothing you can check for, as there is no "return value" that will tell you you can't allocate. However, I have also seen a similar argument levelled at malloc - it's possible apparently to malloc more than your physical RAM and still get a non-null pointer back. I don't know what the OS does when you use all that up though - does it turn to swap space to make up the difference? What happens when your allocation is greater than RAM+swap? And the alloca function doesn't appear to help here:

The alloca() function returns a pointer to the beginning of the
allocated space.  If the allocation causes stack overflow,
program behavior is undefined.

Anyway, so far I have been greatly enjoying writing segfault-free code, and never experienced stack issues.

Use malloc but never free it

Another way is to use malloc but just never free it. If your program is a "batch mode" program, for example a commandline program that you know will exit after some short time, you can just let the OS clean up the allocated memory when the process is terminated.