Reentrant code means the ability for a piece of code to be called simultaneously from two or more threads. Attention to re-enterability is needed when using a multi-tasking operating system, or when using interrupts since an interrupt is really a temporary thread.

The code generated natively by gcc is reentrant. But, only some of the libraries in avr-libc are explicitly reentrant, and some are known not to be reentrant. In general, any library call that reads and writes global variables (including I/O registers) is not reentrant. This is because more than one thread could read or write the same storage at the same time, unaware that other threads are doing the same, and create inconsistent and/or erroneous results.

A library call that is known not to be reentrant will work if it is used only within one thread and no other thread makes use of a library call that shares common storage with it.

Below is a table of library calls with known issues.

Library call

Reentrant Issue

Workaround/Alternative

rand(), random()

Uses global variables to keep state information.

Use special reentrant versions: rand_r(), random_r().

strtod(), strtol(), strtoul()

Uses the global variable errno to return success/failure.

Ignore errno, or protect calls with cli()/sei() or ATOMIC_BLOCK() if the application can tolerate it. Or use sccanf() or sccanf_P() if possible.

malloc(), realloc(), calloc(), free()

Uses the stack pointer and global variables to allocate and free memory.

Protect calls with cli()/sei() or ATOMIC_BLOCK() if the application can tolerate it. If using an OS, use the OS provided memory allocator since the OS is likely modifying the stack pointer anyway.

fdevopen(), fclose()

Uses calloc() and free().

Protect calls with cli()/sei() or ATOMIC_BLOCK() if the application can tolerate it. Or use fdev_setup_stream() or FDEV_SETUP_STREAM(). Note: fclose() will only call free() if the stream has been opened with fdevopen().

eeprom_*(), boot_*()

Accesses I/O registers.

Protect calls with cli()/sei(), ATOMIC_BLOCK(), or use OS locking.

pgm_*_far()

Accesses I/O register RAMPZ.

Starting with GCC 4.3, RAMPZ is automatically saved for ISRs, so nothing further is needed if only using interrupts. Some OSes may automatically preserve RAMPZ during context switching. Check the OS documentation before assuming it does. Otherwise, protect calls with cli()/sei(), ATOMIC_BLOCK(), or use explicit OS locking.

printf(), printf_P(), vprintf(), vprintf_P(), puts(), puts_P()

Alters flags and character count in global FILE stdout.

Use only in one thread. Or if returned character count is unimportant, do not use the *_P versions. Note: Formatting to a string output, e.g. sprintf(), sprintf_P(), snprintf(), snprintf_P(), vsprintf(), vsprintf_P(), vsnprintf(), vsnprintf_P(), is thread safe. The formatted string could then be followed by an fwrite() which simply calls the lower layer to send the string.

fprintf(), fprintf_P(), vfprintf(), vfprintf_P(), fputs(), fputs_P()

Alters flags and character count in the FILE argument. Problems can occur if a global FILE is used from multiple threads.

Assign each thread its own FILE for output. Or if returned character count is unimportant, do not use the *_P versions.

assert()

Contains an embedded fprintf(). See above for fprintf().

See above for fprintf().

clearerr()

Alters flags in the FILE argument.

Assign each thread its own FILE for output.

getchar(), gets()

Alters flags, character count, and unget buffer in global FILE stdin.

Use only in one thread. ***

fgetc(), ungetc(), fgets(), scanf(), scanf_P(), fscanf(), fscanf_P(), vscanf(), vfscanf(), vfscanf_P(), fread()

Alters flags, character count, and unget buffer in the FILE argument.

Assign each thread its own FILE for input. *** Note: Scanning from a string, e.g. sscanf() and sscanf_P(), are thread safe.

An effort will be made to keep this table up to date if any new issues are discovered or introduced.

Back to FAQ Index.