diff --git a/README.md b/README.md index f415329..5d03f03 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Primarily designed for usage in embedded systems, where printf is not available Using the standard libc printf may pull **a lot** of unwanted library stuff and can bloat code size about 20k or is not 100% thread safe. In this cases the following implementation can be used. Absolutely **NO dependencies** are required, *printf.c* brings all necessary routines, even its own fast `ftoa` (float), `ntoa` (decimal) conversion. -If memory footprint is really a critical issue, floating point and 'long long' support and can be turned off via the `PRINTF_FLOAT_SUPPORT` and `PRINTF_LONG_LONG_SUPPORT` compiler switches. +If memory footprint is really a critical issue, floating point and 'long long' support and can be turned off via the `PRINTF_SUPPORT_FLOAT` and `PRINTF_SUPPORT_LONG_LONG` compiler switches. When using printf (instead of sprintf/snprintf) you have to provide your own `_putchar()` low level function as console/serial output. @@ -52,7 +52,12 @@ int snprintf(char* buffer, size_t count, const char* format, ...); int vsnprintf(char* buffer, size_t count, const char* format, va_list va); ``` -**Due to genaral security reasons it is highly recommended to use `snprintf` (with the max buffer size as `count` parameter) instead of `sprintf`.** +If `buffer` is set to `nullptr` nothing is written and just the formatted length is returned. +```C +int length = sprintf(nullptr, "Hello, world"); // length is set to 12 +``` + +**Due to genaral security reasons it is highly recommended to prefer and use `snprintf` (with the max buffer size as `count` parameter) instead of `sprintf`.** `sprintf` has no buffer limitation, so when needed - use it really with care! @@ -113,9 +118,13 @@ The length sub-specifier modifies the length of the data type. | Length | d i | u o x X | |--------|------|---------| | (none) | int | unsigned int | +| hh | char | unsigned char | +| h | short int | unsigned short int | | l | long int | unsigned long int | -| ll | long long int | unsigned long long int (if PRINTF_LONG_LONG_SUPPORT is defined) | -| z | size_t int | unsigned size_t int | +| ll | long long int | unsigned long long int (if PRINTF_SUPPORT_LONG_LONG is defined) | +| j | intmax_t | uintmax_t | +| z | size_t | size_t | +| t | ptrdiff_t | ptrdiff_t (if PRINTF_SUPPORT_PTRDIFF_T is defined) | ### Return value @@ -134,8 +143,9 @@ If any error is encountered, `-1` is returned. |------|---------------|-------------| | PRINTF_NTOA_BUFFER_SIZE | 32 | ntoa (integer) conversion buffer size. This must be big enough to hold one converted numeric number _including_ leading zeros, normally 32 is a sufficient value. Created on the stack | | PRINTF_FTOA_BUFFER_SIZE | 32 | ftoa (float) conversion buffer size. This must be big enough to hold one converted float number _including_ leading zeros, normally 32 is a sufficient value. Created on the stack | -| PRINTF_FLOAT_SUPPORT | undefined | Define this to enable floating point (%f) support | -| PRINTF_LONG_LONG_SUPPORT | undefined | Define this to enable long long (%ll) support | +| PRINTF_SUPPORT_FLOAT | defined | Define this to enable floating point (%f) support | +| PRINTF_SUPPORT_LONG_LONG | defined | Define this to enable long long (%ll) support | +| PRINTF_SUPPORT_PTRDIFF_T | defined | Define this to enable ptrdiff_t (%t) support | ## Test suite diff --git a/printf.c b/printf.c index edc0a4e..5ba4748 100644 --- a/printf.c +++ b/printf.c @@ -36,32 +36,42 @@ // ntoa conversion buffer size, this must be big enough to hold -// one converted numeric number including padded zeros (dyn created on stack) +// one converted numeric number including padded zeros (dynamically created on stack) +// 32 byte is a good default #define PRINTF_NTOA_BUFFER_SIZE 32U // ftoa conversion buffer size, this must be big enough to hold -// one converted float number including padded zeros (dyn created on stack) +// one converted float number including padded zeros (dynamically created on stack) +// 32 byte is a good default #define PRINTF_FTOA_BUFFER_SIZE 32U // define this to support floating point (%f) -#define PRINTF_FLOAT_SUPPORT +#define PRINTF_SUPPORT_FLOAT // define this to support long long types (%llu or %p) -#define PRINTF_LONG_LONG_SUPPORT +#define PRINTF_SUPPORT_LONG_LONG + +// define this to support the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +#define PRINTF_SUPPORT_PTRDIFF_T + /////////////////////////////////////////////////////////////////////////////// // internal flag definitions -#define FLAGS_ZEROPAD (1U << 0U) -#define FLAGS_LEFT (1U << 1U) -#define FLAGS_PLUS (1U << 2U) -#define FLAGS_SPACE (1U << 3U) -#define FLAGS_HASH (1U << 4U) -#define FLAGS_UPPERCASE (1U << 5U) -#define FLAGS_LONG (1U << 6U) -#define FLAGS_LONG_LONG (1U << 7U) -#define FLAGS_PRECISION (1U << 8U) -#define FLAGS_WIDTH (1U << 9U) +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_WIDTH (1U << 11U) + // output function type typedef void (*out_fct_type)(char character, char* buffer, size_t idx, size_t maxlen); @@ -93,11 +103,11 @@ static inline void _out_char(char character, char* buffer, size_t idx, size_t ma // internal strlen // \return The length of the string (excluding the terminating 0) -static inline size_t _strlen(const char* str) +static inline unsigned int _strlen(const char* str) { const char* s; for (s = str; *s; ++s); - return (size_t)(s - str); + return (unsigned int)(s - str); } @@ -209,7 +219,7 @@ static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxl // internal itoa for 'long long' type -#if defined(PRINTF_LONG_LONG_SUPPORT) +#if defined(PRINTF_SUPPORT_LONG_LONG) static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) { char buf[PRINTF_NTOA_BUFFER_SIZE]; @@ -226,10 +236,10 @@ static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); } -#endif // PRINTF_LONG_LONG_SUPPORT +#endif // PRINTF_SUPPORT_LONG_LONG -#if defined(PRINTF_FLOAT_SUPPORT) +#if defined(PRINTF_SUPPORT_FLOAT) static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) { char buf[PRINTF_FTOA_BUFFER_SIZE]; @@ -271,13 +281,13 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d ++whole; } } - else if ((diff == 0.5) && ((frac == 0) || (frac & 1))) { + else if ((diff == 0.5) && ((frac == 0U) || (frac & 1U))) { // if halfway, round up if odd, OR if last digit is 0 ++frac; } - // TBD: for very large numbers switch back to native sprintf for exponentials. anyone want to write code to replace this? - // normal printf behavior is to print EVERY whole number digit which can be 100s of characters overflowing your buffers == bad + // TBD: for very large numbers switch back to native sprintf for exponentials. Anyone want to write code to replace this? + // Normal printf behavior is to print EVERY whole number digit which can be 100s of characters overflowing your buffers == bad if (value > thres_max) { return 0U; } @@ -288,7 +298,7 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d // greater than 0.5, round up, e.g. 1.6 -> 2 ++whole; } - else if ((diff == 0.5) && (whole & 1)) { + else if ((diff == 0.5) && (whole & 1U)) { // exactly 0.5 and ODD, then round up // 1.5 -> 2, but 2.5 -> 2 ++whole; @@ -361,7 +371,7 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d return idx; } -#endif // PRINTF_FLOAT_SUPPORT +#endif // PRINTF_SUPPORT_FLOAT // internal vsnprintf @@ -434,17 +444,39 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const } // evaluate length field - if (*format == 'l' || *format == 'L') { - flags |= FLAGS_LONG; - format++; - } - if ((*format == 'l') && (flags & FLAGS_LONG)) { - flags |= FLAGS_LONG_LONG; - format++; - } - if (*format == 'z') { - flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; + switch (*format) { + case 'l' : + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h' : + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't' : + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j' : + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z' : + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default : + break; } // evaluate specifier @@ -486,7 +518,7 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const if ((*format == 'i') || (*format == 'd')) { // signed if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_LONG_LONG_SUPPORT) +#if defined(PRINTF_SUPPORT_LONG_LONG) const long long value = va_arg(va, long long); idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); #endif @@ -496,14 +528,14 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); } else { - const int value = va_arg(va, int); + const int value = (flags & FLAGS_CHAR) ? va_arg(va, char) : (flags & FLAGS_SHORT) ? va_arg(va, short int) : va_arg(va, int); idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); } } else { // unsigned if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_LONG_LONG_SUPPORT) +#if defined(PRINTF_SUPPORT_LONG_LONG) idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); #endif } @@ -511,21 +543,22 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); } else { - idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned int), false, base, precision, width, flags); + const unsigned int value = (flags & FLAGS_CHAR) ? va_arg(va, unsigned char) : (flags & FLAGS_SHORT) ? va_arg(va, unsigned short int) : va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); } } format++; break; } -#if defined(PRINTF_FLOAT_SUPPORT) +#if defined(PRINTF_SUPPORT_FLOAT) case 'f' : case 'F' : idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); format++; break; -#endif // PRINTF_FLOAT_SUPPORT +#endif // PRINTF_SUPPORT_FLOAT case 'c' : { - size_t l = 1U; + unsigned int l = 1U; // pre padding if (!(flags & FLAGS_LEFT)) { while (l++ < width) { @@ -546,7 +579,7 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const case 's' : { char* p = va_arg(va, char*); - size_t l = _strlen(p); + unsigned int l = _strlen(p); // pre padding if (flags & FLAGS_PRECISION) { l = (l < precision ? l : precision); @@ -573,14 +606,14 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const case 'p' : { width = sizeof(void*) * 2U; flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; -#if defined(PRINTF_LONG_LONG_SUPPORT) +#if defined(PRINTF_SUPPORT_LONG_LONG) if (sizeof(uintptr_t) == sizeof(long long)) { idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); } else { #endif idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); -#if defined(PRINTF_LONG_LONG_SUPPORT) +#if defined(PRINTF_SUPPORT_LONG_LONG) } #endif format++; @@ -644,4 +677,3 @@ inline int vsnprintf(char* buffer, size_t count, const char* format, va_list va) { return _vsnprintf(_out_buffer, buffer, count, format, va); } - diff --git a/test/test_suite.cpp b/test/test_suite.cpp index 8ee90a3..21eb13f 100644 --- a/test/test_suite.cpp +++ b/test/test_suite.cpp @@ -994,6 +994,18 @@ TEST_CASE("types", "[]" ) { test::sprintf(buffer, "%s", "A Test"); REQUIRE(!strcmp(buffer, "A Test")); + + test::sprintf(buffer, "%hhu", 0xFFFFUL); + REQUIRE(!strcmp(buffer, "255")); + + test::sprintf(buffer, "%hu", 0x123456UL); + REQUIRE(!strcmp(buffer, "13398")); + + test::sprintf(buffer, "%s%hhi %hu", "Test", 10000, 0xFFFFFFFF); + REQUIRE(!strcmp(buffer, "Test16 65535")); + + test::sprintf(buffer, "%tx", &buffer[10] - &buffer[0]); + REQUIRE(!strcmp(buffer, "a")); }