17 Useful C Libraries

1. Overview

List of Common Libraries

Below is a table showcasing some of the commonly used C libraries and their relevance in embedded applications. It also highlights a few commonly used functions and variables from each.

Library Use in Embedded Commonly Used Functions/Variables
stdio.h Used for input-output operations. In embedded systems, functions might be used sparingly due to limited resources. printf(), scanf(), getchar(), putchar()
stdlib.h Provides general utilities that can be useful in embedded contexts for tasks like conversions and memory allocations. malloc(), free(), atoi(), rand(), srand()
string.h Helps in handling strings. In embedded systems, direct memory manipulation using this library can be quite handy. strcpy(), strncpy(), strlen(), strcmp()
math.h Useful for mathematical operations. Its utility in embedded systems depends on the application's computational needs. sin(), cos(), tan(), pow(), sqrt()
avr/io.h Specific to AVR microcontrollers. It's essential for setting up and controlling pins, ports, and peripherals. PORTB, PINB, DDRB (For controlling ports on AVR)
stm32f4xx.h Specific to STM32F4 ARM Cortex-M microcontrollers. Used for controlling various features and peripherals. Functions and variables would be specific to the STM32 family like GPIO_Pin_1

Notes

  • The relevance of a library in embedded systems largely depends on the specific requirements and constraints of a project. For example, using stdio.h for printing debug messages might be acceptable in a resource-rich embedded system but might not be suitable for a very constrained one.

  • Some libraries like avr/io.h and stm32f4xx.h are platform-specific, meaning they are designed for specific microcontrollers or architectures. Always refer to the documentation of the microcontroller you are working with to understand the available libraries and their features.

  • In embedded systems, direct manipulation of memory and registers is more common. Hence, libraries that provide a closer interface to hardware (like avr/io.h) are crucial.


2. stdio.h

Introduction

stdio.h stands for "Standard Input-Output Header" and is one of the most frequently used standard libraries in C programming. While it offers an array of functions for file and stream-based input and output operations, in embedded contexts, it's often used more sparingly due to constraints on resources. Nevertheless, certain functions might still find their place in debugging or in systems with ample resources.

Commonly Used Functions

  • printf(): This function sends formatted output to stdout (typically the screen). In embedded systems, it might be redirected to send data over UART or other communication interfaces for debugging.

  • scanf(): Used for reading formatted input. In embedded contexts, it might be useful to capture user input or commands sent over a communication interface.

  • getchar(): Reads a single character from stdin. This can be useful in embedded systems where you might be waiting for a single character command from a user.

  • putchar(): Writes a single character to stdout. Similar to printf(), it might be redirected in embedded systems for various outputs.

  • fopen() & fclose(): These functions are used to open and close files respectively. Their usage in embedded systems is typically limited to those with file system capabilities, like SD card integrations.

  • fgetc() & fputc(): Retrieve or write a character from/to a file respectively. Their relevance in embedded systems is similar to fopen() and fclose().

Considerations in Embedded Systems

  1. Resource Constraints: Embedded systems often have limited memory and processing power. Functions like printf() can be heavy and might increase the program's footprint substantially.

  2. Redirected IO: In many embedded systems, the standard input and output streams (stdin and stdout) don't map to a screen and keyboard as they do in PCs. Instead, they might be redirected to UART, a serial port, or another communication interface.

  3. Limited File System Access: Many embedded systems don't have a traditional file system. Thus, file operations in stdio.h might not be directly applicable. However, systems equipped with external storage like SD cards might leverage these functions.

Example in Embedded Systems

Let's say you're working with a microcontroller that's reading data from a sensor and you want to output this data over UART for debugging:

#include <stdio.h>

// Assuming UART initialization and sensor read functions are available

int main() {
    UART_Init(); // Initialize UART
    while(1) {
        int sensor_value = Read_Sensor();
        printf("Sensor Reading: %d\n", sensor_value);
        // Wait for a while before the next reading
        delay_ms(1000);
    }
    return 0;
}

