This simple method just creates an array of dynamic size n and initializes it with values 0 ... n-1. It contains a mistake, malloc() allocates just n instead of sizeof(int) * n bytes:
int *make_array(size_t n) {
int *result = malloc(n);
for (int i = 0; i < n; ++i) {
//printf("%d", i);
result[i] = i;
}
return result;
}
int main() {
int *result = make_array(8);
for (int i = 0; i < 8; ++i) {
printf("%d ", result[i]);
}
free(result);
}
When you check the output you will see that it will print some numbers as expected but the last ones are gibberish. However, once I inserted the printf() inside the loop, the output was strangely correct, even tho the allocation was still wrong! Is there some kind of memory allocation associated with printf()?
You allocate 8 bytes for the array, but you store 8
int
, each of which is at least 2 bytes (probably 4), so you are writing past the end of the allocated memory. Doing so invokes undefined behavior.When you invoke undefined behavior, anything can happen. Your program can crash, it can show unexpected results, or it can appear to work properly. A seemingly unrelated change can change which of the above actions occur.
Fix the memory allocation, and you code will work as expected.
Whether
printf()
allocates any memory in the course of performing its work is unspecified. It would not be surprising if any given implementation did so, but there is no reason to assume that it does. Moreover, if one implementation does, that says nothing about whether a different implementation does.That you see different behavior when the
printf()
is inside the loop tells you nothing. The program exhibits undefined behavior by overrunning the bounds of an allocated object. Once it does that, all subsequent behavior is undefined. You cannot reason about undefined behavior, at least not in terms of C semantics. The program has no C semantics once undefined behavior commences. That's what "undefined" means.Strictly, to answer the question in the title, the answer would be that it depends on the implementation. Some implementations might allocate memory, while others might not.
Though there are other problems inherent in your code, which I will elaborate on below.
Note: this was originally a series of comments I made on the question. I decided that it was too much for a comment, and moved them to this answer.
I believe on systems using a segmented memory model, allocations are "rounded up" to a certain size. I.e. if you allocate X bytes, your program will indeed own those X bytes, however, you'll also be able to (incorrectly) run past those X bytes for a while before the CPU notices that you're violating bounds and sends a SIGSEGV.
This is most likely why your program isn't crashing in your particular configuration. Note that the 8 bytes you allocated will only cover two ints on systems where
sizeof (int)
is 4. The other 24 bytes needed for the other 6 ints do not belong to your array, so anything can write to that space, and when you read from that space, you are going to get garbage, if your program doesn't crash first, that is.The number 6 is important. Remember it for later!
Note: The following is speculation, and I'm also assuming you're using glibc on a 64-bit system. I'm going to add this because I feel it might help you understand possible reasons why something might appear to work correctly, while actually being incorrect.
The reason it's "magically correct" most likely has to do with
printf
receiving those numbers through va_args.printf
is probably populating the memory area just past the array's physical boundary (because vprintf is allocating memory to perform the "itoa" operation needed to printi
). In other words, those "correct" results are actually just garbage that "appears to be correct", but in reality, that's just what happens to be in RAM. If you try changingint
tolong
while keeping the 8 byte allocation, your program will be more likely to crash becauselong
is longer thanint
.The glibc implementation of malloc has an optimization where it allocates a whole page from the kernel every time it runs out of heap. This makes it faster because rather than ask the kernel for more memory on every allocation, it can just grab available memory from the "pool" and make another "pool" when the first one fills up.
That said, like the stack, malloc's heap pointers, coming from a memory pool, tend to be contiguous (or at least very close together). Meaning that printf's calls to malloc will likely appear just after the 8 bytes you allocated for your int array. No matter how it works, though, the point is that no matter how "correct" the results may seem, they are actually just garbage and you're invoking undefined behavior, so there's no way of knowing what's going to happen, or whether the program will do something else under different circumstances, like crash or produce unexpected behavior.
So I tried running your program with and without the printf, and both times, the results were wrong.
For whatever reason, nothing interfered with the memory holding
2..5
. However, something interfered with the memory holding6
and7
. My guess is that this is vprintf's buffer used to create a string representation of the numbers.1041
would be the text, and0
would be the null terminator,'\0'
. Even if it's not a result of vprintf, something is writing to that address between the population and the printing of the array.This is the interesting part. You didn't mention in your question whether your program crashed. But when I ran it, it crashed. Hard.
It's also a good idea to check with valgrind, if you have it available. Valgrind is a helpful program that reports how you're using your memory. Here is valgrind's output:
As you can see, valgrind reports that you have an
invalid write of size 4
and aninvalid read of size 4
(4 bytes is the size of an int on my system). It's also mentioning that you're reading a block of size 0 that comes after a block of size 8 (the block that you malloc'd). This tells you that you're going past the array and into garbage land. Another thing you might notice is that it generated 12 errors from 2 contexts. Specifically, that's 6 errors in a writing context and 6 errors in a reading context. Exactly the amount of un-allocated space I mentioned earlier.Here's the corrected code:
And here's valgrind's output:
Notice that it reports no errors and that the results are correct.