1

According to the Linux man page, the difference between calloc and malloc is that malloc does not initialize the memory, while calloc does initialize the memory.

What that means in practice is that if I create a struct like this:

struct Danger {
    char a;
    char b;
    char c;
    char d;
}

If I create it with malloc:

struct Danger *dang = malloc(sizeof(struct Danger));

It seems that dang->a could really be any value at this point, as the memory isn't initialized. If I use calloc:

struct Danger *dang = calloc(1, sizeof(struct Danger));

I know now that dang->a must equal \0.

The reason malloc presumably exists is in the case that you'll be writing to the entire memory space you've allocated immediately, you don't really care about what's in there at first. This saves you an extra step of zeroing out the data which will be overwritten anyway

It seems though, that from a program security and stability point of view, using malloc should be the exception rather than the rule, as it can easily lead to undefined behavior.

Two questions:

  • Do C programs in the wild deal with unexpected functionality as a result of malloc often? Is this a fairly recurrent problem?
  • How much of a performance penalty do I really get if I just use calloc every time instead of malloc?
Naftuli Kay
  • 87,710
  • 93
  • 269
  • 411
  • 3
    Why is it an exceptional situation to want some memory into which you are storing data? Have you ever, say, opened and read a *file*? Or read from a network socket? Or converted a string encoding? Or encrypted some data? – Kerrek SB Aug 22 '14 at 19:40
  • 3
    To continue the tirade, we can turn this around: When do you ever need something to be initially zero? *That* should be the exception. Zero is only one out of many sensible numbers that could be the initial value of some state inside an algorithm. – Kerrek SB Aug 22 '14 at 19:42
  • 1
    @PascalCuoq: I don't think heartbleed was an example of `malloc` exposing old data. Heartbleed was a matter of reading past the end of a buffer and crossing over into other (possibly-freed, possibly-still-allocated) memory on the heap. – R.. GitHub STOP HELPING ICE Aug 22 '14 at 19:48
  • @R.. Oh, right, I was confused. – Pascal Cuoq Aug 22 '14 at 19:50
  • 1
    In many cases (for large allocations) you get the result of an anonymous `mmap` for your `malloc` anyway, and that tends to be zero initialized on some systems (e.g. Linux). For your question 1: Any good static analyzer, compiler with warnings on or something like valgrind will yell at you if you use uninitialized values. So it is not a big issue if you use such tools and fix the warnings. – schlenk Aug 22 '14 at 19:52
  • @schlenk It is enough to pass the address of an uninitialized array of char to a function for most static analyzers to consider that it was **probably** initialized there. In the most difficult to identify cases of information leakage, the function does write to the buffer, but (in some executions at least) does not write to all of it. Using a static analyzer that finds all uses of uninitialized memory, you do find a few in software that should by principle not have any of this. – Pascal Cuoq Aug 22 '14 at 19:55
  • @schlenk: Anonymous `mmap` is always zero-initialized, not just on "some systems". (Technically, there's no formal standard for anonymous `mmap` right now, but that's how all actual implementations behave, and the work on adding the specification of it to POSIX is underway right now, and will require it to be zero-initialized.) – R.. GitHub STOP HELPING ICE Aug 22 '14 at 19:58
  • Not exactly meant serious: C has the tendency to prefer efficiency over safety in many aspects, like surgeon's knives - very effective and precise, but you might cut yourself if you're not careful. Here you even have the choice: use `malloc()` if you want efficiency, use `calloc()` (or even better: learn to program in ADA), if you don't know exactly what youre doing. – mfro Aug 22 '14 at 20:04
  • @mfro Well, I'd say, whenever you are programming, you *really* should know what you are doing. Independent of language and toolset. – cmaster - reinstate monica Aug 22 '14 at 20:13
  • 1
    @cmaster: certainly. And this is not only true for programming ;) – mfro Aug 22 '14 at 20:17
  • 2
    A contrarian point of view is that `calloc` will make it appear to static analyzers and Valgrind like you intend for the initial value of the allocated block to be zero, and if that value does not make sense and you forgot to really initialize it with sensible values, they won't be able to warn you of the functional bug you have introduced in your program. With `malloc`, static analyzers and Valgrind would have known about your intentions and could have warned you that you forgot to initialize (part of) the block. – Pascal Cuoq Aug 22 '14 at 20:22
  • From a _Security_ standpoint, it makes sense to zero memory just before it is `free()` rather than concern if it is zero'd on allocation. I see no security issue on `malloc()/calloc()`. – chux - Reinstate Monica Aug 22 '14 at 21:55

1 Answers1

2

Do C programs in the wild deal with unexpected functionality as a result of malloc often? Is this a fairly recurrent problem?

They deal with the "problem" often, but usually in the way that they try not to read the junk data. This is usually no problem, because the data needs to be initialized anyway. The real danger lies in unintentially reading junk/zero data, which cannot be ruled out by using calloc().

There are ways to ensure that a program does not read junk data. The easiest is to just execute the test suite with valgrind. It reports any reading of junk data. It will also catch any use-after-free errors, accesses of unallocated memory, lost memory blocks, etc.

How much of a performance penalty do I really get if I just use calloc every time instead of malloc?

Precisely the overhead of writing the writing the data twice. It might remove some valuable data from the cache, so it really depends on how big the memory block is, and the sequence of operations that you use afterwards to fill it with sensible data.

  • Worst case: The zeroing evicts data from the cache that you need to fill the memory block. This data needs to be reloaded from memory which is the main part of the performance impact. For a memory block in the order of some megabytes, this can amount to an impact in the order of some 100 microseconds. Details depend on your machine.

  • Best case: You fill a small memory block right after allocating it without reading any data. In this case the overhead is simply that of writing the data to first level cache. Even though this is a fast operation, you can still expect the zeroing to take the same time as your own initialization - cache is not infinitely fast.


Pesonally, I only use calloc() as a convenient way to get a zero initialized block when I need a zero initialized block. Which is usually not the case. But it happens from time to time.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • 2
    +1 for mentioning valgrind, allowing tools like valgrind to detect uninitialized uses is a huge advantage of using malloc instead of calloc. As calloc writes to the memory it cannot reason anymore that a use of this value without another initialization was intentional or not. It is very hard to debug uninitialized uses where zero is not a good value without valgrind. glibc malloc also gives you MALLOC_PERTURB_ to find these issues easier. – jtaylor Aug 22 '14 at 21:33
  • Calloc is also better and faster than malloc and manual zero the memory for very large memory. – jclin Aug 22 '14 at 22:51
  • @jclin `calloc()` vs. `malloc() & memset()` performance can be deceiving as `calloc()` can simple defer the real zeroing until later. http://stackoverflow.com/questions/2688466/why-mallocmemset-is-slower-than-calloc?rq=1 The net time to zero the memory could thus be larger, it just does not always show up right away. – chux - Reinstate Monica Aug 22 '14 at 23:56
  • @jclin If your memory allocation is backed by new memory requested from the kernel, then you are right: the kernel zeros all new pages requested by applications to prevent side channel attacks. `calloc()` knows this and should skip zeroing in this case. However, this logic does not hold for reused memory. Typical memory allocators never return memory to the kernel. They reuse it themselves. Due to this, the performance gain of `calloc()` is a one-shot. The net gain is limited to the time for zeroing the entire memory footprint of your process once, which is less than a second in most cases. – cmaster - reinstate monica Aug 23 '14 at 11:40