In this example, the printf() function outputs the sensor readings over UART, assuming the stdout is redirected to the UART.


3. stdlib.h

Introduction

stdlib.h, the "Standard Library Header," encompasses a variety of functions and macros for performing general operations in C, such as memory allocation, program control, and data conversions. In embedded contexts, some of these functionalities might be critical, while others may be rarely used due to the specific constraints of embedded systems.

Commonly Used Functions

  • malloc() & free(): These functions allow for dynamic memory allocation and deallocation. In embedded systems, dynamic memory allocation is typically used with caution due to limited and non-expandable memory resources.

  • atoi(), atol(), atof(): These functions convert strings to integers (atoi()), long integers (atol()), and floating-point numbers (atof()). They can be useful in embedded systems when parsing input data or commands.

  • rand() & srand(): Used to generate random numbers and set the seed for random number generation respectively. Depending on the application, these might be used in embedded systems, for example, in cryptographic operations or simulations.

  • exit(): This function causes a program to exit and return control to the operating system. In many embedded systems, where there's no OS in a traditional sense, the behavior of this function might differ.

  • abs(): Returns the absolute value of an integer. Useful in various calculations in embedded applications.

  • div(): Returns both the quotient and the remainder of the division of two integers. Handy for mathematical operations without the need for floating-point operations.

Considerations in Embedded Systems

  1. Memory Management: Functions like malloc() and free() are pivotal in systems programming but can introduce challenges in embedded systems, such as memory fragmentation. Some embedded systems might avoid dynamic memory allocation altogether.

  2. Limited Resources: Not all functions in stdlib.h will be appropriate for every embedded context. For instance, generating random numbers might be resource-intensive for some microcontrollers.

  3. Data Conversions: Parsing and converting data is common in embedded systems, especially when dealing with user inputs or communication protocols. Functions like atoi() become quite handy in these scenarios.

Example in Embedded Systems

Imagine a scenario where an embedded device receives commands as string data, and one of the commands is to set a timer delay:

#include <stdlib.h>

// Assuming UART initialization and set_delay function are available

void handle_command(char *cmd) {
    if(strncmp(cmd, "SET_DELAY ", 10) == 0) {
        int delay_time = atoi(cmd + 10); // Convert the part after "SET_DELAY " to an integer
        set_delay(delay_time);
    }
    // Other command handling can be added here
}

int main() {
    UART_Init(); // Initialize UART
    char received_data[50]; 
    while(1) {
        UART_Receive(received_data, 50); // Assume this function fills the received_data buffer
        handle_command(received_data);
    }
    return 0;
}

In this example, the command "SET_DELAY 500" would set a delay of 500 units (e.g., milliseconds). The atoi() function from stdlib.h is used to convert the string representation of the number to an integer.


4. string.h

Introduction

string.h provides a collection of functions to manipulate arrays of characters, or strings. Given the frequent requirement of string operations in various programming scenarios, functions from this header can be indispensable. In embedded systems, they're commonly employed for parsing, data manipulation, and communication tasks.

Commonly Used Functions

  • strcpy() & strncpy(): These functions are used to copy one string into another. strncpy() allows copying up to a specified number of characters, making it safer against buffer overflows.

  • strlen(): Returns the length of a string (excluding the terminating null character).

  • strcmp() & strncmp(): Compare two strings. They return zero if the strings are equal. The strncmp() version compares up to a specified number of characters.

  • strcat() & strncat(): Concatenate (or append) one string to the end of another. strncat() appends up to a specified number of characters.

  • strchr(): Searches for the first occurrence of a character in a string.

  • strstr(): Searches for the first occurrence of a substring within a string.

