1
0
Fork 0

Merge branch 'no_ntoa_template'

development v2.0.0
Marco Paland 8 years ago
commit 002234f209

2
.gitattributes vendored

@ -0,0 +1,2 @@
# ignore test path
test/* linguist-vendored

@ -9,11 +9,11 @@
This is a tiny but **fully loaded** printf, sprintf and snprintf implementation.
Primarily designed for usage in embedded systems, where printf is not available due to memory issues or in avoidance of linking against libc.
Using the standard libc printf may pull **a lot** of unwanted library stuff and can bloat code size about 20k. In this case the following implementation can be used.
Absolutely **NO dependencies** are required, printf.cpp brings all necessary routines, even its own fast `ftoa` conversion.
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`, `ntoa` conversion.
If memory footprint is really a critical issue, floating point support can be turned off via the `PRINTF_FLOAT_SUPPORT` compiler switch.
When using printf (instead of sprintf) you have to provide your own `_putchar()` low level function as console output.
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.
When using printf (instead of sprintf) you have to provide your own `_putchar()` low level function as console/serial output.
## Highligths and design goals
@ -28,18 +28,28 @@ Therefore I decided to write an own implementation which meets the following ite
- Support of dec/float number representation (with an own fast itoa/ftoa)
- Reentrant and thread-safe, malloc free
- LINT and compiler L4 warning free, coverity clean, automotive ready
- Extensive test suite (> 270 test cases) passing
- Extensive test suite (> 280 test cases) passing
- Simply the best *printf* around the net
- MIT license
## Usage
Add/link `printf.cpp` to your project and include `printf.h`. That's it.
Usage is 1:1 like the according stdio.h library version:
`int printf(const char* format, ...);`
`int sprintf(char* buffer, const char* format, ...);`
`int snprintf(char* buffer, size_t count, const char* format, ...);`
Add/link *printf.c* to your project and include *printf.h*. That's it.
Implement your low level output function needed for `printf()`:
```C
void _putchar(char character)
{
// send char to console etc.
}
```
Usage is 1:1 like the according stdio.h library version:
```C
int printf(const char* format, ...);
int sprintf(char* buffer, const char* format, ...);
int snprintf(char* buffer, size_t count, const char* format, ...);
```
**Due to genaral security reasons it is highly recommended to use `snprintf` (with the max buffer size as `count` parameter) only.**
`sprintf` has no buffer limitation, so when necessary - use it with care!
@ -115,6 +125,7 @@ The length sub-specifier modifies the length of the data type.
| NTOA_BUFFER_SIZE | 32 | ntoa (integer) conversion buffer size. This must be big enough to hold one converted numeric number, normally 32 is a sufficient value. |
| FTOA_BUFFER_SIZE | 32 | ftoa (float) conversion buffer size. This must be big enough to hold one converted float number, normally 32 is a sufficient value. |
| PRINTF_FLOAT_SUPPORT | defined | Define this to enable floating point (%f) support |
| PRINTF_LONG_LONG_SUPPORT | defined | Define this to enable long long (%ll) support |
## Test suite

@ -31,21 +31,24 @@
///////////////////////////////////////////////////////////////////////////////
#include <stdarg.h>
#include <stdbool.h>
#include "printf.h"
// buffer size used for printf
// buffer size used for printf (created on stack)
#define PRINTF_BUFFER_SIZE 128U
// ntoa conversion buffer size, this must be big enough to hold one converted numeric number
// ntoa conversion buffer size, this must be big enough to hold one converted numeric number (created on stack)
#define NTOA_BUFFER_SIZE 32U
// ftoa conversion buffer size, this must be big enough to hold one converted float number
// ftoa conversion buffer size, this must be big enough to hold one converted float number (created on stack)
#define FTOA_BUFFER_SIZE 32U
// define this to support floating point (%f)
#define PRINTF_FLOAT_SUPPORT
// define this to support long long types (%llu or %p)
#define PRINTF_LONG_LONG_SUPPORT
///////////////////////////////////////////////////////////////////////////////
@ -62,11 +65,12 @@
#define FLAGS_WIDTH (1U << 9U)
// internal strlen, returns the length of the string
static inline size_t _strlen(const char* str)
{
// internal strlen
// \return The length of the string (excluding the terminating 0)
static inline size_t _strlen(const char* str)
{
size_t len = 0U;
while (str[len] != '\0') {
while (str[len] != (char)0) {
len++;
}
return len;
@ -91,33 +95,15 @@ static inline unsigned int _atoi(const char** str)
}
// internal itoa
template<typename T>
static size_t _ntoa(T value, char* buffer, unsigned int base, size_t maxlen, unsigned int prec, unsigned int width, unsigned int flags)
// internal itoa format
static size_t _ntoa_format(char* buffer, char* buf, size_t len, bool negative, unsigned int base, size_t maxlen, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[NTOA_BUFFER_SIZE];
size_t len = 0U;
unsigned int negative = 0U;
if (maxlen == 0U) {
return 0U;
}
if (base > 16U) {
return 0U;
}
if (value < 0) {
negative = 1U;
value = 0 - value;
}
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || (value != 0)) {
do {
char digit = (char)(value % (T)base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= (T)base;
} while ((len < NTOA_BUFFER_SIZE) && (value > 0));
}
// pad leading zeros
while (!(flags & FLAGS_LEFT) && (len < prec) && (len < NTOA_BUFFER_SIZE)) {
@ -171,7 +157,7 @@ static size_t _ntoa(T value, char* buffer, unsigned int base, size_t maxlen, uns
// reverse string
for (size_t i = 0U; (i < len) && (i < maxlen); ++i) {
buffer[i] = buf[len - i - 1];
buffer[i] = buf[len - i - 1U];
}
// append pad spaces up to given width
@ -185,24 +171,66 @@ static size_t _ntoa(T value, char* buffer, unsigned int base, size_t maxlen, uns
}
#if defined(PRINTF_FLOAT_SUPPORT)
static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec, unsigned int width, unsigned int flags)
// internal itoa for 'long' type
static size_t _ntoa_long(char* buffer, unsigned long value, bool negative, unsigned long base, size_t maxlen, unsigned int prec, unsigned int width, unsigned int flags)
{
// test for NaN
if (!(value == value) && (maxlen > 2U)) {
buffer[0] = 'n'; buffer[1] = 'a'; buffer[2] = 'n';
return (size_t)3U;
char buf[NTOA_BUFFER_SIZE];
size_t len = 0U;
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while ((len < NTOA_BUFFER_SIZE) && value);
}
return _ntoa_format(buffer, buf, len, negative, (unsigned int)base, maxlen, prec, width, flags);
}
// internal itoa for 'long long' type
#if defined(PRINTF_LONG_LONG_SUPPORT)
static size_t _ntoa_long_long(char* buffer, unsigned long long value, bool negative, unsigned long long base, size_t maxlen, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[NTOA_BUFFER_SIZE];
size_t len = 0U;
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while ((len < NTOA_BUFFER_SIZE) && value);
}
// if input is larger than thres_max, revert to exponential
const double thres_max = (double)0x7FFFFFFF;
return _ntoa_format(buffer, buf, len, negative, (unsigned int)base, maxlen, prec, width, flags);
}
#endif // PRINTF_LONG_LONG_SUPPORT
#if defined(PRINTF_FLOAT_SUPPORT)
static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[FTOA_BUFFER_SIZE];
size_t len = 0U;
double diff = 0;
double diff = 0.0;
// if input is larger than thres_max, revert to exponential
const double thres_max = (double)0x7FFFFFFF;
// powers of 10
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
// test for negative
bool negative = false;
if (value < 0) {
negative = true;
value = 0 - value;
}
// limit precision
if (!(flags & FLAGS_PRECISION)) {
prec = 6U; // by default, precesion is 6
@ -212,12 +240,6 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec
prec = 9U;
}
unsigned int negative = 0U;
if (value < 0) {
negative = 1U;
value = 0 - value;
}
int whole = (int)value;
double tmp = (value - whole) * pow10[prec];
unsigned long frac = (unsigned long)tmp;
@ -239,10 +261,10 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec
// 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 0;
return 0U;
}
if (prec == 0) {
if (prec == 0U) {
diff = value - whole;
if (diff > 0.5) {
// greater than 0.5, round up, e.g. 1.6 -> 2
@ -278,7 +300,7 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec
break;
}
}
// pad leading zeros
while (!(flags & FLAGS_LEFT) && (len < prec) && (len < FTOA_BUFFER_SIZE)) {
buf[len++] = '0';
@ -325,15 +347,15 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec
// internal vsnprintf
static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_list va)
static size_t _vsnprintf(char* buffer, size_t buffer_len, const char* format, va_list va)
{
unsigned int flags, width, precision, n;
size_t idx = 0U;
while (idx < buffer_len) {
// end reached?
if (*format == '\0') {
buffer[idx] = '\0';
if (*format == (char)0) {
buffer[idx] = (char)0;
break;
}
@ -405,13 +427,13 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_
// evaluate specifier
switch (*format) {
case 'd' :
case 'i' :
case 'u' :
case 'x' :
case 'X' :
case 'o' :
case 'b' :
case 'd' :
case 'i' : {
case 'b' : {
// set the base
unsigned int base;
if (*format == 'x' || *format == 'X') {
@ -442,25 +464,32 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_
if ((*format == 'i') || (*format == 'd')) {
// signed
if (flags & FLAGS_LONG_LONG) {
idx += _ntoa<long long>(va_arg(va, long long), &buffer[idx], base, buffer_len - idx, precision, width, flags);
#if defined(PRINTF_LONG_LONG_SUPPORT)
const long long value = va_arg(va, long long);
idx += _ntoa_long_long(&buffer[idx], (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, buffer_len - idx, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
idx += _ntoa<long>(va_arg(va, long), &buffer[idx], base, buffer_len - idx, precision, width, flags);
const long value = va_arg(va, long);
idx += _ntoa_long(&buffer[idx], (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, buffer_len - idx, precision, width, flags);
}
else {
idx += _ntoa<int>(va_arg(va, int), &buffer[idx], base, buffer_len - idx, precision, width, flags);
const int value = va_arg(va, int);
idx += _ntoa_long(&buffer[idx], (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, buffer_len - idx, precision, width, flags);
}
}
else {
// unsigned
if (flags & FLAGS_LONG_LONG) {
idx += _ntoa<unsigned long long>(va_arg(va, unsigned long long), &buffer[idx], base, buffer_len - idx, precision, width, flags);
#if defined(PRINTF_LONG_LONG_SUPPORT)
idx += _ntoa_long_long(&buffer[idx], va_arg(va, unsigned long long), false, base, buffer_len - idx, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
idx += _ntoa<unsigned long>(va_arg(va, unsigned long), &buffer[idx], base, buffer_len - idx, precision, width, flags);
idx += _ntoa_long(&buffer[idx], va_arg(va, unsigned long), false, base, buffer_len - idx, precision, width, flags);
}
else {
idx += _ntoa<unsigned int>(va_arg(va, unsigned int), &buffer[idx], base, buffer_len - idx, precision, width, flags);
idx += _ntoa_long(&buffer[idx], va_arg(va, unsigned int), false, base, buffer_len - idx, precision, width, flags);
}
}
format++;
@ -521,13 +550,14 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_
case 'p' : {
width = sizeof(void*) * 2U;
flags |= FLAGS_ZEROPAD;
size_t size_void = sizeof(void*);
if (size_void > sizeof(long)) {
idx +=_ntoa<unsigned long long>(reinterpret_cast<unsigned long long>(va_arg(va, void*)), &buffer[idx], 16U, buffer_len - idx, precision, width, flags);
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
if (sizeof(void*) == sizeof(long long)) {
#if defined(PRINTF_LONG_LONG_SUPPORT)
idx += _ntoa_long_long(&buffer[idx], (unsigned long long)va_arg(va, void*), false, 16U, buffer_len - idx, precision, width, flags);
#endif
}
else {
idx += _ntoa<unsigned long>(reinterpret_cast<unsigned long>(va_arg(va, void*)), &buffer[idx], 16U, buffer_len - idx, precision, width, flags);
idx += _ntoa_long(&buffer[idx], (unsigned long)va_arg(va, void*), false, 16U, buffer_len - idx, precision, width, flags);
}
format++;
break;
@ -555,7 +585,7 @@ int printf(const char* format, ...)
va_list va;
va_start(va, format);
char buffer[PRINTF_BUFFER_SIZE];
size_t ret = vsnprintf(buffer, PRINTF_BUFFER_SIZE, format, va);
size_t ret = _vsnprintf(buffer, PRINTF_BUFFER_SIZE, format, va);
va_end(va);
for (size_t i = 0U; i < ret; ++i) {
_putchar(buffer[i]);
@ -568,7 +598,7 @@ int sprintf(char* buffer, const char* format, ...)
{
va_list va;
va_start(va, format);
size_t ret = vsnprintf(buffer, (size_t)-1, format, va);
size_t ret = _vsnprintf(buffer, (size_t)-1, format, va);
va_end(va);
return (int)ret;
}
@ -578,7 +608,7 @@ int snprintf(char* buffer, size_t count, const char* format, ...)
{
va_list va;
va_start(va, format);
size_t ret = vsnprintf(buffer, count, format, va);
size_t ret = _vsnprintf(buffer, count, format, va);
va_end(va);
return (int)ret;
}

@ -1,76 +1,85 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2017, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// This file is part of the turnkey-board.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
// embedded systems with a very limited resources.
// Use this instead of bloated standard/newlib printf.
// These routines are thread safe and reentrant!
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _PRINTF_H_
#define _PRINTF_H_
#include <stddef.h>
/**
* Output a character to a custom device like UART.
* This function is declared here only. You have to write your custom implementation somewhere.
* \param character to output
* \return On success, the character written is returned
*/
int _putchar(char character);
/**
* Tiny printf implementation
* You have to implement _putchar if you use printf()
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
int printf(const char* format, ...);
/**
* Tiny sprintf implementation
* Due to security reasons YOU SHOULD CONSIDER USING SNPRINTF INSTEAD!
* \param buffer A pointer to the buffer where to store the formatted string
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
int sprintf(char* buffer, const char* format, ...);
/**
* Tiny snprintf implementation
* \param buffer A pointer to the buffer where to store the formatted string
* \param count The maximum number of characters to store in the buffer, including a terminating null character
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
int snprintf(char* buffer, size_t count, const char* format, ...);
#endif // _PRINTF_H_
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2017, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
// embedded systems with a very limited resources.
// Use this instead of bloated standard/newlib printf.
// These routines are thread safe and reentrant!
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _PRINTF_H_
#define _PRINTF_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Output a character to a custom device like UART.
* This function is declared here only. You have to write your custom implementation somewhere.
* \param character to output
*/
void _putchar(char character);
/**
* Tiny printf implementation
* You have to implement _putchar if you use printf()
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
int printf(const char* format, ...);
/**
* Tiny sprintf implementation
* Due to security reasons YOU SHOULD CONSIDER USING SNPRINTF INSTEAD!
* \param buffer A pointer to the buffer where to store the formatted string
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
int sprintf(char* buffer, const char* format, ...);
/**
* Tiny snprintf implementation
* \param buffer A pointer to the buffer where to store the formatted string
* \param count The maximum number of characters to store in the buffer, including a terminating null character
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
int snprintf(char* buffer, size_t count, const char* format, ...);
#ifdef __cplusplus
}
#endif
#endif // _PRINTF_H_

@ -35,15 +35,13 @@
namespace test {
// use functions in own test namespace to avoid stdio conflicts
#include "../printf.h"
#include "../printf.cpp"
#include "../printf.c"
} // namespace test
// dummy putchar
int test::_putchar(char)
{
return 0;
}
void test::_putchar(char)
{ }
@ -883,21 +881,48 @@ TEST_CASE("float", "[]" ) {
TEST_CASE("types", "[]" ) {
char buffer[100];
test::sprintf(buffer, "%i", 0);
REQUIRE(!strcmp(buffer, "0"));
test::sprintf(buffer, "%i", 1234);
REQUIRE(!strcmp(buffer, "1234"));
test::sprintf(buffer, "%i", 32767);
REQUIRE(!strcmp(buffer, "32767"));
test::sprintf(buffer, "%i", -32767);
REQUIRE(!strcmp(buffer, "-32767"));
test::sprintf(buffer, "%li", 30L);
REQUIRE(!strcmp(buffer, "30"));
test::sprintf(buffer, "%li", -2147483647L);
REQUIRE(!strcmp(buffer, "-2147483647"));
test::sprintf(buffer, "%li", 2147483647L);
REQUIRE(!strcmp(buffer, "2147483647"));
test::sprintf(buffer, "%lli", 30LL);
REQUIRE(!strcmp(buffer, "30"));
test::sprintf(buffer, "%lli", -9223372036854775807LL);
REQUIRE(!strcmp(buffer, "-9223372036854775807"));
test::sprintf(buffer, "%lli", 9223372036854775807LL);
REQUIRE(!strcmp(buffer, "9223372036854775807"));
test::sprintf(buffer, "%lu", 100000L);
REQUIRE(!strcmp(buffer, "100000"));
test::sprintf(buffer, "%lu", 0xFFFFFFFFL);
REQUIRE(!strcmp(buffer, "4294967295"));
test::sprintf(buffer, "%llu", 281474976710656LLU);
REQUIRE(!strcmp(buffer, "281474976710656"));
test::sprintf(buffer, "%llu", 18446744073709551615LLU);
REQUIRE(!strcmp(buffer, "18446744073709551615"));
test::sprintf(buffer, "%b", 60000);
REQUIRE(!strcmp(buffer, "1110101001100000"));
@ -951,6 +976,14 @@ TEST_CASE("pointer", "[]" ) {
else {
REQUIRE(!strcmp(buffer, "0000000012345678"));
}
test::sprintf(buffer, "%p", (void*)0xFFFFFFFFLU);
if (sizeof(void*) == 4U) {
REQUIRE(!strcmp(buffer, "FFFFFFFF"));
}
else {
REQUIRE(!strcmp(buffer, "00000000FFFFFFFF"));
}
}

Loading…
Cancel
Save