views:

208

answers:

5

In UNIX systems we know malloc() is a non-reentrant function (system call). Why is that?

Similarly, printf() also is said to be non-reentrant; why?

I know the definition of re-entrancy, but I wanted to know why it applies to these functions. What prevents them being guaranteed reentrant?

A: 

Most likely because you can't start writing output while another call to printf is still printing it's self. The same goes for memory allocation and deallocation.

danpker
This explains what "re-entrant" is, but not why these functions are non re-entrant.
ChrisF
Oh great so. I was foolish for asking this about printf. Thanks. But can't we call malloc() from two different threads simultaneously ?
RIPUNJAY TRIPATHI
*"you can't start writing output while another call to printf is still printing it's self."* Why? What bit of `printf` causes that? It's not obvious. Maybe the result would be `Hello, woSOMETHINGELSErld!`, but all you asked would be nevertheless printed?
Pavel Shved
@RIPUNJAY TRIPATHI: It depends on the implementation, but usually malloc is thread safe if you have threading support switched on in your compilation.
JeremyP
@Jeremy: Are thread-safety and reenterancy one and same? I thought they are two different things.
RIPUNJAY TRIPATHI
@RIPUNJAY: A reentrant function is also thread-safe, but a thread-safe function is not necessarily reentrant. And yes, they are two different things. For instance, on systems which support threads malloc() is typically thread-safe, but not reentrant.
janneb
@janneb: Thanks :) I knew that.
RIPUNJAY TRIPATHI
A: 

It's because both works with global resources: heap memory structures and console.

EDIT: the heap is nothing else than a kind linked list structure. Each malloc or free modifies it, so having several threads in the same time with writing access to it will damage its consistency.

EDIT2: another detail: they could be made reentrant by default by using mutexes. But this approach is costly, and there is no garanty that they will be always used in MT environment.

So there are two solutions: to make 2 library functions, one reentrant and one not or leave the mutex part to the user. They've choosed the second.

Also, it can be because the original versions of these functions were non-reentrant, so the've been declared so for compatibility.

ruslik
Not a good answer, thats obvious.
RIPUNJAY TRIPATHI
So you claim that `malloc` is not thread-safe? Interesting... (-1) And you also claim that including mutexes makes the function reentrant... Even more interesting! (-2)
Pavel Shved
A: 

If you try calling malloc from two separate threads (unless you have a thread-safe version, not guaranteed by C standard), bad things happen, because there's only one heap for two threads. Same for printf- the behaviour is undefined. That's what makes them in reality non-reentrant.

DeadMG
OK, but where can it fail ? Will be good if I get some examples.
RIPUNJAY TRIPATHI
It is undefined behavior in the C specification, there is not example of where it may fail, cause at the time when writing the C specification this was a non issue (no threads at all). It might work find on some implementations and not at all on others while both implementations may follow the specification.
UnixShadow
@RIPUNJAY: It could happen that both calls to malloc return the same pointer, because because both malloc invocations determined that block to be available (the first invocation would be interrupted between determining the block as free and marking it as allocated).
Bart van Ingen Schenau
@UnixShadow:Sorry there is some problem. If there was NO threads concept at all, why RE-ENTERANCY is linked to that ?
RIPUNJAY TRIPATHI
@deadmg: thread safety and RE-ENTERANCY are different though they are related.
yadab
Thread-safety is only one of the cases for reentrancy. Another is, as mentioned, signal handlers (and other interrupt contexts).
Michael Foukarakis
+1  A: 

Let's understand what we mean by re-entrant. A re-entrant function can be invoked before a previous invocation has finished. This might happen if

  • a function is called in a signal handler (or more generally than Unix some interrupt handler) for a signal that was raised during execution of the function
  • a function is called recursively

malloc isn't re-entrant because it is managing several global data structures that track free memory blocks.

printf isn't re-entrant because it modifies a global variable i.e. the content of the FILE* stout.

JeremyP
+8  A: 

malloc and printf usually use global structures, and employ lock-based synchronization internally. That's why they're not reentrant.

The malloc function could either be thread-safe or thread-unsafe. Both are not reentrant:

  1. Any thread-unsafe function is not reentrant (reentrant functions are thread-safe by definition). Malloc operates on a global heap, and it's possible that two different invocations of malloc that happen at the same time, return the same memory block. (The 2nd malloc call should happen before an address of the chunk is fetched, but the chunk is not marked as unavailable). This violates the postcondition of malloc, so this implementation would not be re-entrant.

  2. To prevent this effect, a thread-safe implementation of malloc would use lock-based synchronization. However, if malloc is called from signal handler, the following situation may happen:

    malloc();            //initial call
      lock(memory_lock); //acquire lock inside malloc implementation
    signal_handler();    //interrupt and process signal
    malloc();            //call malloc() inside signal handler
      lock(memory_lock); //try to acquire lock in malloc implementation
      // DEADLOCK!  We wait for release of memory_lock, but 
      // it won't be released because the original malloc call is interrupted
    

    This situation won't happen when malloc is simply called from different threads. Indeed, the reentrancy concept goes beyond thread-safety and also requires functions to work properly even if one of its invocation never terminates. That's basically the reasoning why any function with locks would be not re-entrant.

The printf function also operated on global data. Any output stream usually employs a global buffer attached to the resource data are sent to (a buffer for terminal, or for a file). The print process is usually a sequence of copying data to buffer and flushing the buffer afterwards. This buffer should be protected by locks in the same way malloc does. Therefore, printf is also non-reentrant.

Pavel Shved
Finally, thank you. I was about to write more or less the same, but your answer appeared just as I was starting to type mine. +1.
janneb
"any function with locks would be not re-entrant". I worked on a system where you could flag mutexes to disable signals, either just while waiting on the mutex, or throughout the time the mutex is held. Obviously it was easy to mis-use, and you have to guarantee the function would return, but it was used to access globals from reentrant functions (generally in the kernel). I assume without proof that other kernels have equivalent mechanisms, but also that the standard doesn't want to require such shenanigans where avoidable.
Steve Jessop
@Steve: For instance, in the Linux kernel spinlocks typically disable interrupts when the lock is taken. "Normal" sleeping mutexes OTOH run with interrupts enabled.
janneb