Considerations in Embedded Systems

  1. Memory Safety: Functions like strcpy(), strcat(), and strcmp() can be sources of vulnerabilities like buffer overflows. When using these functions, one should always ensure that adequate space is allocated and that bounds are checked. Variants like strncpy() and strncat() are safer in this regard, but they still require care.

  2. Efficiency: String manipulation functions can be resource-intensive. It's essential to understand the computational and memory overheads, especially in constrained environments.

  3. Parsing & Commands: Many embedded systems might receive commands or data as strings, especially when interacting with users or other systems. string.h functions are frequently used to parse and process such inputs.

Example in Embedded Systems

Consider an embedded device that accepts commands in the format "COMMAND:PARAMETER". Here's a snippet that parses such commands:

#include <string.h>

void process_command(char *cmd) {
    char *delimiter = ":";
    char *command = strtok(cmd, delimiter);
    char *parameter = strtok(NULL, delimiter);

    if(strcmp(command, "SET_LED") == 0) {
        if(strcmp(parameter, "ON") == 0) {
            // Code to turn the LED on
        } else if(strcmp(parameter, "OFF") == 0) {
            // Code to turn the LED off
        }
    }
    // More command processing can be added here
}

int main() {
    char received_data[50];
    // Assume some mechanism to receive the data into received_data
    process_command(received_data);
    return 0;
}

In this example, the strtok() function is used to tokenize the input string, and strcmp() is employed to compare the parsed command and parameter to known strings.


5. math.h

Introduction

math.h is a header in the C standard library that provides a plethora of mathematical functions. These functions help solve mathematical operations that might be challenging to implement manually. In embedded systems, depending on the application, these functions can be either invaluable or might be avoided due to constraints.

Commonly Used Functions

  • sin(), cos(), tan(): Compute trigonometric sine, cosine, and tangent, respectively.

  • sqrt(): Calculates the square root of a number.

  • pow(): Raises a number to the power of another number.

  • log(), log10(): Computes natural logarithm and base-10 logarithm, respectively.

  • ceil(), floor(): Round numbers up (to the nearest integer) and down, respectively.

  • fabs(): Returns the absolute value of a floating-point number.

Considerations in Embedded Systems

  1. Performance Overhead: Mathematical functions, especially floating-point ones, can be computationally intensive. Many microcontrollers may not even have native floating-point units, leading to significant overhead when these functions are used.

  2. Precision: Depending on the application, the precision provided by these functions might be either overkill or insufficient. Always test to ensure the results meet the application's requirements.

  3. Alternatives: In many embedded applications, lookup tables or approximations might be used instead of functions like sin() or cos() to save computational resources.

  4. Library Size: The inclusion of math.h functions can significantly increase the size of the compiled code. For systems with very limited memory, this can be a concern.

Example in Embedded Systems

Let's say you're working with a sensor that provides data in a curve, and you need to linearize this data using a logarithmic formula:

#include <math.h>

double linearize_sensor_data(double raw_data) {
    return log10(raw_data) * 100; // An example formula
}

int main() {
    double sensor_data = get_sensor_data(); // Assume this function retrieves data
    double linear_data = linearize_sensor_data(sensor_data);
    // Process or send the linearized data
    return 0;
}

In this hypothetical scenario, log10() from math.h is used to help linearize sensor readings.


6. Q&A

1. Question:
What are the primary purposes of the stdio.h library in C?

Answer:
stdio.h stands for standard input-output header. Its primary functions include:

  • Reading from and writing to the console (e.g., printf, scanf).
  • File operations like opening, closing, reading from, and writing to files (e.g., fopen, fclose, fread, fwrite).
  • Error reporting (e.g., perror).

2. Question:
Which function in stdlib.h can be used to dynamically allocate memory in C, and how does it differ from calloc?

Answer:
The malloc function in stdlib.h is used for dynamic memory allocation. The primary difference between malloc and calloc is:

  • malloc allocates uninitialized memory, whereas calloc allocates zero-initialized memory.
  • malloc takes a single argument (total size to be allocated), while calloc takes two arguments (number of blocks and size of each block).

3. Question:
What functions in string.h would you use to concatenate two strings and to find the length of a string?

