This is one of the big areas where C likely diverges from languages you already know: manual memory management.
Other languages uses reference counting, garbage collection, or other means to determine when to allocate new memory for some data—and when to deallocate it when no variables refer to it.
And that’s nice. It’s nice to be able to not worry about it, to just drop all the references to an item and trust that at some point the memory associated with it will be freed.
But C’s not like that, entirely.
Of course, in C, some variables are automatically allocated and deallocated when they come into scope and leave scope. We call these automatic variables. They’re your average run-of-the-mill block scope “local” variables. No problem.
But what if you want something to persist longer than a particular block? This is where manual memory management comes into play.
You can tell C explicitly to allocate for you a certain number of bytes that you can use as you please. And these bytes will remain allocated until you explicitly free that memory89.
It’s important to free the memory you’re done with! If you don’t, we call that a memory leak and your process will continue to reserve that memory until it exits.
If you manually allocated it, you have to manually free it when you’re done with it.
So how do we do this? We’re going to learn a couple new functions, and make use of the sizeof
operator to help us learn how many bytes to allocate.
In common C parlance, devs say that automatic local variables are allocated “on the stack”, and manually-allocated memory is “on the heap”. The spec doesn’t talk about either of those things, but all C devs will know what you’re talking about if you bring them up.
All functions we’re going to learn in this chapter can be found in <stdlib.h>
.
malloc()
and free()
The malloc()
function accepts a number of bytes to allocate, and returns a void pointer to that block of newly-allocated memory.
Since it’s a void*
, you can assign it into whatever pointer type you want… normally this will correspond in some way to the number of bytes you’re allocating.
So… how many bytes should I allocate? We can use sizeof
to help with that. If we want to allocate enough room for a single int
, we can use sizeof(int)
and pass that to malloc()
.
After we’re done with some allocated memory, we can call free()
to indicate we’re done with that memory and it can be used for something else. As an argument, you pass the same pointer you got from malloc()
(or a copy of it). It’s undefined behavior to use a memory region after you free()
it.
Let’s try. We’ll allocate enough memory for an int
, and then store something there, and the print it.
// Allocate space for a single int (sizeof(int) bytes-worth):
int *p = malloc(sizeof(int));
*p = 12; // Store something there
("%d\n", *p); // Print it: 12
printf
(p); // All done with that memory
free
//*p = 3490; // ERROR: undefined behavior! Use after free()!
Now, in that contrived example, there’s really no benefit to it. We could have just used an automatic int
and it would have worked. But we’ll see how the ability to allocate memory this way has its advantages, especially with more complex data structures.
One more thing you’ll commonly see takes advantage of the fact that sizeof
can give you the size of the result type of any constant expression. So you could put a variable name in there, too, and use that. Here’s an example of that, just like the previous one:
int *p = malloc(sizeof *p); // *p is an int, so same as sizeof(int)
All the allocation functions return a pointer to the newly-allocated stretch of memory, or NULL
if the memory cannot be allocated for some reason.
Some OSes like Linux can be configured in such a way that malloc()
never returns NULL
, even if you’re out of memory. But despite this, you should always code it up with protections in mind.
int *x;
= malloc(sizeof(int) * 10);
x
if (x == NULL) {
("Error allocating 10 ints\n");
printf// do something here to handle it
}
Here’s a common pattern that you’ll see, where we do the assignment and the condition on the same line:
int *x;
if ((x = malloc(sizeof(int) * 10)) == NULL)
("Error allocating 10 ints\n");
printf// do something here to handle it
}
We’ve seen how to allocate space for a single thing; now what about for a bunch of them in an array?
In C, an array is a bunch of the same thing back-to-back in a contiguous stretch of memory.
We can allocate a contiguous stretch of memory—we’ve seen how to do that. If we wanted 3490 bytes of memory, we could just ask for it:
char *p = malloc(3490); // Voila
And—indeed!—that’s an array of 3490 char
s (AKA a string!) since each char
is 1 byte. In other words, sizeof(char)
is 1
.
Note: there’s no initialization done on the newly-allocated memory—it’s full of garbage. Clear it with memset()
if you want to, or see calloc()
, below.
But we can just multiply the size of the thing we want by the number of elements we want, and then access them using either pointer or array notation. Example!
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// Allocate space for 10 ints
int *p = malloc(sizeof(int) * 10);
// Assign them values 0-45:
for (int i = 0; i < 10; i++)
p[i] = i * 5;
// Print all values 0, 5, 10, 15, ..., 40, 45
for (int i = 0; i < 10; i++)
printf("%d\n", p[i]);
// Free the space
free(p);
}
The key’s in that malloc()
line. If we know each int
takes sizeof(int)
bytes to hold it, and we know we want 10 of them, we can just allocate exactly that many bytes with:
sizeof(int) * 10
And this trick works for every type. Just pass it to sizeof
and multiply by the size of the array.
calloc()
This is another allocation function that works similarly to malloc()
, with two key differences:
You still use free()
to deallocate memory obtained through calloc()
.
Here’s a comparison of calloc()
and malloc()
.
// Allocate space for 10 ints with calloc(), initialized to 0:
int *p = calloc(10, sizeof(int));
// Allocate space for 10 ints with malloc(), initialized to 0:
int *q = malloc(10 * sizeof(int));
(q, 0, 10 * sizeof(int)); // set to 0 memset
Again, the result is the same for both except malloc()
doesn’t zero the memory by default.
realloc()
If you’ve already allocated 10 int
s, but later you decide you need 20, what can you do?
One option is to allocate some new space, and then memcpy()
the memory over… but it turns out that sometimes you don’t need to move anything. And there’s one function that’s just smart enough to do the right thing in all the right circumstances: realloc()
.
It takes a pointer to some previously-allocted memory (by malloc()
or calloc()
) and a new size for the memory region to be.
It then grows or shrinks that memory, and returns a pointer to it. Sometimes it might return the same pointer (if the data didn’t have to be copied elsewhere), or it might return a different one (if the data did have to be copied).
Be sure when you call realloc()
, you specify the number of bytes to allocate, and not just the number of array elements! That is:
*= 2;
num_floats
= realloc(p, num_floats); // WRONG: need bytes, not number of elements!
np
= realloc(p, num_floats * sizeof(float)); // Better! np
Let’s allocate an array of 20 float
s, and then change our mind and make it an array of 40.
We’re going to assign the return value of realloc()
into another pointer just to make sure it’s not NULL
. If it’s not, then we can reassign it into our original pointer. (If we just assigned the return value directly into the original pointer, we’d lose that pointer if the function returned NULL
and we’d have no way to get it back.)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// Allocate space for 20 floats
float *p = malloc(sizeof *p * 20); // sizeof *p same as sizeof(float)
// Assign them fractional values 0.0-1.0:
for (int i = 0; i < 20; i++)
p[i] = i / 20.0;
// But wait! Let's actually make this an array of 40 elements
float *new_p = realloc(p, sizeof *p * 40);
// Check to see if we successfully reallocated
if (new_p == NULL) {
printf("Error reallocing\n");
return 1;
}
// If we did, we can just reassign p
p = new_p;
// And assign the new elements values in the range 1.0-2.0
for (int i = 20; i < 40; i++)
p[i] = 1.0 + (i - 20) / 20.0;
// Print all values 0.0-2.0 in the 40 elements:
for (int i = 0; i < 40; i++)
printf("%f\n", p[i]);
// Free the space
free(p);
}
Notice in there how we took the return value from realloc()
and reassigned it into the same pointer variable p
that we passed in. That’s pretty common to do.
Also if line 7 is looking weird, with that sizeof *p
in there, remember that sizeof
works on the size of the type of the expression. And the type of *p
is float
, so that line is equivalent to sizeof(float)
.
I want to demonstrate two things with this full-blown example.
realloc()
to grow a buffer as we read in more data.realloc()
to shrink the buffer down to the perfect size after we’ve completed the read.What we see here is a loop that calls fgetc()
over and over to append to a buffer until we see that the last character is a newline.
Once it finds the newline, it shrinks the buffer to just the right size and returns it.
#include <stdio.h>
#include <stdlib.h>
// Read a line of arbitrary size from a file
//
// Returns a pointer to the line.
// Returns NULL on EOF or error.
//
// It's up to the caller to free() this pointer when done with it.
//
// Note that this strips the newline from the result. If you need
// it in there, probably best to switch this to a do-while.
char *readline(FILE *fp)
{
int offset = 0; // Index next char goes in the buffer
int bufsize = 4; // Preferably power of 2 initial size
char *buf; // The buffer
int c; // The character we've read in
buf = malloc(bufsize); // Allocate initial buffer
if (buf == NULL) // Error check
return NULL;
// Main loop--read until newline or EOF
while (c = fgetc(fp), c != '\n' && c != EOF) {
// Check if we're out of room in the buffer accounting
// for the extra byte for the NUL terminator
if (offset == bufsize - 1) { // -1 for the NUL terminator
bufsize *= 2; // 2x the space
char *new_buf = realloc(buf, bufsize);
if (new_buf == NULL) {
free(buf); // On error, free and bail
return NULL;
}
buf = new_buf; // Successful realloc
}
buf[offset++] = c; // Add the byte onto the buffer
}
// We hit newline or EOF...
// If at EOF and we read no bytes, free the buffer and
// return NULL to indicate we're at EOF:
if (c == EOF && offset == 0) {
free(buf);
return NULL;
}
// Shrink to fit
if (offset < bufsize - 1) { // If we're short of the end
char *new_buf = realloc(buf, offset + 1); // +1 for NUL terminator
// If successful, point buf to new_buf;
// otherwise we'll just leave buf where it is
if (new_buf != NULL)
buf = new_buf;
}
// Add the NUL terminator
buf[offset] = '\0';
return buf;
}
int main(void)
{
FILE *fp = fopen("foo.txt", "r");
char *line;
while ((line = readline(fp)) != NULL) {
printf("%s\n", line);
free(line);
}
fclose(fp);
}
When growing memory like this, it’s common (though hardly a law) to double the space needed each step just to minimize the number of realloc()
s that occur.
Finally you might note that readline()
returns a pointer to a malloc()
d buffer. As such, it’s up to the caller to explicitly free()
that memory when it’s done with it.
realloc()
with NULL
Trivia time! These two lines are equivalent:
char *p = malloc(3490);
char *p = realloc(NULL, 3490);
That could be convenient if you have some kind of allocation loop and you don’t want to special-case the first malloc()
.
int *p = NULL;
int length = 0;
while (!done) {
// Allocate 10 more ints:
+= 10;
length = realloc(p, sizeof *p * length);
p
// Do amazing things
// ...
}
In that example, we didn’t need an initial malloc()
since p
was NULL
to start.
You probably aren’t going to need to use this.
And I don’t want to get too far off in the weeds talking about it right now, but there’s this thing called memory alignment, which has to do with the memory address (pointer value) being a multiple of a certain number.
For example, a system might require that 16-bit values begin on memory addresses that are multiples of 2. Or that 64-bit values begin on memory addresses that are multiples of 2, 4, or 8, for example. It depends on the CPU.
Some systems require this kind of alignment for fast memory access, or some even for memory access at all.
Now, if you use malloc()
, calloc()
, or realloc()
, C will give you a chunk of memory that’s well-aligned for any value at all, even struct
s. Works in all cases.
But there might be times that you know that some data can be aligned at a smaller boundary, or must be aligned at a larger one for some reason. I imagine this is more common with embedded systems programming.
In those cases, you can specify an alignment with aligned_alloc()
.
The alignment is an integer power of two greater than zero, so 2
, 4
, 8
, 16
, etc. and you give that to aligned_alloc()
before the number of bytes you’re interested in.
The other restriction is that the number of bytes you allocate needs to be a multiple of the alignment. But this might be changing. See C Defect Report 46090
Let’s do an example, allocating on a 64-byte boundary:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
// Allocate 256 bytes aligned on a 64-byte boundary
char *p = aligned_alloc(64, 256); // 256 == 64 * 4
// Copy a string in there and print it
strcpy(p, "Hello, world!");
printf("%s\n", p);
// Free the space
free(p);
}
I want to throw a note here about realloc()
and aligned_alloc()
. realloc()
doesn’t have any alignment guarantees, so if you need to get some aligned reallocated space, you’ll have to do it the hard way with memcpy()
.
Here’s a non-standard aligned_realloc()
function, if you need it:
void *aligned_realloc(void *ptr, size_t old_size, size_t alignment, size_t size)
{
char *new_ptr = aligned_alloc(alignment, size);
if (new_ptr == NULL)
return NULL;
size_t copy_size = old_size < size? old_size: size; // get min
if (ptr != NULL)
(new_ptr, ptr, copy_size);
memcpy
(ptr);
free
return new_ptr;
}
Note that it always copies data, taking time, while real realloc()
will avoid that if it can. So this is hardly efficient. Avoid needing to reallocate custom-aligned data.