0

Possible Duplicate:
behaviour of malloc(0)

I'm trying to understand memory allocation in C. So I am experimenting with malloc. I allotted 0 bytes for this pointer but yet it can still hold an integer. As a matter of fact, no matter what number I put into the parameter of malloc, it can still hold any number I give it. Why is this?

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *ptr = (int*)malloc(0);

    *ptr = 9;

    printf("%i", *ptr); // 9

    free(ptr);

    return 0;
}

It still prints 9, what's up with that?

Community
  • 1
  • 1
user2030677
  • 3,448
  • 4
  • 23
  • 36

5 Answers5

3

If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().

I guess you are hitting the 2nd case. Anyway that pointer just by mistake happens to be in an area where you can write without generating segmentation fault, but you are probably writing in the space of some other variable messing up its value.

LtWorf
  • 7,286
  • 6
  • 31
  • 45
2

For one thing, your compiler may be seeing these two lines back to back and optimizing them:

*ptr = 9;
printf("%i", *ptr);

With such a simplistic program, your compiler may actually be optimizing away the entire memory allocate/free cycle and using a constant instead. A compiler-optimized version of your program could end up looking more like simply:

printf("9");

The only way to tell if this is indeed what is happening is to examine the assembly that your compiler emits. If you're trying to learn how C works, I recommend explicitly disabling all compiler optimizations when you build your code.

Regarding your particular malloc usage, remember that you will get a NULL pointer back if allocation fails. Always check the return value of malloc before you use it for anything. Blindly dereferencing it is a good way to crash your program.

The link that Nick posted gives a good explanation about why malloc(0) may appear to work (note the significant difference between "works" and "appears to work"). To summarize the information there, malloc(0) is allowed to return either NULL or a pointer. If it returns a pointer, you are expressly forbidden from using it for anything other than passing it to free(). If you do try to use such a pointer, you are invoking undefined behavior and there's no way to tell what will happen as a result. It may appear to work for you, but in doing so you may be overwriting memory that belongs to another program and corrupting their memory space. In short: nothing good can happen, so leave that pointer alone and don't waste your time with malloc(0).

