aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarco Paland <marco@paland.com>2018-05-11 12:49:41 +0200
committerMarco Paland <marco@paland.com>2018-05-11 12:49:41 +0200
commitbb5a8af50748b8846ce6d29063ef0747ca4cff86 (patch)
tree55ec59e64b0ad41237174732fa6668599ae20302
parent8609d7628583d7ee0d8e6447a4ad3d249b9b6434 (diff)
downloadprintf-bb5a8af50748b8846ce6d29063ef0747ca4cff86.tar.gz
printf-bb5a8af50748b8846ce6d29063ef0747ca4cff86.tar.bz2
printf-bb5a8af50748b8846ce6d29063ef0747ca4cff86.zip
feat(printf): added support for %h, %hh, %j and %t length modifiers
- Renamed compiler switches - Minor cleanup Closes #12
-rw-r--r--README.md22
-rw-r--r--printf.c124
-rw-r--r--test/test_suite.cpp12
3 files changed, 106 insertions, 52 deletions
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 <stddef.h> 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"));
}