Here is a safe_printf() function which never calls malloc()!
I ended up writing a safe_printf() function which never calls malloc(), in case anyone ever needs it. I've tested it, and it works fine inside malloc(). It uses the write() system call (not std C) to write the chars to stdout.
Here it is:
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#ifdef DEBUG
/// Debug printf function.
/// See: https://stackoverflow.com/a/1941336/4561887
#define DEBUG_PRINTF(...) safe_printf("DEBUG: "__VA_ARGS__)
#define DEBUG_ASSERT(test_condition) assert(test_condition)
#else
#define DEBUG_PRINTF(...) \
do \
{ \
} while (0)
#define DEBUG_ASSERT(test_condition) \
do \
{ \
} while (0)
#endif
/// \brief A **safe** version of `printf()`, meaning it NEVER calls `malloc()`, unlike
/// `printf()`, which may.
/// \details This is achieved by using a fixed-size buffer to format strings into. You may see
/// a much simpler implementation from the Hacettepe University in Turkey, here, as a
/// basic example:
/// https://web.cs.hacettepe.edu.tr/~bbm341/codes/ecf/signals/safe_printf.c
#define SAFE_PRINTF_BUF_SIZE 2048
__attribute__((__format__(printf, 1, 2)))
int safe_printf(const char *format, ...)
{
static char buf[SAFE_PRINTF_BUF_SIZE];
// number of chars successfully written to `buf` and which we need to print
int chars_to_print = 0;
va_list args;
va_start(args, format);
int char_count_or_error = vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
DEBUG_ASSERT(char_count_or_error >= 0); // if fails: ENCODING ERROR
DEBUG_ASSERT(char_count_or_error < (int)sizeof(buf)); // if fails: BUFFER IS TOO SMALL
if (char_count_or_error < 0)
{
// ERROR: ENCODING ERROR
return char_count_or_error;
}
else if (char_count_or_error >= (int)sizeof(buf))
{
// ERROR: BUFFER IS TOO SMALL to write the full character sequence!
chars_to_print = sizeof(buf) - 1;
char_count_or_error = -char_count_or_error; // make negative to show it was an error
}
else // char_count_or_error >= 0 && char_count_or_error < sizeof(buf)
{
// No error
chars_to_print = char_count_or_error;
}
ssize_t num_bytes_written = write(STDOUT_FILENO, buf, chars_to_print);
DEBUG_ASSERT(num_bytes_written >= 0); // If fails: failure to write
// Return BUFFER IS TOO SMALL error
if (char_count_or_error < 0)
{
return char_count_or_error;
}
return num_bytes_written;
}
References:
- The comment by @kaylum:
write(STDOUT_FILENO, str, strlen(str))
- The answer by @Employed Russian
- A much simpler implementation from the Hacettepe University in Turkey, here, as a basic example: https://web.cs.hacettepe.edu.tr/~bbm341/codes/ecf/signals/safe_printf.c
- Linux
write(2) reference page: https://man7.org/linux/man-pages/man2/write.2.html
- The
vsnprintf() usage example here: http://www.cplusplus.com/reference/cstdio/vsnprintf/
stdarg.h reference: https://www.cplusplus.com/reference/cstdarg/
See also:
- [my answer: this is where I identified the infinite recursion problem where
printf() calls malloc(), which calls printf(), repeatedly; that is what motivated me to write the safe_printf() implementation above!] "Segmentation fault (core dumped)" for: "No such file or directory" for libioP.h, printf-parse.h, vfprintf-internal.c, etc