Answer:
To concatenate two strings, you'd use strcat or strncat. To find the length of a string, you'd use strlen.


4. Question:
How can you use the math.h library to compute the square root of a number and to round a floating-point number to the nearest integer?

Answer:
To compute the square root of a number, you'd use the sqrt function. To round a floating-point number to the nearest integer, you'd use the round function.


5. Question:
In stdlib.h, which function would you use to convert a string representing an integer to an actual integer?

Answer:
You'd use the atoi (ASCII to Integer) function to convert a string to an integer.


6. Question:
Explain the difference between strcpy and strncpy from the string.h library.

Answer:
Both strcpy and strncpy are used to copy strings. The main difference is:

  • strcpy copies the entire source string to the destination without checking the size, which can lead to buffer overflows.
  • strncpy copies up to 'n' characters from the source to the destination, making it safer against buffer overflows when used correctly.

7. Question:
What is the function prototype of printf in stdio.h, and what are format specifiers?

Answer:
Function prototype of printf: int printf(const char *format, ...);. Format specifiers are tokens used within the format string to define how subsequent arguments are displayed. Examples include %d for integers, %f for floating-point numbers, and %s for strings.


8. Question:
From the math.h library, how can you compute the power of a number (e.g., )?

Answer:
You'd use the pow function. For example, pow(x, y) would compute .


9. Question:
What are the differences between fgets and gets from stdio.h?

Answer:
- fgets reads input until a newline or a specified limit of characters. It's considered safer because you can specify the maximum number of characters to be read. - gets reads input until a newline but doesn't allow you to specify a limit, making it susceptible to buffer overflows. Due to its unsafe nature, gets is deprecated in modern C standards.


10. Question:
In stdlib.h, what is the purpose of the qsort function, and what are its key parameters?

Answer:
qsort is a standard C function used for sorting arrays. Key parameters are:

  • A pointer to the array to be sorted.
  • Number of elements in the array.
  • Size of each element.
  • A comparator function pointer that defines the sorting order.

11. Question:
In stdio.h, what's the main difference between the fprintf and printf functions, and when might you use each?

Answer:
printf writes formatted output to the standard output (stdout), whereas fprintf writes to a specified file stream. You'd use printf for console output, but fprintf when you want to write formatted data directly to a file or other streams like stderr.


12. Question:
Why might you prefer strnlen over strlen when working with strings in string.h?

Answer:
strnlen checks up to a maximum number of characters to determine a string's length, ensuring that it doesn't read beyond a buffer's boundaries if the string isn't null-terminated. It's safer in scenarios where you might deal with potentially malformed strings.


13. Question:
What is the difference between fmod and the modulo operator % in math.h, and why might you choose to use one over the other?

Answer:
fmod computes the remainder of dividing two floating-point numbers, whereas % is the modulo operator for integers. You'd use fmod when working with floating-point values and % for integer arithmetic.


14. Question:
In stdlib.h, describe a scenario where using realloc might be beneficial and how it differs from malloc.

Answer:
realloc is used to resize previously allocated memory. It's beneficial when you have a dynamically-sized data structure, like a vector, and need to increase or decrease its memory allocation. While malloc allocates a new block of memory without considering any previous allocations, realloc can either expand or shrink an existing block of memory, potentially moving it if necessary.


15. Question:
From string.h, explain the difference between memcmp and strcmp.

Answer:
Both functions compare two strings, but:

  • strcmp compares two null-terminated strings and stops the comparison when a null character is found.
  • memcmp compares a specified number of bytes (characters) and doesn't treat the null character specially.

16. Question:
In the context of stdio.h, how would you use fseek and ftell, and why are they useful?

Answer:
fseek is used to move the file pointer to a specified location within a file, and ftell returns the current position of the file pointer. They're useful for random access file operations, such as when you need to jump to a specific part of a file to read or write data.


17. Question:
How does the atof function in stdlib.h handle invalid input, and what are its limitations?

