From 59eabf489dde295fdacf33467d5ffb5f0a8b0283 Mon Sep 17 00:00:00 2001 From: Marco Paland Date: Mon, 30 Oct 2017 13:14:52 +0100 Subject: [PATCH 1/5] Added travis, coveralls and coverity support --- .travis.yml | 65 +++++++++++++ Makefile | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 13 ++- 3 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 .travis.yml create mode 100644 Makefile diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..edf7794 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,65 @@ +# Use a C++11 distro +dist: trusty +sudo: required + +# Enable C++ support +language: cpp + +# Compiler selection +compiler: gcc + +env: + global: + # coverity key + - secure: "MQNBwhFpj7NaDbN6NujsXQGRMpBYHvGtcjLXY9gCXhLUw+Ex40ArBEy1IEQAol7aGtFZiTZDijH1ihL4itzVmu4Bo2sx5WdMGRFS5tp/t4zFFiqpmmtbkYOUHY8wAp3xyxV2NL2EU0KlDloNvFd/4tY2opklUB3eTF+7TQ5CmwWRFGOf2GpGpmQ3+KS1ANhrxdn9sQIP8rSU+JD1/zwrAgeEN7RAxB23t9AU9MeGsOW3CCp3te8vrgNJ/xnOh7T4F/8hQ4lA5iCqqf7OUvcWOif3uBPzlckBTmQ6ylMvOncslj6TBkPJOd33MOmZcWHiPPEAvcXQlzERaLqgHLIg5A/0aTVo27J4iImHbwE61L2JhWvrx5mPkKz/9aTpZyYyQL1hXInk8R9VJ5+JENA5+maF+KNvdf1Zp2Foa3BLFFdRl5thpgyafvcEAZ/7KJ1BC3u4OgTBhh9Tw+1UMFXB9W9+2W2iYUIun8BunDny1aLmnp/t7O1auXjqW2lAWHtH86v9NickDupzkw1klN3Ac+hmfBDffyxUjE2LlmjpC20z2uwKrZCT7XRhalYstPPIwQ6uGr6TVZjqq6mzEjpmmF93U8WYx7iWBJmen0CHbkjY3n8dbUe7aiXQHwjbXFZ8MsWszFvr9eicJOcAOxXKK5jOw4uQSER6NI2H04rwbIs=" + +# addons +addons: + apt: + packages: + - gcc-6 + - g++-6 + sources: + - ubuntu-toolchain-r-test + + coverity_scan: + project: + name: "mpaland/printf" + description: "printf implementation" + notification_email: marco@paland.com + build_command_prepend: "make clean" + build_command: "make" + branch_pattern: master + +before_install: + # install coveralls + - pip install --user cpp-coveralls + # connect coverity + - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- + +# Active branches +branches: + only: + - travis + +script: + # Link gcc-6 and g++-6 to their standard commands + - sudo rm /usr/bin/gcc + - sudo rm /usr/bin/g++ + - sudo ln -s /usr/bin/gcc-6 /usr/bin/gcc + - sudo ln -s /usr/bin/g++-6 /usr/bin/g++ + # Export CC and CXX + - export CC=/usr/bin/gcc-6 + - export CXX=/usr/bin/g++-6 + # Check versions of gcc, g++ + - gcc -v && g++ -v + # Run build commands + - make + # execute the text suite + - bin/test_suite -d yes + # coverall profiling + - tmp/cov/test_suite + +after_success: + # Report to coveralls + - coveralls --build-root ${TRAVIS_BUILD_DIR} --include printf.cpp --gcov 'gcov-6' --gcov-options '\-lp' diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7b28a1a --- /dev/null +++ b/Makefile @@ -0,0 +1,271 @@ +# ------------------------------------------------------------------------------ +# +# Generic Makefile +# +# Copyright Marco Paland 2007 - 2017 +# Distributed under the MIT License +# +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Paths +# ------------------------------------------------------------------------------ +PATH_TOOLS_CC = /usr/bin/ +PATH_TOOLS_CC_LIB = /usr/lib/ +PATH_TOOLS_UTIL = + +PATH_BIN = bin +PATH_TMP = tmp +PATH_NUL = /dev/null +PATH_OBJ = $(PATH_TMP)/obj +PATH_LST = $(PATH_TMP)/lst +PATH_ERR = $(PATH_TMP)/err +PATH_PRE = $(PATH_TMP)/pre +PATH_COV = $(PATH_TMP)/cov + + +# ------------------------------------------------------------------------------ +# Application to build +# ------------------------------------------------------------------------------ + +APP = test_suite + + +# ----------------------------------------------------------------------------- +# Project file list +# Format is: +# FILES_PRJ = file1 \ +# foo/file2 \ +# bar/file3 +# ----------------------------------------------------------------------------- + +FILES_PRJ = test/test_suite + + +# ------------------------------------------------------------------------------ +# Additional include files and compiler defines +# Format is: +# C_INCLUDES = -Iinclude_path1 \ +# -Iinclude_path2 \ +# -Iinclude_path3 \ +# ------------------------------------------------------------------------------ + +C_INCLUDES = + +C_DEFINES = + + +# ------------------------------------------------------------------------------ +# The target name and location +# ------------------------------------------------------------------------------ +TRG = $(PATH_BIN)/$(APP) + + +# ------------------------------------------------------------------------------ +# object files +# ------------------------------------------------------------------------------ +FILES_TMP = $(FILES_PRJ) +FILES_O = $(addsuffix .o, $(FILES_TMP)) + + +# ------------------------------------------------------------------------------ +# VPATH definition +# +# VPATH is required for the maker to find the C-/ASM-Source files. +# Extract the directory/module names from the file list with the dir +# command and remove the duplicated directory names with the sort command. +# FILES_PRJ is listed first to make sure that the source files in the project +# directory are searched first. +# ------------------------------------------------------------------------------ +VPATH := $(sort $(dir $(FILES_TMP))) + + +# ------------------------------------------------------------------------------ +# Development tools +# ------------------------------------------------------------------------------ +AR = $(PATH_TOOLS_CC)ar +AS = $(PATH_TOOLS_CC)g++ +CC = $(PATH_TOOLS_CC)g++ +CL = $(PATH_TOOLS_CC)g++ +NM = $(PATH_TOOLS_CC)nm +GCOV = $(PATH_TOOLS_CC)gcov +OBJDUMP = $(PATH_TOOLS_CC)objdump +OBJCOPY = $(PATH_TOOLS_CC)objcopy +READELF = $(PATH_TOOLS_CC)readelf +SIZE = $(PATH_TOOLS_CC)size + +ECHO = $(PATH_TOOLS_UTIL)echo +MAKE = $(PATH_TOOLS_UTIL)make +MKDIR = $(PATH_TOOLS_UTIL)mkdir +RM = $(PATH_TOOLS_UTIL)rm +SED = $(PATH_TOOLS_UTIL)sed + + +# ------------------------------------------------------------------------------ +# Compiler flags for the target architecture +# ------------------------------------------------------------------------------ + +GCCFLAGS = $(C_INCLUDES) \ + $(C_DEFINES) \ + -std=c++11 \ + -g \ + -Wall \ + -pedantic \ + -Wmain \ + -Wundef \ + -Wsign-conversion \ + -Wuninitialized \ + -Wshadow \ + -Wunreachable-code \ + -Wswitch-default \ + -Wswitch \ + -Wcast-align \ + -Wmissing-include-dirs \ + -Winit-self \ + -Wdouble-promotion \ + -gdwarf-2 \ + -fno-exceptions \ + -O2 \ + -ffunction-sections \ + -ffat-lto-objects \ + -fdata-sections \ + -fverbose-asm \ + -Wextra \ + -Wunused-parameter \ + -Wfloat-equal + +CFLAGS = $(GCCFLAGS) \ + -Wunsuffixed-float-constants \ + -x c \ + -std=c99 + +CPPFLAGS = $(GCCFLAGS) \ + -x c++ \ + -fno-rtti \ + -fstrict-enums \ + -fno-use-cxa-atexit \ + -fno-use-cxa-get-exception-ptr \ + -fno-nonansi-builtins \ + -fno-threadsafe-statics \ + -fno-enforce-eh-specs \ + -ftemplate-depth-64 \ + -fexceptions + +AFLAGS = $(GCCFLAGS) \ + -x assembler + +LFLAGS = $(GCCFLAGS) \ + -x none \ + -Wl,--gc-sections + +# ------------------------------------------------------------------------------ +# Targets +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Main-Dependencies (app: all) +# ------------------------------------------------------------------------------ +.PHONY: all +all: clean_prj $(TRG) $(TRG)_nm.txt + + +# ------------------------------------------------------------------------------ +# Main-Dependencies (app: rebuild) +# ------------------------------------------------------------------------------ +.PHONY: rebuild +rebuild: clean $(TRG) $(TRG)_nm.txt + + +# ------------------------------------------------------------------------------ +# clean project +# ------------------------------------------------------------------------------ +.PHONY: clean_prj +clean_prj: + @-$(ECHO) +++ cleaning project + @-$(RM) -rf $(PATH_BIN) 2> $(PATH_NUL) + @-$(MKDIR) -p $(PATH_BIN) + @-$(MKDIR) -p $(PATH_OBJ) + @-$(MKDIR) -p $(PATH_ERR) + @-$(MKDIR) -p $(PATH_LST) + @-$(MKDIR) -p $(PATH_PRE) + @-$(MKDIR) -p $(PATH_COV) + + +# ------------------------------------------------------------------------------ +# clean all +# ------------------------------------------------------------------------------ +.PHONY: clean +clean: + @-$(ECHO) +++ cleaning all + @-$(RM) -rf $(PATH_BIN) 2> $(PATH_NUL) + @-$(RM) -rf $(PATH_TMP) 2> $(PATH_NUL) + @-$(MKDIR) -p $(PATH_BIN) + @-$(MKDIR) -p $(PATH_OBJ) + @-$(MKDIR) -p $(PATH_ERR) + @-$(MKDIR) -p $(PATH_LST) + @-$(MKDIR) -p $(PATH_COV) + + +# ------------------------------------------------------------------------------ +# print the GNUmake version and the compiler version +# ------------------------------------------------------------------------------ +.PHONY: version +version: + # Print the GNU make version and the compiler version + @$(ECHO) GNUmake version: + @$(MAKE) --version + @$(ECHO) GCC version: + @$(CL) -v + + +# ------------------------------------------------------------------------------ +# Rules +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Link/locate application +# ------------------------------------------------------------------------------ +$(TRG) : $(FILES_O) + @-$(ECHO) +++ linkink application to generate: $(TRG) + @-$(CL) $(LFLAGS) -L. -lc $(PATH_OBJ)/*.o -Wl,-Map,$(TRG).map -o $(TRG) + # profiling + @-$(CL) $(LFLAGS) -L. -lc $(PATH_COV)/*.o --coverage -o $(PATH_COV)/$(APP) + + +# ------------------------------------------------------------------------------ +# parse the object files to obtain symbol information, and create a size summary +# ------------------------------------------------------------------------------ +$(TRG)_nm.txt : $(TRG) + @-$(ECHO) +++ parsing symbols with nm to generate: $(TRG)_nm.txt + @-$(NM) --numeric-sort --print-size $(TRG) > $(TRG)_nm.txt + @-$(ECHO) +++ demangling symbols with c++filt to generate: $(TRG)_cppfilt.txt + @-$(NM) --numeric-sort --print-size $(TRG) | $(CPPFILT) > $(TRG)_cppfilt.txt + @-$(ECHO) +++ creating size summary table with size to generate: $(TRG)_size.txt + @-$(SIZE) -A -t $(TRG) > $(TRG)_size.txt + + +%.o : %.cpp + @$(ECHO) +++ compile: $< + # Compile the source file + # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window + # ...and Create an assembly listing using objdump + # ...and Generate a dependency file (using the -MM flag) + @-$(CL) $(CPPFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre + @-$(CL) $(CPPFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err + @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err + @-$(OBJDUMP) --disassemble --line-numbers -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst + @-$(CL) $(CPPFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d + # profiling + @-$(CL) $(CPPFLAGS) -O0 --coverage $< -c -o $(PATH_COV)/$(basename $(@F)).o 2> $(PATH_NUL) + +%.o : %.c + @$(ECHO) +++ compile: $< + # Compile the source file + # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window + # ...and Create an assembly listing using objdump + # ...and Generate a dependency file (using the -MM flag) + @-$(CL) $(CFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre + @-$(CC) $(CFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err + @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err + @-$(OBJDUMP) -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst + @-$(CC) $(CFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d diff --git a/README.md b/README.md index 8ec250f..b941f74 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ # printf / sprintf for embedded systems +[![Build Status](https://travis-ci.org/mpaland/printf.svg?branch=master)](https://travis-ci.org/mpaland/printf) +[![Coveralls Status](https://coveralls.io/repos/github/mpaland/printf/badge.svg?branch=master)](https://coveralls.io/github/mpaland/printf?branch=master) +[![Coverity Status](https://img.shields.io/coverity/scan/14180.svg)](https://scan.coverity.com/projects/mpaland-printf) +[![Github Issues](https://img.shields.io/github/issues/mpaland/printf.svg)](http://github.com/mpaland/printf/issues) +[![Github Releases](https://img.shields.io/github/release/mpaland/printf.svg)](https://github.com/mpaland/printf/releases) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/mpaland/avl_array/master/LICENSE) + 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. +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. If memory footprint is really a critical issue, floating point support can be turned off via the `PRINTF_FLOAT_SUPPORT` compiler switch. @@ -12,13 +19,13 @@ When using printf (instead of sprintf) you have to provide your own `_putchar()` ## Design goals There is a boatload of so called 'tiny' printf implementations around. So why this one? -I tested many implementations, but most of them have very limited flag/specifier support, a lot of other dependencies or are just not standard compliant and failing the test suite. +I've tested many implementations, but most of them have very limited flag/specifier support, a lot of other dependencies or are just not standard compliant and failing the test suite. Therefore I decided to write an own implementation which meets the following items: - Very small implementation (< 500 code lines) - NO dependencies, no libs, just one module file - Support of all important flags, width and precision sub-specifiers (see below) - - Support of float number representation (with an own fast ftoa) + - Support of dec/float number representation (with an own fast itoa/ftoa) - Reentrant and thread-safe, malloc free - LINT and compiler L4 warning free, clean code - Extensive test suite passing From 4995a4f70335873f1050bcb6b0e040a3e9e88688 Mon Sep 17 00:00:00 2001 From: Marco Paland Date: Mon, 30 Oct 2017 13:19:35 +0100 Subject: [PATCH 2/5] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b941f74..fd7d142 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ If memory footprint is really a critical issue, floating point support can be tu When using printf (instead of sprintf) you have to provide your own `_putchar()` low level function as console output. -## Design goals +## Highligths and design goals There is a boatload of so called 'tiny' printf implementations around. So why this one? I've tested many implementations, but most of them have very limited flag/specifier support, a lot of other dependencies or are just not standard compliant and failing the test suite. From f54e549e4b8053eb9266f87491877896790eadb8 Mon Sep 17 00:00:00 2001 From: Marco Paland Date: Mon, 30 Oct 2017 13:38:08 +0100 Subject: [PATCH 3/5] Changed coverity key --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index edf7794..0d7cadc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ compiler: gcc env: global: # coverity key - - secure: "MQNBwhFpj7NaDbN6NujsXQGRMpBYHvGtcjLXY9gCXhLUw+Ex40ArBEy1IEQAol7aGtFZiTZDijH1ihL4itzVmu4Bo2sx5WdMGRFS5tp/t4zFFiqpmmtbkYOUHY8wAp3xyxV2NL2EU0KlDloNvFd/4tY2opklUB3eTF+7TQ5CmwWRFGOf2GpGpmQ3+KS1ANhrxdn9sQIP8rSU+JD1/zwrAgeEN7RAxB23t9AU9MeGsOW3CCp3te8vrgNJ/xnOh7T4F/8hQ4lA5iCqqf7OUvcWOif3uBPzlckBTmQ6ylMvOncslj6TBkPJOd33MOmZcWHiPPEAvcXQlzERaLqgHLIg5A/0aTVo27J4iImHbwE61L2JhWvrx5mPkKz/9aTpZyYyQL1hXInk8R9VJ5+JENA5+maF+KNvdf1Zp2Foa3BLFFdRl5thpgyafvcEAZ/7KJ1BC3u4OgTBhh9Tw+1UMFXB9W9+2W2iYUIun8BunDny1aLmnp/t7O1auXjqW2lAWHtH86v9NickDupzkw1klN3Ac+hmfBDffyxUjE2LlmjpC20z2uwKrZCT7XRhalYstPPIwQ6uGr6TVZjqq6mzEjpmmF93U8WYx7iWBJmen0CHbkjY3n8dbUe7aiXQHwjbXFZ8MsWszFvr9eicJOcAOxXKK5jOw4uQSER6NI2H04rwbIs=" + - secure: "NKZbBnMALGIIQJy/s2kc3EST/stw+gjhtrGq0jkbsWr7Wx3FH+lmLeHNsDXRnD1VbpG02c5YsLllqz9OVu+0yxWGepvKNmCz1cNITIALEHbrax8/Af9LzPRL/QZxS/Qe11sMuySp4X16mFBUyxMd/X+I9i96Xf1vKkZABklYD1Q=" # addons addons: @@ -25,7 +25,7 @@ addons: coverity_scan: project: name: "mpaland/printf" - description: "printf implementation" + description: "Tiny printf implementation" notification_email: marco@paland.com build_command_prepend: "make clean" build_command: "make" From e50beaa69ea4832dd984fef2e4c297df3495801b Mon Sep 17 00:00:00 2001 From: Marco Paland Date: Mon, 30 Oct 2017 13:38:59 +0100 Subject: [PATCH 4/5] Fixed compiler warnings --- printf.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/printf.cpp b/printf.cpp index 22831d0..5eb8840 100644 --- a/printf.cpp +++ b/printf.cpp @@ -85,7 +85,7 @@ static inline unsigned int _atoi(const char** str) { unsigned int i = 0U; while (_is_digit(**str)) { - i = i * 10U + *((*str)++) - '0'; + i = i * 10U + (unsigned int)(*((*str)++) - '0'); } return i; } @@ -197,8 +197,8 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec const double thres_max = (double)0x7FFFFFFF; char buf[FTOA_BUFFER_SIZE]; - size_t len = 0U; - double diff = 0.0; + size_t len = 0U; + double diff = 0; // powers of 10 static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; @@ -225,7 +225,7 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec if (diff > 0.5) { ++frac; - // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 if (frac >= pow10[prec]) { frac = 0; ++whole; @@ -479,7 +479,7 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ } } // char output - buffer[idx++] = va_arg(va, char); + buffer[idx++] = (char)va_arg(va, int); // post padding if (flags & FLAGS_LEFT) { while ((idx < buffer_len) && (l++ < width)) { From 5813e52e3f08ca1c4bb4e4ea41e03ce787ac25c8 Mon Sep 17 00:00:00 2001 From: Marco Paland Date: Tue, 31 Oct 2017 13:49:28 +0100 Subject: [PATCH 5/5] Fix compiler/coverity warnings, add test cases --- .travis.yml | 2 +- README.md | 11 ++--- printf.cpp | 44 ++++++++++--------- test/test_suite.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0d7cadc..c3047a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ before_install: # Active branches branches: only: - - travis + - master script: # Link gcc-6 and g++-6 to their standard commands diff --git a/README.md b/README.md index fd7d142..f2e230f 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ [![Github Releases](https://img.shields.io/github/release/mpaland/printf.svg)](https://github.com/mpaland/printf/releases) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/mpaland/avl_array/master/LICENSE) -This is a tiny but fully loaded printf, sprintf and snprintf implementation. +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. +Absolutely **NO dependencies** are required, printf.cpp brings all necessary routines, even its own fast `ftoa` 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. @@ -27,8 +27,8 @@ Therefore I decided to write an own implementation which meets the following ite - Support of all important flags, width and precision sub-specifiers (see below) - Support of dec/float number representation (with an own fast itoa/ftoa) - Reentrant and thread-safe, malloc free - - LINT and compiler L4 warning free, clean code - - Extensive test suite passing + - LINT and compiler L4 warning free, coverity clean, automotive ready + - Extensive test suite (> 260 test cases) passing - MIT license @@ -41,7 +41,8 @@ Usage is 1:1 like the according stdio.h library version: `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.** +**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! ## Format specifiers diff --git a/printf.cpp b/printf.cpp index 5eb8840..d5f3603 100644 --- a/printf.cpp +++ b/printf.cpp @@ -65,18 +65,18 @@ // internal strlen, returns the length of the string static inline size_t _strlen(const char* str) { - size_t len = 0U; - while (str[len] != '\0') { - len++; - } - return len; -} + size_t len = 0U; + while (str[len] != '\0') { + len++; + } + return len; +} -// returns 1 if char is a digit, 0 if not -static inline unsigned int _is_digit(char ch) +// returns true if char is a digit +static inline bool _is_digit(char ch) { - return (ch >= '0' && ch <= '9') ? 1U : 0U; + return (ch >= '0') && (ch <= '9'); } @@ -113,7 +113,7 @@ static size_t _ntoa(T value, char* buffer, unsigned int base, size_t maxlen, uns // write if precision != 0 and value is != 0 if (!(flags & FLAGS_PRECISION) || (value != 0)) { do { - char digit = (char)((unsigned)value % base); + 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)); @@ -271,13 +271,13 @@ static size_t _ftoa(double value, char* buffer, size_t maxlen, unsigned int prec } } - // do whole part - // Take care of sign conversion. Number is reversed - size_t wlen = 0U; - do { + // do whole part, number is reversed + while (len < FTOA_BUFFER_SIZE) { buf[len++] = (char)(48 + (whole % 10)); - wlen++; - } while ((len < FTOA_BUFFER_SIZE) && (whole /= 10)); + if (!(whole /= 10)) { + break; + } + } // pad leading zeros while (!(flags & FLAGS_LEFT) && (len < prec) && (len < FTOA_BUFFER_SIZE)) { @@ -368,7 +368,7 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ width = _atoi(&format); } else if (*format == '*') { - const int w = (unsigned int)va_arg(va, int); + const int w = va_arg(va, int); if (w < 0) { flags |= FLAGS_LEFT; // reverse padding width = (unsigned int)-w; @@ -410,8 +410,6 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ case 'X' : case 'o' : case 'b' : - // no plus or space flag for the types above - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); case 'd' : case 'i' : { // set the base @@ -435,8 +433,13 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ flags |= FLAGS_UPPERCASE; } + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + // convert the integer - if (*format == 'i' || *format == 'd') { + if ((*format == 'i') || (*format == 'd')) { // signed if (flags & FLAGS_LONG_LONG) { idx += _ntoa(va_arg(va, long long), &buffer[idx], base, buffer_len - idx, precision, width, flags); @@ -537,6 +540,7 @@ static size_t vsnprintf(char* buffer, size_t buffer_len, const char* format, va_ default : buffer[idx++] = *format; + format++; break; } } diff --git a/test/test_suite.cpp b/test/test_suite.cpp index a90f701..0b2f680 100644 --- a/test/test_suite.cpp +++ b/test/test_suite.cpp @@ -203,6 +203,9 @@ TEST_CASE("0 flag", "[]" ) { test::sprintf(buffer, "%0d", 42); REQUIRE(!strcmp(buffer, "42")); + test::sprintf(buffer, "%0ld", 42L); + REQUIRE(!strcmp(buffer, "42")); + test::sprintf(buffer, "%0d", -42); REQUIRE(!strcmp(buffer, "-42")); @@ -864,4 +867,105 @@ TEST_CASE("float", "[]" ) { test::sprintf(buffer, "%+6.2f", 42.8952); REQUIRE(!strcmp(buffer, "+42.90")); + test::sprintf(buffer, "%+5.1f", 42.9252); + REQUIRE(!strcmp(buffer, "+42.9")); + + test::sprintf(buffer, "%f", 42.5); + REQUIRE(!strcmp(buffer, "42.500000")); + + test::sprintf(buffer, "%.1f", 42.5); + REQUIRE(!strcmp(buffer, "42.5")); + + test::sprintf(buffer, "%f", (float)42167); + REQUIRE(!strcmp(buffer, "42167.000000")); +} + + +TEST_CASE("types", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%i", 1234); + REQUIRE(!strcmp(buffer, "1234")); + + test::sprintf(buffer, "%li", 30L); + REQUIRE(!strcmp(buffer, "30")); + + test::sprintf(buffer, "%lli", 30LL); + REQUIRE(!strcmp(buffer, "30")); + + test::sprintf(buffer, "%lu", 100000L); + REQUIRE(!strcmp(buffer, "100000")); + + test::sprintf(buffer, "%llu", 281474976710656LLU); + REQUIRE(!strcmp(buffer, "281474976710656")); + + test::sprintf(buffer, "%b", 60000); + REQUIRE(!strcmp(buffer, "1110101001100000")); + + test::sprintf(buffer, "%lb", 12345678L); + REQUIRE(!strcmp(buffer, "101111000110000101001110")); + + test::sprintf(buffer, "%o", 60000); + REQUIRE(!strcmp(buffer, "165140")); + + test::sprintf(buffer, "%lo", 12345678L); + REQUIRE(!strcmp(buffer, "57060516")); + + test::sprintf(buffer, "%lx", 0x12345678L); + REQUIRE(!strcmp(buffer, "12345678")); + + test::sprintf(buffer, "%llx", 0x1234567891234567LLU); + REQUIRE(!strcmp(buffer, "1234567891234567")); + + test::sprintf(buffer, "%lx", 0xabcdefabL); + REQUIRE(!strcmp(buffer, "abcdefab")); + + test::sprintf(buffer, "%lX", 0xabcdefabL); + REQUIRE(!strcmp(buffer, "ABCDEFAB")); + + test::sprintf(buffer, "%c", 'v'); + REQUIRE(!strcmp(buffer, "v")); + + test::sprintf(buffer, "%cv", 'w'); + REQUIRE(!strcmp(buffer, "wv")); + + test::sprintf(buffer, "%s", "A Test"); + REQUIRE(!strcmp(buffer, "A Test")); +} + + +TEST_CASE("pointer", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%p", (void*)0x1234U); + if (sizeof(void*) == 4U) { + REQUIRE(!strcmp(buffer, "00001234")); + } + else { + REQUIRE(!strcmp(buffer, "0000000000001234")); + } + + test::sprintf(buffer, "%p", (void*)0x12345678U); + if (sizeof(void*) == 4U) { + REQUIRE(!strcmp(buffer, "12345678")); + } + else { + REQUIRE(!strcmp(buffer, "0000000012345678")); + } +} + + +TEST_CASE("unknown flag", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%kmarco", 42, 37); + REQUIRE(!strcmp(buffer, "kmarco")); +} + + +TEST_CASE("misc", "[]" ) { + char buffer[100]; + + test::sprintf(buffer, "%u%u%ctest%d %s", 5, 3000, 'a', -20, "bit"); + REQUIRE(!strcmp(buffer, "53000atest-20 bit")); }