Skip to content
Snippets Groups Projects
This is the "poor programmer's" printf implementation. It is meant to
be used in small environments, like microcontrollers or soft
processors.  Actually, that's where I needed it years ago and where
I'm still using it.

It is a complete printf (it only misses the %[charset] feature, and
obviously floating point support). It relies on an external "puts"
function for the actual output; the full version also needs strnlen.
Unfortunately, the stdio puts adds a trailing newline (while most
embedded implementations do not). The sprintf function is included
as pp_sprintf.

The printf engine, vsprintf(), comes in four flavours:

In summary:

    - the "full" version is a normal printf (GPL2, from older Linux kernel)
      (as a build-time option, it supports int64_t items, as "ll" or "L")

    - the "xint" version accepts all formats and prints hex and int only

    - the "mini" version accepts all formats but only prints hex (GPL2)

    - the "none" version accepts all formats and prints nothing (PD)

The version you use can be selected at compile time, so you can
develop with a full-featured printf and install the minimal one in
production, saving a few kilobytes and still not loosing information
in messages.  At the end I list compiled sizes for a few use cases.

While I use this very code in several projects, the only example here
is a stupid main that prints something.  You are expected to pick these
files and copy them to your projects, rather than use this "package"
as a system library.


	The full implementation in detail
	=================================

This comes from u-boot, which means that it is an earlier printf as
used in the Linux kernel. It is licensed according to the GNU GPL
version 2.  It includes all formats and prefixes and so on. It is
clearly bugless because everybody is using it.

In July 2014 I made some changes, which includes adding 64-bit items,
but the feature is disabled by default. There is a whole section later
in this file about this.

It is selected at compile time by setting the make variable
"CONFIG_PRINTF_FULL" to "y". You can do that in the environment,
or use Kconfig in your application.

(The Makefile selects this by default if you set nothing in the
environment or make variables)

Example calls (example-printf.c):

    pp_printf("integer %5i %5i %05i\n", 1024, 666, 53);
    pp_printf("octal   %5o %5o %05o\n", 1024, 666, 53);
    pp_printf("hex     %5x %5x %05x\n", 1024, 666, 53);
    pp_printf("HEX etc %5X %+5d %-5i\n", 1024, 666, 53);
    pp_printf("neg     %5i %05i %05x\n", -5, -10, -15);
    pp_printf("char: %c  string %s %5s %.5s\n", 65, "foo", "foo",
              "verylongstring");
    pp_printf("hour    %02d:%02d:%02d\n", 12, 9, 0);

Result (as you see, format modifiers are respected):

    integer  1024   666 00053
    octal    2000  1232 00065
    hex       400   29a 00035
    HEX etc   400  +666 53
    neg        -5 -0010 fffffff1
    char: A  string foo   foo veryl
    hour    12:09:00

Footprint: 1400-3200 bytes, plus 100-400 bytes for the frontend.

(With the 2014 changes the footprint is a few hundred bytes less, but
I lacked the time to make comprehensive builds; if you enable 64-bit
support, you'll get a size impact of a few hunderd bytes: for ARM,
around 200 bytes in vsprintf-full plus 200 for div64.o).

	The xint implementation in detail
	================================

This prints correctly "%c", "%s", "%i", "%x". Formats "%u" and "%d"
are synonyms of "%i", and "%p" is a synonym for "%x".  The only
supported attributes are '0' and a one-digit width (e.g.: "%08x"
works).  I personally use it a lot but I don't like it much, because it
is not powerful enough nor low-level as real hacker's too should be.
However, it matches the requirement of some projects with a little
user interface, where the "full" code reveals too large and the "mini"
code is too unfair to the reader.  To compile it and link the example,
please set "CONFIG_PRINTF_XINT=y" in your environment or Makefile.

This is the result of the example. As expected, data is aligned and
has leading zeroes when requested, but bot other formats are obeyed:

    integer  1024   666 00053
    octal    2000  1232 00065
    hex       400   29a 00035
    HEX etc   400   666    53
    neg        -5 -0010 fffffff1
    char: A  string foo foo verylongstring
    hour    12:09:00

Footprint: 350-800 bytes, plus 100-400 bytes for the frontend


	The miminal implementation in detail
	===================================

It is derived from the full one. I left all format parsing intact, but
only print "%s" and "%c". Everything else is printed as hex numbers
like "<badc0ffe>". This means your 47 printed as "%03i" will be output
as "<0000002f>" instead of "047".  Still, the standard format is
accepted without errors and no information is lost.

I have made no checks nor reasoning about 32-bit vs 64-bit. I only
used it on 32-bit computers and never printed "long long". Now that it
is published, I have an incentive to do it, though.

It is selected at compile time by setting CONFIG_PRINTF_MINI=y as a
make variable, possibly inherited by the environment.  It is licensed
as GPL version 2 because it's derived from the full one -- I left the
parsing as I found in there.

