Benefits of dynamic memory allocation in programming

I’m familiarising mywself with C, and currently I’m learning about dynamic memory allocation. So, for instance, if, for whatever reason, I need a buffer that can hold ten floating-point numbers, I can do the following:

(1) float* buffer = malloc(sizeof(float) * 10);

The return value “buffer” of this function call will be a pointer to the start of the memory block. But alternatively, I could do the same with an array:

(2) float buffer[10];

Again, “buffer” is a pointer to the first element in the allocated memory.

Is there any reason why I should prefer (1) over (2)? The resources I’m relying on (online courses and a print book) claim that dynamic memory allocation with malloc is preferable when you don’t know at compile time how much memory you’re going to need. I don’t understand what this is supposed to mean. After all, malloc, just like the declaration of an array, needs the size of the required memory space as a parameter. Sure, there is no need to hard-wire that size; I can, for instance, calculate elsewhere in my program an integer variable “size” corresponding to the number of floats my buffer will need to hold:

(3) float* buffer = malloc(sizeof(float) * size);

But the same thing works with arrays:

(4) float buffer[size];

I read that memory created via malloc is in the heap whereas arrays sit in the stack. But why would I care about this difference? It’s all coming out of the same overall memory that my computer has, after all. So the only difference that I can see is that malloc allows for subsequent expansion with realloc if I later decide that I want to add more elements. But as I understand it, realloc is kind of a lucky shot: It works nicely if the additional memory (in the equired amount) happens to be available after the end of the originally allocated block; in that case, the new elements will be tacked on back-to-back. If no memory is available there, realloc will allocate new contiguous memory elsewhere and copy the existing elements there. This is something that I can also do with arrays.

It all depends on the application.
I program in C for embedded systems. I never use dynamic memory allocation. There is no “graceful failure” available, so I make sure when I am developing the application that there is always enough memory to do what I need, and I pre-allocate it.
For desktop applications (which tend to be more sophisticated, and also have much more varied data use cases), dynamic allocation makes sense - if a person wants to edit a huge image, you can attempt to allocate a buffer, and if that fails, put up an error message.

Also, with dynamically allocated memory, you can shuffle large buffers around. Releasing one to free up memory for another. And, probably most importantly, in modern OSs, dynamically allocated memory uses the VM subsystem, meaning that memory is practically unlimited. This is not true for static arrays.

Also, arrays are allocated on the stack, which tends to have much less memory available to it (megabytes vs gigabytes in the heap).

Trivial example: Suppose you want a program that asks the user for a list of numbers, and then outputs the same list of numbers (or maybe something that comes from that list, like the median). You won’t know until after it’s already running how many numbers the user will enter. float buffer[size]; won’t work unless the compiler can tell, at time of compiling (well before it’ll be run) what size is.

For toy programs it doesn’t matter. But for programs that are written to perform real word tasks you need to consider what happens when you run out of available memory. Continually allocating memory from the heap will eventually deplete the heap. For large tasks or long lived tasks this is a serious issue.
Say you write a function that performs some task that requires a slab of memory of size that isn’t known at compile time, once you allocate that memory with malloc, what happens when the function exits? If you used malloc it is lost forever. So you free it. This places the memory back on the heap. Your program can run essentially.

In a language like C you have 4 kinds of storage, one of which you don’t usually see. These are static (allocated when the program starts up), automatic (on the stack), and dynamic (malloc and free). The one you don’t see is registers, where the compiler never allocates any space at all and just keeps the value in a register.

Languages for typical application tasks nowadays use automatic storage management (typically via garbage collection) to manage the heap. However C and it brethren, in general, can’t perform automatic garbage collection because there is no way of knowing a-priori where pointers to memory may be lurking in the dynamic state of a running program. Languages like Java and Python don’t allow creation or manipulation of pointers except by the language proper. In C you can just cast an integer to a pointer, and things like array indexing are explicitly just pointer arithmetic.

As @beowulff notes, there are times when you do exactly know where every bit of data lives, and you need to know exactly where every cycle of computation goes. Garbage collection is pretty much ruled out here as it runs automatically in the background, and you can’t reason about program performance well enough. (There are real time garbage collection algorithms, but they come with other issues.)

In general, modern general purpose languages provide automatic storage management. But this isn’t always the best answer.

Note, it is possible to fragment the heap. If you malloc a lot of bits of memory, and then say free every second one, the heap will be filled with holes. If you ask for a larger lump of memory, there may not be a contiguous length of space to fit it. A garbage collected system, because it knows where the pointers in your data are, is able to shuffle the data about, fix up the pointers everywhere, and reclaim all the space. With no GC you are left to build bespoke solutions, such as pool managers.

Note that case #4, defining an array with a variable size, wasn’t originally supported by C. Variable length arrays were introduced in C99 I believe. So you may find yourself using a compiler that doesn’t support C99, in which case that feature is unavailable and you are forced to use malloc.

