How to include snprintf without syscalls needed for printf?

On an embedded platform, the syscalls needed for printf aren't usually available. The standard solution is to write simple stubs e.g. for _write.

I don't need or want printf. I need snprintf for internal string formatting. Is there any way to get that without having to stub out the syscalls, which will never be called anyway?

It seems backwards to have to write stubs for syscalls just to get internal string formatting routines.


If I snprintf a constant char buf, it links fine. This is presumably because the compiler takes out snprintf and just uses puts.

But if I have a "%d" format string, I get linker errors.

Compile command (word wrapped for this post):

$ make flash -B
arm-none-eabi-gcc src/main.c -W -Wall -Wextra -Werror 
-Wundef -Wshadow -Wdouble-promotion -Wformat-truncation 
-Wconversion -Wno-attributes -DSTM32F446RE -DSTM32F446XX 
-DSTM32F4 -g3 -Os -fno-common -ffunction-sections 
-fdata-sections -fanalyzer -I. -I./libopencm3/include 
-MMD -mcpu=cortex-m4 -mthumb -mfloat-abi=hard 
-mfpu=fpv4-sp-d16  -Tconfig/link.ld 
--static -nostartfiles -L./libopencm3/lib -lopencm3_stm32f4 
-Wl,--fatal-warnings -Wl,--gc-sections -Wl,--cref 
-o firmware.elf


/usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libg.a(lib_a-sbrkr.o): in function `_sbrk_r':
/build/newlib-pB30de/newlib-3.3.0/build/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/sbrkr.c:51: undefined reference to `_sbrk'
/usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libg.a(lib_a-abort.o): in function `abort':
/build/newlib-pB30de/newlib-3.3.0/build/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/stdlib/../../../../../../../../newlib/libc/stdlib/abort.c:59: undefined reference to `_exit'
/usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/10.3.1/../../../
/usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libg.a(lib_a-readr.o): in function `_read_r':
/build/newlib-pB30de/newlib-3.3.0/build/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/readr.c:49: undefined reference to `_read'
collect2: error: ld returned 1 exit status


  • I would use a stand-alone version, that makes minimal or zero sys calls, and uses the stack so you don't have to worry about concurrency.

    I've seen a sprintf & friends implementation in the past that doesn't call into the file system at all.

    I'm using printf from the Arm mbed project. sprintf doesn't require IO per-se, but because printf is included you just have to provide a small stub: for fpuctc, FILE and stdout

    My stubs are:

    struct io_stub_file
        int chanId;
        int (*putc_func)(int chan, char c);
        uint32_t flags; // you might not need this
        void (* pre_put_hook)(void); // you might not need this
    typedef struct io_stub_file FILE;
    extern struct io_stub_file * io_stub_ftab[];
    #define stdout io_stub_ftab[1]

    One problem with this, it will clash with <stdio.h> but you probably want to be avoiding that header anyway. It will inevitably drag in things from reent.h and such like, that it seems you build does not support. Initialise the stdout entry so its putc_func drives your uart, or whatever you prefer to consider as stdout.

    I would recommend against stubbing out things like write and sbrk because sooner or later, you will accidentally call on something in newlib that requires these things to be functional, then you'll be wondering why your standard library isn't working properly. If these things aren't functional then stay away from the parts of newlib that require them - if you accidentally call on them the linker errors will soon tell you, rather than it failing haphazardly.