Result of example-printf (you can "make CONFIG_PRINTF_MINI=y):

    integer <00000400> <0000029a> <00000035>
    octal   <00000400> <0000029a> <00000035>
    hex     <00000400> <0000029a> <00000035>
    HEX etc <00000400> <0000029a> <00000035>
    neg     <fffffffb> <fffffff6> <fffffff1>
    char: A  string foo foo verylongstring
    hour    <0000000c>:<00000009>:<00000000>

As promised, %c and %s is printed correctly, but without obeying the
format modifiers, but all integer value are printed in hex.

Footprint: 200-600 bytes, plus 100-400 for the frontend.


	The empty implementation in detail
	==================================

The empty implementation, called "none" to respect the 4-letter
pattern of "full" and "mini" doesn't parse any format. It simply
prints the format string and nothing more.  This allows to keep the
most important messages, like the welcome string or a "Panic" string,
while saving code space.

It is selected at compile time by setting CONFIG_PRINTF_NONE.

Result of example-printf (you can "make CONFIG_PRINTF_MINI=y):

    integer %5i %5i %05i
    octal   %5o %5o %05o
    hex     %5x %5x %05x
    HEX etc %5X %+5d %-5i
    neg     %5i %05i %05x
    char: %c  string %s %5s %.5s
    hour    %02d:%02d:%02d

Footprint: 25-110 bytes, plus 100-400 for the frontend.

If you want to remove all printf overhead in production, you should
use a preprocessor macro to completely kill the printf calls. This
would save you the parameter-passing overhead in the caller and all
the constant strings in .rodata.  I don't support this in the package,
though, and I discourage from doing it, for the usual
preprocessor-related reasons.

	int64_t support
	===============

vsprintf-full.c now supports printing 64-bit items. It works in all
platforms, including the 16-bit AVR CPU.  The feature is disabled by
default because it has run-time impact (all integer conversions are
performed using 64-bit division.

Moreover, 64-bit supports requires 64-bit division. As customary in
the kernel/bootloader world we avoid picking the libgcc
implementation, and rely on the smaller __div64_32() function,
instead, which only supports a 32-bit divisor and thus a 32-bit
remainder; but this is all we need.  The function is available in
"lib64.c", by Bernardo Innocenti, which is used in the kernel and many
other projects.

So, to enable 64-bit support in pp-printf, please use

   CONFIG_PRINTF_64BIT=y

in your environment or command-line of make. If your project uses
Kconfig, you'll rely on it instead (I'm running pp-printf in 3
Kconfig-based projects and it works great).

If you already have __div64_32() in your project, you are done:
vsprintf-full.o will have an undefined symbol, that your final link
will resolve.  If you miss the function, you can use the local
version. To do that, set

   CONFIG_PRINTF_LOCAL_DIV64=y

in your environment or the command line of make.

The test program for 64-bit formats is not built by default. To build
it, you should

   make CONFIG_PRINTF_64BIT=y example-printf64

This automatically picks div64.o: you'll get a link error if you also
set CONFIG_PRINTF_LOCAL_DIV64=y. This is not a problem as the test
is meant to be run locally, only once or so.

The test prints one thousand, one million and so on using the various
formats, ending with the maximum positive and maximum negative::

    positive:                     1000
    negative:                    -1000
    neg-unsigned: 18446744073709550616
    pos-hex:        0x00000000000003e8
    pos-HEX:        0x00000000000003E8
    neg-hex:        0xfffffffffffffc18

    [...]

    positive:      1000000000000000000
    negative:     -1000000000000000000
    neg-unsigned: 17446744073709551616
    pos-hex:        0x0de0b6b3a7640000
    pos-HEX:        0x0DE0B6B3A7640000
    neg-hex:        0xf21f494c589c0000

    max positive:  9223372036854775807
    max negative: -9223372036854775808

You may want to verify with your own target platform, before using the
code in production (you can avoid that if you trust me, but I wouldn't
blindly trust programmers, in general).


	Footprint of the various implementations
	========================================

NOTE: these figures refer to the 2012 master. The "full"
implementation is now a few hundred bytes smaller.
Also, I made no measures for the 64-bit version, which adds a few
hundred bytes (and run-time overhead of every integer print).

This table excludes the static buffer (256 in .bss by default) and
only lists the code size (command "size", column "text"), compiled
with -Os as for this Makefile.

printf.o is the frontend and is linked in all four configurations,
the other ones are exclusive one another:

                        printf.o       full  xint  mini  none

   x86, gcc-4.4.5           87         1715   476   258    48
   x86-64, gcc-4.4.5       418         2325   712   433    77
   x86, gcc-4.6.2          255         2210   577   330   110
   arm, gcc-4.2.2          156         2408   684   356    52
   arm, gcc-4.5.2          128         2235   645   353    44
   arm, gcc-4.5.2 thumb2    80         1443   373   209    26
   lm32, gcc-4.5.3         196         3228   792   576    44
   mips, gcc-4.4.1         184         2616   824   504    72
   powerpc, gcc-4.4.1      328         2895   881   521    48
   coldfire, gcc-4.4.1      96         2025   485   257    42
   sh4, gcc-4.4.1          316         2152   608   408    34