Home     Blog    News    Twitter    Google+    Contact

Reentrant/Non-reentrant Functions

The compiler will produce both reentrant and non-reentrant functions. Reentrant functions are fully compatible with ANSI C and allow recursive calls within and between functions. Also a reentrant function can be called during an interrupt even if it is being invoked during the non-interrupt part of the process or by another lower priority interrupt. Space for parameters and local variables is allocated dynamically on a stack located in either internal indirectly addressable data memory or external data memory depending on the memory model. Since this stack space is released as soon as the function exits, reentrant code is data memory efficient. However, the 8051 instruction set is not particularly well suited to conventional stack operations (it has no indexed addressing modes for accessing data memory) and a dramatic improvement in program size and speed can be achieved by implementing a statically allocated stack system. With this approach, each function has it's own fixed data memory for storage of its parameters and local variables. Because this memory is in a fixed location, if the function is called again while it is already running, the parameters and local variables of the second call will overwrite those of the first call. The function cannot be reentered and it is therefore a non-reentrant function.

Although a statically allocated stack system is not as data memory efficient as a dynamic, reentrant stack, the amount of statically allocated stack is not the sum total of the stack space required by each function present in the program. It can be considerably less. This is because the linker carries out an analysis of the complete program and constructs a function call tree. It uses this call tree to determine which functions can safely share stack space. The stack space for functions that cannot possibly invoke each other are then overlaid. The linker is also able to exactly determine the total stack space required by functions that have a variable number of arguments (such as printf() and scanf()) and so no space is wasted allowing for additional arguments that are never present.

Another advantage of non-reentrant functions is that it is possible to support true _bit types as parameters and local variables. If you use a _bit type as a parameter or local variable in a reentrant function, it will be quietly converted to unsigned char. However, with a non-reentrant function it will remain as a _bit type located in bit addressable data memory.

The compiler can be set using a command-line option (or dialogue box option in the Embedded Development Studio) to default to either reentrant or non-reentrant code. This will determine whether the reentrant or non-reentrant libraries are included and all functions will be compiled as the selected default type unless they are specifically designated as either reentrant or non- reentrant. The _reentrant and _nonreentrant keywords can be placed to the left of the function name to force it to be one type or the other regardless of the default type selected. These keywords should also be used with function prototypes to ensure the function is called correctly from other modules. Nevertheless, the linker will check function calls against function type across the complete program and warn of a reentrant/non-reentrant mismatch. (Note that the compiler does not need to use function name colouring to perform this type checking. No additional characters are appended to function names other than the normal underscore.)

The linker will also warn if a non-reentrant function is potentially recursive so that you can take steps to declare this particular function as reentrant. This warning does not mean that the function is bound to be recursive, only that the linker has found the function itself on its own call tree. Whether or not it is actually recursive depends upon the execution path of the program.

Even though the linker can warn you if a function is potentially recursive, it cannot warn you if a function is potentially reentrant due to an interrupt event. Consequently, care should be taken when including calls to non-reentrant functions (including library functions) from within an interrupt function.

If you want a particular library function to be reentrant (so that you can use it for instance in both an interrupt function and your normal code without fear of conflict) and the default type is non- reentrant then you will need to take special steps to integrate this reentrant library function into your code. Let's say you want to use the reentrant version of strcpy() in an otherwise non- reentrant program. You need to do the following:

Modify the function prototype in library header file string.h changing:

char * strcpy (char * string1, const char * string2);

to:

char * _reentrant strcpy (char * string1, const char *string2);

Extract the reentrant version of strcpy() from the appropriate memory model. The non-reentrant libraries have an 'n' as the last character and so the small memory model non-reentrant library is slib51n.lib and the small memory model reentrant library is slib51.lib. To extract strcpy() from slib.51.lib use the command line:

lib *strcpy slib51.lib;

(slib51.lib must be in the current directory and PATH must point to lib.exe)

This will create object file strcpy.obj leaving the original library unchanged.

Now you can either include this extracted object file in your build, replace the version in the non-reentrant library with this reentrant version, or build a new library with this object file, in it and include this new library in your build.