2

I'm implementing my own fast_malloc() to replace malloc(). I need debugging prints inside it. Are there any print calls which are guaranteed to NOT ever call malloc(), or do I need to create my own, safe versions?

Previously, I had accidentally caused infinite recursion by having my malloc() call printf(), which then calls malloc(), which then calls printf()...forever.

If I need to create my own safe versions which use a fixed-size static array under the hood as the buffer to format into, that's all I need to know. I can do that.

How about puts() or putc()? They should be safe, no?

I'm on Linux Ubuntu 20.04. Ideally, whatever I do will be cross-platform-compliant, but I suppose I can customize if I need to for low-level system calls.


Related:

  1. Related, but not a duplicate since it is specific to snprintf(): snprintf that calls malloc, or snprintf that does not call malloc
  2. Does fprintf use malloc() under the hood?
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • 4
    `write(STDOUT_FILENO, str, strlen(str))` – kaylum Jun 30 '21 at 06:43
  • @kaylum, looks like that is this Linux system call, no? https://man7.org/linux/man-pages/man2/write.2.html. Probably not cross-platform-compliant? – Gabriel Staples Jun 30 '21 at 06:49
  • 1
    I doubt that `puts` and `putc` call `malloc`. But anyway using `write` to stdout for sure doesn't and it's pretty platform independant. – Jabberwocky Jun 30 '21 at 06:50
  • 1
    You probably have to use only system call. Under Windows, it is [WriteFile](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile). – fpiette Jun 30 '21 at 06:52
  • 1
    If you can add code to your project: [No-malloc one-file printf() with optional floating point support](https://www.reddit.com/r/embedded/comments/3mbn47/nomalloc_onefile_printf_with_optional_floating/) – Mark Plotnick Jun 30 '21 at 15:35
  • @kaylum, side note: consider making your comments answers next time. Your comment above [as well as here](https://stackoverflow.com/questions/68171246/in-gcc-is-there-any-way-to-dynamically-add-a-function-call-to-the-start-of-main#comment120485051_68171246) were both the answers it looks like. – Gabriel Staples Jun 30 '21 at 18:02

2 Answers2

6

Are there any print calls which are guaranteed to NOT ever call malloc()

Usually you can call fprintf(stderr, ...). This is because stderr is unbuffered by default.

However, this may not work from within malloc early in the process lifetime, before the rest of libc has initialized itself.

Your best bet is to use write(2).

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
1

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:

  1. The comment by @kaylum:
    write(STDOUT_FILENO, str, strlen(str))
    
  2. The answer by @Employed Russian
  3. 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
  4. Linux write(2) reference page: https://man7.org/linux/man-pages/man2/write.2.html
  5. The vsnprintf() usage example here: http://www.cplusplus.com/reference/cstdio/vsnprintf/
  6. stdarg.h reference: https://www.cplusplus.com/reference/cstdarg/

See also:

  1. [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
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • This is valid only if `vsnprintf` doesn't call `malloc` either. Which as far as I know is implementation dependent. – fpiette Jul 03 '21 at 06:12