i was looking at this heap implementation, and i was wondering if it is necessary to implement heap for printf even if i'm calling setvbuf(stdout, NULL, _IONBF, 0) before calling printf.
i looked at how printf is implemented and it calls this function :
int
_DEFUN (_VFPRINTF_R, (data, fp, fmt0, ap),
struct _reent *data _AND
FILE * fp _AND
_CONST char *fmt0 _AND
va_list ap)
{
register char *fmt; /* format string */
register int ch; /* character from fmt */
register int n, m; /* handy integers (short term usage) */
register char *cp; /* handy char pointer (short term usage) */
register struct __siov *iovp;/* for PRINT macro */
register int flags; /* flags as above */
int ret; /* return value accumulator */
int width; /* width from format (%8d), or 0 */
int prec; /* precision from format (%.3d), or -1 */
char sign; /* sign prefix (' ', '+', '-', or 0円) */
char old_sign; /* saved value of sign when looping for vectors */
int old_ch; /* saved value of ch when looping for vectors */
char *format_anchor; /* start of format to process */
wchar_t wc;
#ifdef FLOATING_POINT
char *decimal_point = localeconv()->decimal_point;
char softsign; /* temporary negative sign for floats */
#ifdef _NO_LONGDBL
union { int i; double d; } _double_ = {0};
#define _fpvalue (_double_.d)
#else
union { int i; _LONG_DOUBLE ld; } _long_double_ = {0};
#define _fpvalue (_long_double_.ld)
int tmp;
#endif
int expt; /* integer value of exponent */
int expsize = 0; /* character count for expstr */
int ndig; /* actual number of digits returned by cvt */
char expstr[7]; /* buffer for exponent string */
#endif
#ifndef _NO_LONGLONG
#define quad_t long long
#define u_quad_t unsigned long long
#else
#define quad_t long
#define u_quad_t u_long
#endif
u_quad_t _uquad; /* integer arguments %[diouxX] */
enum { OCT, DEC, HEX } base;/* base for [diouxX] conversion */
int dprec; /* a copy of prec if [diouxX], 0 otherwise */
int realsz; /* field size expanded by dprec */
int size; /* size of converted field or string */
char *xdigs = NULL; /* digits for [xX] conversion */
#define NIOV 8
struct __suio uio; /* output information: summary */
struct __siov iov[NIOV];/* ... and individual io vectors */
char buf[BUF]; /* space for %c, %[diouxX], %[eEfgG] */
char ox[2]; /* space for 0x hex-prefix */
#ifdef __ALTIVEC__
char vec_sep; /* vector separator char */
int vec_print_count; /* number of vector chunks remaining */
vec_16_byte_union vec_tmp;
#endif /* __ALTIVEC__ */
mbstate_t state; /* mbtowc calls from library must not change state */
/*
* Choose PADSIZE to trade efficiency vs. size. If larger printf
* fields occur frequently, increase PADSIZE and make the initialisers
* below longer.
*/
#define PADSIZE 16 /* pad chunk size */
static _CONST char blanks[PADSIZE] =
{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
static _CONST char zeroes[PADSIZE] =
{'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
/*
* BEWARE, these `goto error' on error, and PAD uses `n'.
*/
#define PRINT(ptr, len) { \
iovp->iov_base = (ptr); \
iovp->iov_len = (len); \
uio.uio_resid += (len); \
iovp++; \
if (++uio.uio_iovcnt >= NIOV) { \
if (__sprint_r(data, fp, &uio)) \
goto error; \
iovp = iov; \
} \
}
1 Answer 1
You need to forget many things you remember from "normal" computers when developing for MCUs.
and i was wondering if it is necessary to implement heap for printf even if i'm calling
setvbuf(stdout, NULL, _IONBF, 0)before calling printf
- On STM32 (and other embedded targets using newlib-nano or newlib-nano + ARM toolchain),
setvbuf(stdout, NULL, _IONBF, 0)does not really do anything useful in most cases:
There is no real stdout buffer
In the nano/newlib builds for STM32,
stdoutis usually tied to a very thin wrapper (like_write()implemented by you, sending characters to SWO, UART, or semihosting).
- With STM32 + newlib-nano,
printfdoes not use the heap for integer/strings, but does use the heap when you enable floating-point formats (%f, %e, %g) via-u _printf_float. The float path calls_dtoa_rwhich allocates via_malloc_rand ultimately your_sbrk.
I would suggest writing your own implementation of the conversion functions:
struct _reent;
char * _dtoa_r(struct _reent *r, double d, int mode, int ndigits,
int *decpt, int *sign, char **rve)
{
(void)r; (void)mode; (void)rve;
static char buf[64]; // not reentrant
// TODO: convert d into buf, set *decpt and *sign
return buf;
}
void _freedtoa(char *p) { (void)p; }
// Optional if you ever use %Lf
char * _ldtoa_r(struct _reent *r, long double d, int mode, int ndigits,
int *decpt, int *sign, char **rve)
{
return _dtoa_r(r, (double)d, mode, ndigits, decpt, sign, rve);
}
registerkeyword really do anything anymore? I read that with optimizations On and today's architecture, the compiler does a pretty good job of registering everything anyway. I thinkregisterwas born in an era of scarce registers and CPUs . . . and hence it made sense then, rt?registerspecifier, e.g., old platforms with old compilers, immature microcontroller architectures with immature compiler support, compilers with weak optimization, etc.