Answer:
atof converts a string to a double. If the string can't be converted, it returns 0.0. A limitation is that you can't differentiate between a valid "0.0" input and an invalid string using atof alone.


18. Question:
Discuss the purpose and usage of strpbrk from string.h.

Answer:
strpbrk searches a string for the first occurrence of any character from a set of specified characters. It returns a pointer to the character in the string that matches one from the set, or NULL if no character is found. It's useful for parsing strings based on multiple possible delimiters.


19. Question:
In math.h, what's the difference between ceil and floor?

Answer:
Both functions operate on floating-point numbers:

  • ceil returns the smallest integer that's greater than or equal to the given value.
  • floor returns the largest integer that's less than or equal to the given value.

20. Question:
Discuss the implications of using rand from stdlib.h for cryptographic purposes.

Answer:
rand is not suitable for cryptographic purposes. It's a pseudorandom number generator that, given the same seed, will produce the same sequence of numbers. Cryptographic applications require true randomness and unpredictability, which rand doesn't provide.


21. Question:
In stdio.h, there's a function called setvbuf. What does it do and when might you use it?

Answer:
setvbuf is used to set the buffering mode for an open file stream. Buffering can affect performance, especially in I/O operations. Depending on the needs, you might set a file stream to be unbuffered, line buffered, or fully buffered. You might use it when you want more control over the buffering behavior, like when dealing with interactive I/O or when optimizing performance.


22. Question:
Describe a scenario in which the strtok function from string.h might be problematic and how you'd work around it.

Answer:
strtok modifies the string it tokenizes and uses a static buffer while tokenizing, which means it's not thread-safe. If you're dealing with multithreaded applications or need the original string intact, you'd run into issues. A workaround might be to use strtok_r, which is reentrant, or to make a copy of the string before tokenizing.


23. Question:
Why would you use calloc instead of malloc from stdlib.h?

Answer:
calloc allocates memory and initializes it to zero, while malloc only allocates memory without initialization. You might use calloc when you want the allocated memory to be set to zero, ensuring predictable behavior.


24. Question:
What is the ferror function in stdio.h, and why is it useful?

Answer:
ferror checks if there's an error flag set for a given file stream. It's useful to determine if an error occurred during a file operation, such as reading or writing, without relying on the return values of those functions alone.


25. Question:
In the context of string.h, explain the difference between strncpy and strncat.

Answer:
Both functions work with strings, but they serve different purposes: - strncpy copies up to 'n' characters from one string to another. - strncat appends up to 'n' characters from one string to the end of another.


26. Question:
math.h includes a function called ldexp. What does it do, and how could it be beneficial?

Answer:
ldexp computes the value of x * (2^exp) for given x and exp. It's beneficial for quickly scaling numbers by powers of two, especially in applications where bitwise operations are important, or when working with floating-point number representations.


27. Question:
Explain the potential pitfalls of using atoi from stdlib.h.

Answer:
atoi converts a string to an integer. However, it has no error handling: If the string isn't a valid integer, or if there's an overflow, the behavior is undefined. For more robust error handling, one might consider using strtol.


28. Question:
string.h provides strcoll. How does it differ from strcmp?

Answer:
While both strcoll and strcmp compare strings, strcoll compares based on the current locale's collation rules. This is important when comparing strings in languages that have different collation orders than the C locale. strcmp does a byte-by-byte comparison, whereas strcoll might give different results for the same strings in different locales.


29. Question:
In math.h, there's a function called isinf. What is its purpose?

Answer:
isinf checks if a floating-point number is positive or negative infinity. It's useful for determining if operations resulted in overflow to infinity, especially in applications that require strict numerical accuracy.


30. Question:
When working with file I/O from stdio.h, what's the difference between feof and ferror?

Answer:
Both functions check the status of a file stream: - feof checks if the end-of-file (EOF) indicator is set. - ferror checks if the error indicator is set.

You'd use feof to determine if you've reached the end of a file during reading, and ferror to check if any errors occurred during file operations.