bta
  • 43,959
  • 6
  • 69
  • 99
  • The compiler may optimize the `*ptr = 9; printf("%i", *ptr);` to `printf("%i",9);` but it is certainly not going to optimize it to `printf("9");`! Remember that the compiler doesn't "know" what printf() even does; it can't optimize away whole parameters to a function! – phonetagger Jan 31 '13 at 22:32
  • 1
    Many compilers *do know* what `printf` does and verifies that the format string is consistent with the values passed to the function. – Bo Persson Jan 31 '13 at 23:09
  • `printf` has a lot of overhead associated with it, so it's not uncommon for compilers to optimize it as much as possible. If the format string and all arguments are compile-time constants, it's certainly reasonable for the compiler to translate it into a pre-formatted string (I've seen it happen on several occasions). – bta Jan 31 '13 at 23:14
2

A lot of good answers here. But it is definitely undefined behavior. Some people declare that undefined behavior means that purple dragons may fly out of your computer or something like that... there's probably some history behind that outrageous claim that I'm missing, but I promise you that purple dragons won't appear regardless of what the undefined behavior will be.

First of all, let me mention that in the absence of an MMU, on a system without virtual memory, your program would have direct access to all of the memory on the system, regardless of its address. On a system like that, malloc() is merely the guy who helps you carve out pieces of memory in an ordered manner; the system can't actually enforce you to use only the addresses that malloc() gave you. On a system with virtual memory, the situation is slightly different... well, ok, a lot different. But within your program, any code in your program can access any part of the virtual address space that's mapped via the MMU to real physical memory. It doesn't matter whether you got an address from malloc() or whether you called rand() and happened to get an address that falls in a mapped region of your program; if it's mapped and not marked execute-only, you can read it. And if it isn't marked read-only, you can write it as well. Yes. Even if you didn't get it from malloc().

Let's consider the possibilities for the malloc(0) undefined behavior:

  • malloc(0) returns NULL.

OK, this is simple enough. There really is a physical address 0x00000000 in most computers, and even a virtual address 0x00000000 in all processes, but the OS intentionally doesn't map any memory to that address so that it can trap null pointer accesses. There's a whole page (generally 4KB) there that's just never mapped at all, and maybe even much more than 4KB. Therefore if you try to read or write through a null pointer, even with an offset from it, you'll hit these pages of virtual memory that aren't even mapped, and the MMU will throw an exception (a hardware exception, or interrupt) that the OS catches, and it declares a SIGSEGV (on Linux/Unix), or an illegal access (on Windows).

  • malloc(0) returns a valid address to previously unallocated memory of the smallest allocable unit.

With this, you actually get a real piece of memory that you can legally call your own, of some size you don't know. You really shouldn't write anything there (and probably not read either) because you don't know how big it is, and for that matter, you don't know if this is the particular case you're experiencing (see the following cases). If this is the case, the block of memory you were given is almost guaranteed to be at least 4 bytes and probably is 8 bytes or perhaps even larger; it all depends on whatever the size is of your implementation's minimum allocable unit.

  • malloc(0) intentionally returns the address of an unmapped page of memory other than NULL.

This is probably a good option for an implementation, as it would allow you or the system to track & pair together malloc() calls with their corresponding free() calls, but in essence, it's the same as returning NULL. If you try to access (read/write) via this pointer, you'll crash (SEGV or illegal access).

  • malloc(0) returns an address in some other mapped page of memory that may be used by "someone else".

I find it highly unlikely that a commercially-available system would take this route, as it serves to simply hide bugs rather than bring them out as soon as possible. But if it did, malloc() would be returning a pointer to somewhere in memory that you do not own. If this is the case, sure, you can write to it all you want, but you'd be corrupting some other code's memory, though it would be memory in your program's process, so you can be assured that you're at least not going to be stomping on another program's memory. (I hear someone getting ready to say, "But it's UB, so technically it could be stomping on some other program's memory. Yes, in some environments, like an embedded system, that is right. No modern commercial OS would let one process have access to another process's memory as easily as simply calling malloc(0) though; in fact, you simply can't get from one process to another process's memory without going through the OS to do it for you.) Anyway, back to reality... This is the one where "undefined behavior" really kicks in: If you're writing to "someone else's memory" (in your own program's process), you'll be changing the behavior of your program in difficult-to-predict ways. Knowing the structure of your program and where everything is laid out in memory, it's fully predictable. But from one system to another, things would be laid out in memory (appearing a different locations in memory), so the effect on one system would not necessarily be the same as the effect on another system, or on the same system at a different time.

  • And finally.... No, that's it. There really, truly, are only those four possibilities. You could argue for special-case subset points for the last two of the above, but the end result will be the same.
phonetagger
  • 7,701
  • 3
  • 31
  • 55
1

The answer to the malloc(0)/free() calls not crashing you can find here:

zero size malloc

About the *ptr = 9, is just like overflowing a buffer (like malloc'ing 10 bytes and access the 11th), you are writing to memory you don't own, and doing that is looking for trouble. In this particular implementation malloc(0) happens to return a pointer instead of NULL.

Bottom line, it is wrong even if it seems to work on a simple case.

Community
  • 1
  • 1
imreal
  • 10,178
  • 2
  • 32
  • 48
  • What if I had done `malloc(1)` instead of the special case `malloc(0)`? – user2030677 Jan 31 '13 at 21:53
  • It would still be wrong, because `int` is probably 4 bytes long. It would probably seem to work also, cause the system will probably allocate a whole word. – imreal Jan 31 '13 at 21:56
  • What do you mean by "whole word'? – user2030677 Jan 31 '13 at 21:59
  • A word is a group of bytes that is usually written to/read from in a single operation. Its size depends on the platform, most of the times it is 4 or 8 bytes. So allocating less than that doesn't make sense. – imreal Jan 31 '13 at 22:02
  • @user2030677- Regarding `malloc(1)`: Remember that the pointer you're using is either 4 or 8 bytes long. Dynamically allocating less than that is a waste of space because the pointer takes up more space than the data (you could simply store the data in the pointer variable instead). Allocations less than or equal to `sizeof(int*)` bytes are generally pointless. – bta Jan 31 '13 at 22:13
0

Some memory allocators have the notion of "minimum allocatable size". So, even if you pass zero, this will return pointer to the memory of word-size, for example. You need to check up with your system allocator documentation. But if it does return pointer to some memory it'd be wrong to rely on it as the pointer is only supposed to be passed either to be passed realloc() or free().

pmod
  • 10,450
  • 1
  • 37
  • 50