In some environments the amount of stack space is limited, so for large arrays malloc may be the only choice. That is, it may be that
char* a = malloc(1000000) will work but char a[1000000] will fail.

Another common case is where you want to allocate an array and have it persist beyond the return of the current function. A stack array is automatically deleted when the current function exits, while a malloc’d array persists until you call free(). Of course this is also a disadvantage of malloc; if you neglect to call free() you have a memory leak.

Or you can remember to call free but forget that you still have pointers to the freed memory. When you actually look, that memory may still contain what you thought you put there or it may have been overwritten with something else. Who knows? You may very well have created a bug you won’t find for years.

When I was a young larval developer, I loved showing off how smart I was by hand rolling elaborate linked structures built on malloc() and free(). They all worked after untold hours of painstaking development and heartrending debugging. At least they did until one day they didn’t…

I was able to do this because I was the only trained programmer in the shop and nobody was reining me in. Even then I could have done much better using simpler tools and not optimizing a bunch of stuff that was already good enough and showing off how clever I was. Now of course, I use ArrayLists and other built in tools that manage memory behind the scenes without allowing the developer direct access to enough rope to hang themself with.

The people working on embedded systems and performance critical applications know who they are and can budget for the extra effort required to do their own memory management. Everyone else should leave it to “the system”.

Think of a common type of application that needs to read in some data records from a file, do some processing on that data, then output the result. You often will not know how big the file is beforehand, it may be different on every run. Whilst developing your app you typically have test data sets with just a handful of records, but in production you may have hundreds or thousands of records. Scaling such an application with a single array allocation will be tricky so one approach is to make a separate memory allocation for each individual record and stored them in a dynamic data structure such as a linked list of binary tree. My bet is you will learn about these kinds of structures in future lessons.

This wasn’t always the case. Dynamically sized stack-allocated arrays were added in the C99 standard (along with lots of other nice-to-have features, like not having to declare all your variables at the beginning of a block). Prior to C99, you had to allocate variable sized allocations via malloc or some other allocator.

Creating a local array causes the memory to be allocated on the stack. If you don’t know how much room you have available on the stack, you’re much more likely to exceed the amount that you’re allowed to put into it. That’s really the main issue. The heap is sized for buckets of data. The stack is sized for local loop counters and the like. (Yes, that’s an exaggeration but it’s the way that you’re expected to code things if you haven’t properly worked out the sizes available to you and what you need for your actual problem that you’re solving.)

Other pros and cons:

The stack is far quicker to allocate. It just adjusts the top of the stack pointer without actually allocating any more memory from the OS. The heap is doing all sorts of nonsense to work.

Variables on the stack are only guaranteed to stay as they are while in scope for your code (I believe?) If you try to return a pointer to one, for example, you’re going to have some crazy bugs later on. If my above belief is correct then passing a pointer into a second function may also be untrustworthy if the bottom of the stack is swapped out to secondary storage as part of the call procedure. (Though, in modern, everyday practice that concern is pretty much none.)

Data on the stack is actually fine, so long as you don’t try to pass data back out of a call. It is fine to pass data to another function call. The caller is by definition still in scope.
Whilst the stack is by default limited to a small size, this is more about catching an infinite recursion early. The usual mechanism (on 32 bit address spaces and similar) is to place the stack far way from the rest of your address space, and have it grow towards that space. Many processor designs explicitly made this so by design, with the call/return instructions automatically growing the stack down in addresses. On a 64 bit address space machine there is a huge amount of freedom.

The stack is limited in size by placing a protected page of memory at the limit, so any attempt to run past this limit is caught by a protection fault. This isn’t 100% foolproof, but is close enough. It is common to be able to change the stack limit. Some languages and uses need this to work. Here is is possible to have the stack use a very large fraction of the available address space.

Where you get into trouble is with multi-threaded code, as each thread needs a stack, and you now need to set a limit from the outset that stops threads crashing into one another.

Data on the stack is no different to any other sort of data. The OS doesn’t really take much notice, you can swap it out to relieve pressure on physical memory, or whatever. However some architectures do get into something of a mess when managing a mix of exceptions (which page faults usually are) and handing VM. When handling the exception, triggering a second exception may require extraordinary care, or must be studiously avoided. But this is a problem for the operating system implementer, not the user code.

Thanks for the replies everyone. I must say I’ve understood most though not quite everything of what has been said, but I think I’ll get the general idea, and the remainder should become clearer to me as I proceed in my course and play around more writing my own little programs.

I’d add (in true Stack Overflow style not actually answering the OP’s question, but I think other posters have) that nowadays I’d get in the habit of using the new* “smart” pointer types in C++ for memory allocation. You want to understand how memory allocation works and what the heap and stack are, but then just use shared_ptr and unique_ptr in practice. Everywhere I’ve worked in recent years has coding standards that force you to use smart pointers over direct memory allocation.

    • new for old fogeys like myself who still think of PS3s as “next gen” gaming consoles.