1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
# Printf and Scanf in Picolibc
Embedded systems are often tightly constrained on code space, which
makes it important to be able to only include code and data actually
needed by the application. The format-string based interface to
printf and scanf makes it very difficult to determine which
conversion operations might be needed by the application.
Picolibc handles this by providing multiple printf and scanf
implementations in the library with varying levels of conversion
support. The application developer selects among these at compile and
link time based on knowledge of the requirements of the
application. The selection is done using compiler command line
options, `--printf` and `--scanf` which map the public printf and
scanf names to internal names in the linker. These both take one of
the available variants as the option value:
cc --printf=i --scanf=i
The function name mapping happens in picolibc.specs file which uses
these options to add --defsym options when linking. These options also
set a preprocessor symbol which handles the 'f' variant, so you need
to pass these options during both compilation and linking.
Using the defsym approach, rather than renaming the functions with the
C preprocessor has a couple of benefits:
* Printf uses within picolibc (which are all integer-only) now share
whichever printf the application needs, avoiding including both
integer-only and float versions
* Printf optimizations in the compiler which map to puts or fputs now
work even when using integer-only or float printf functions.
Make sure you use only the option associated with the desired function
as --defsym will force the underlying code to be included in the
output even if unused.
The defsym approach does not work with link-time optimization. In that
case, applications can only use the default. The library must be
compiled with the desired variant as the default (using the
`-Dformat-default` configuration option).
## Printf and Scanf levels in Picolibc
There are five levels of printf support provided by Picolibc that can
be selected when building applications. One of these is the default
used when no symbol definitions are applied; that is selected using
the picolibc built-time option, `-Dformat-default`, which defaults to
`double`. The picolibc specs for GCC includes support for --printf and
--scanf options which set both the preprocessor defines as well as
configuring the linker to alias vfprintf and vfscanf to the desired
function using the --defsym linker command line option. For instance,
if you use cc --printf=d --scanf=l, the compiler will see
-D_PICOLIBC_PRINTF='d' -D_PICOLIBC_SCANF='l'
and the linker will see
--defsym=vfprintf=__d_vfprintf --defsym=vfscanf=__l_vfscanf
A linker that supports -alias instead of --defsym would see
-alias,__d_vfprintf,vfprintf -alias,__l_vfscanf,vfscanf
When not using the picolibc specs, you can manually specify these on
the compiler command line
These are listed in order of decreasing functionality and
size.
* 'd' format (default when `-Dformat-default=d`). This offers full printf
functionality, including both float and double conversions along
with the C99 extensions and POSIX positional parameters. There is
optional support for the C23 %b format specifier via the
`io-percent-b` setting and optional support for long double values
via the `io-long-double` setting.
* 'f' format (default when `-Dformat-default=f`). This is the
same as 'd' format except that it supports only float for scanf and
supports float instead of double for printf. It also doesn't
support long double conversions.
* 'l' format (default when `-Dformat-default=l`). This
removes support for all float and double conversions and makes
support for C99 extensions and POSIX positional parameters optional
via the `io-c99-formats` and `io-pos-args` settings.
* 'i' format (default when `-Dformat-default=i`). This removes
support for long long conversions where those values are larger
than long values.
* 'm' format (default when`-Dformat-default=m`). This removes support
for width and precision, alternate presentation modes and alternate
sign presentation. All of these are still parsed correctly, so
applications needn't adjust format strings in most cases, but the
output will not be the same. It also disables any support for
positional arguments and %b formats that might be selected with
`io-pos-args` or `io-percent-b`.
'f' format requires a special macro for float values:
`printf_float`. To make it easier to switch between that and the
default level, that macro is also correctly defined for the other two
levels.
Here's an example program to experiment with these options:
```c
#include <stdio.h>
void main(void) {
printf(" 2⁶¹ = %lld π ≃ %.17g\n", 1ll << 61, printf_float(3.141592653589793));
}
```
Now we can build and run it with the double options:
$ arm-none-eabi-gcc --printf=d -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf.elf printf.c
$ arm-none-eabi-size printf.elf
text data bss dec hex filename
7748 80 4104 11932 2e9c printf.elf
$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf.elf -nographic
2⁶¹ = 2305843009213693952 π ≃ 3.141592653589793
Switching to float-only reduces the size but lets this still work,
although the floating point value has reduced precision:
$ arm-none-eabi-gcc --printf=f -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-float.elf printf.c
$ arm-none-eabi-size printf-float.elf
text data bss dec hex filename
6284 80 4104 10468 28e4 printf-float.elf
$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-float.elf -nographic
2⁶¹ = 2305843009213693952 π ≃ 3.1415927
Selecting the long-long variant reduces the size further, but now the
floating point value is not displayed correctly:
$ arm-none-eabi-gcc --printf=l -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-long-long.elf printf.c
$ arm-none-eabi-size printf-long-long.elf
text data bss dec hex filename
2188 80 4104 6372 18e4 printf-long-long.elf
$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-long-long.elf -nographic
2⁶¹ = 2305843009213693952 π ≃ *float*
Going to integer-only reduces the size even further, but now it
doesn't output the long long value correctly:
$ arm-none-eabi-gcc --printf=i -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-int.elf printf.c
$ arm-none-eabi-size printf-int.elf
text data bss dec hex filename
2028 80 4104 6212 1844 printf-int.elf
$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-int.elf -nographic
2⁶¹ = 0 π ≃ *float*
To shrink things still further, use the 'minimal' variant. This
doesn't even look at the %g formatting instruction, so that value
displays as '%g'.
$ arm-none-eabi-gcc --printf=m -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-min.elf printf.c
$ arm-none-eabi-size printf-min.elf
text data bss dec hex filename
1508 80 4104 5692 163c printf-min.elf
$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-min.elf -nographic
2⁶¹ = 0 π ≃ %g
There's a build-time option available that enables long-long support
in the minimal printf variant, `-Dminimal-io-long-long=true`. Building with
that increases the size modestly while fixing the long long output:
$ arm-none-eabi-size printf-min.elf
text data bss dec hex filename
1632 80 4104 5816 16b8 printf-min.elf
$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-min.elf -nographic
2⁶¹ = 2305843009213693952 π ≃ %g
## Picolibc build options for stdio
In addition to the application build-time options, picolibc includes a
number of picolibc build-time options to control the feature set (and
hence the size) of the library:
* `-Dio-c99-formats=true` This option controls whether support for
the C99 type-specific format modifiers 'j', 'z' and 't' and the hex
float format 'a' are included in the long-long, integer and minimal
printf and scanf variants. Support for the C99 format specifiers
like PRId8 is always provided. This option is enabled by default.
* `-Dio-long-long=true` This deprecated option controls whether
support for long long types is included in the integer variant of
printf and scanf. Instead of using this option, applications should
select the long long printf and scanf variants. This option is
disabled by default.
* `-Dio-pos-args=true` This option add support for C99 positional
arguments to the long long and integer printf and scanf variant
(e.g. "%1$"). Positional arguments are always supported in the
double and float variants and never supported in the minimal
variant. This option is disabled by default.
* `-Dio-long-double=true` This option add support for long double
parameters. That is limited to systems using 80- and 128- bit long
doubles, or systems for which long double is the same as
double. This option is disabled by default
* `-Dio-float-exact=true` This option, which is enabled by default,
controls whether the tinystdio code uses exact algorithms for
printf and scanf. When enabled, printing at least 9 digits
(e.g. "%.9g") for 32-bit floats and 17 digits (e.g. "%.17g") for
64-bit floats ensures that passing the output back to scanf will
exactly re-create the original value.
* `-Datomic-ungetc=true` This option, which is enabled by default,
controls whether getc/ungetc use atomic instruction sequences to
make them re-entrant. Without this option, multiple threads using
getc and ungetc may corrupt the state of the input buffer.
* `-Dposix-console=true` This option provides stdin/stdout/stderr
support using the picolibc POSIX I/O support.
* `-Dformat=default={d,f,l,i,m}` Selects which printf and scanf
variant is used by default.
* `-Dprintf-aliases=true` This option, which is enabled by default,
provides for linker alias support for the --printf and --scanf
compiler options.
* `-Dio-percent-b=true` This option, which is disabled by default,
supports the C23 %b format specifier.
* `-Dprintf-small-ultoa=true` This option, which is enabled by
default, switches printf's binary-to-decimal conversion code to a
version which avoids soft division for values larger than the
machine registers as those functions are often quite large and
slow. Applications using soft division on large values elsewhere
will save space by disabling this option as that avoids including
custom divide-and-modulus-by-ten implementations.
* `-Dprintf-percent-n=true` This option, which is disabled by default,
provides support for the dangerous %n printf format specifier.
* `-Dminimal-io-long-long=true` This option controls whether support
for long long types is included in the minimal variant of printf
and scanf. This option is disabled by default.
* `-Dfast-bufio=true` This option directly calls the read and write
hooks from fread and fwrite when interacting with buffered streams.
* `-Dio-wchar=true` This option enables wide character input and
output even when picolibc is built without multi-byte character
support.
Picolibc also allows use of the legacy newlib stdio library, but that
is outside the scope of this documentation.
|