C runtime library
The C runtime library is a collection of subroutines that are needed to build a program in the C programming language. The subroutines can be put into 2 categories: the C standard library and compiler-specific auxiliary functions. These go hand-in-hand with the startup code, which contains the first instructions to be carried out when program execution starts.
A C runtime library is also required for embedded systems, where the entire program is typically monolithic (a single executable), as well as for larger systems such as Windows, Linux, and MacOS, where the application runs on top of an operating system and, typically, in the virtual memory space as well.
C runtime-library content
C standard library
The functions available in the C standard library are defined in the C standard. Examples of functions are
memory and string functions, e.g. strcpy
, memcpy
, memset
, strcmp
; formatted I/O functions, e.g. printf
, snprintf
, scanf
, sscanf
; and heap functions, e.g. alloc
, free
, realloc
.
Compiler-specific auxiliary functions
Compiler-specific auxiliary functions are highly dependent on the compiler and architecture in question. They usually supply functionality required by the C standard, which cannot be easily implemented using single instructions on the target CPU.
Typical here are arithmetic routines. On a CPU that does not offer a divide instruction, a divide routine must be provided in the library, so that the code required to perform this division does not need to be inserted in line where division is required, as the division code might be quite large.
Even where a processor has built-in multiplication and division instructions, they are often not powerful enough. Modern compilers implement 64-bit variables, and the 64-bit arithmetic cannot be easily performed by the processor in a single instruction.
Auxiliary functions for 64-bit arithmetic (particularly multiplication and division) are required. These functions are actually used by cryptography algorithms, and their performance is important to the system performance of these algorithms.
Implementation
A C runtime library can be implemented entirely in C (as a high-level language), but, for performance reasons, good implementations usually have various routines implemented in assembly language (ASM). Technically, other languages can be used (such as C++), but they typically are not.
Performance
The performance of an application program depends largely on the C runtime library. Functions such as memcpy are often used by application programs, either directly (visible to the programmers, and where the application program directly calls memcpy) or even indirectly (through code generated by the compiler to, for example, copy a structure).
Other functions, such as arithmetic functions, may also have a significant effect on performance. Different implementations can have vastly different performance, with the difference being a factor of 10 or more. A very simple memcpy implementation would copy bytes in a loop, one byte at a time, and be written in C, as below:
void *memcpy( void *dest, const void *src, size_t NumBytes) {
char *pDest;
char *pSrc;
pDest = (char*)dest; // Copy to byte size pointers to make the loop look prettier. Note that most compilers optimize things away anyhow
pSrc = (char*)src;
while (NumBytes > 0) {
*pDest++ = *pSrc++;
--NumBytes;
}
return dest; // Actually a waste of time to return the original pointer but it seems that is what the C-standard demands
}
A good implementation is carried out in assembly language; will load and store as many words as possible (so typically 32 bits on a 32-bit processor); and use special instructions where possible and available, such as multi-word read and write. The resulting difference in performance can be very large, and since a lot of applications make heavy use of memcpy, the difference in performance can be equally large.
Size of the C runtime library
The size of the C runtime library is largely irrelevant. What is important is how much of it gets linked to the application. This is where good and not so good implementations vary. A good implementation will add minimal amounts of space (for small applications, this can and should be no more than a few hundred bytes), where other implementations will not allow the creation of applications smaller than 20 KB.
Granularity
Ideally, the C runtime library has very fine granularity. This means that only the functions actually referenced by the application are linked to the application.
Heap use
Good runtime libraries do not require a heap. This is especially important in embedded systems, where the application program often does not use the heap. If a runtime library needs a heap, then the heap is required just for the runtime library. Important here is linking within the additional code for the runtime library's heap management, as well as setting aside some RAM to be used as a heap.
Implementations of the C runtime library
There are lots of C runtime-library implementations available. Every compiler comes with its own implementation, as it is not really fully usable without it. For GCC, various free-of-charge options are available, such as glibc, newlib, and newlib-nano. There is also a commercial version available, with much better performance and a smaller memory footprint: SEGGER RunTime Library
For more-detailed information, visit the SEGGER Runtime Library product page on segger.com.
An example standardized library
Arm have documented the APIs required [1] for a C-standard-conforming compiler to interoperate with other EABI-compliant compilers. This enables linking of libraries that are compiled with compiler A to be used by compiler and linker B if both A and B conform to the same EABI.
There are many functions in the ABI, such as floating-point emulation for processors that lack a floating-point unit, or 64-bit multiplication and division for compilers that do not expand these operations inline.
A language processor's runtime system must provide implementations of these functions even if the compiler never calls them. This enables, for instance, a library written for a Cortex-M3 device (that uses calls to the floating-point helpers) to be linked with an application written for a Cortex-M4 device (with a floating-point unit and no calls to helpers). There is a small penalty for using floating-point helpers in the Cortex-M3 code rather than floating-point instructions, but the code continues to work.