Giter Club home page Giter Club logo

printf's Introduction

A printf / sprintf Implementation for Embedded Systems

Build Status codecov Coverity Status Github Issues Github Releases GitHub license

This is a tiny but fully loaded printf, sprintf and (v)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 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 (floating point), ntoa (decimal) conversion.

If memory footprint is really a critical issue, floating point, exponential and 'long long' support and can be turned off via the PRINTF_DISABLE_SUPPORT_FLOAT, PRINTF_DISABLE_SUPPORT_EXPONENTIAL and PRINTF_DISABLE_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.

2020 announcement

This project is not dead! I just had no time in 2019 for sufficient support, sorry. Within the next weeks, I will have a look to all PRs and open issues.
Thank you all for supporting this project.

Highlights 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 most of the test suite. Therefore I decided to write an own, final implementation which meets the following items:

  • Very small implementation (around 600 code lines)
  • NO dependencies, no libs, just one module file
  • Support of all important flags, width and precision sub-specifiers (see below)
  • Support of decimal/floating number representation (with an own fast itoa/ftoa)
  • Reentrant and thread-safe, malloc free, no static vars/buffers
  • LINT and compiler L4 warning free, mature, coverity clean, automotive ready
  • Extensive test suite (> 400 test cases) passing
  • Simply the best printf around the net
  • MIT license

Usage

Add/link printf.c to your project and include printf.h. That's it. Implement your low level output function needed for printf():

void _putchar(char character)
{
  // send char to console etc.
}

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, ...);
int vsnprintf(char* buffer, size_t count, const char* format, va_list va);

// use output function (instead of buffer) for streamlike interface
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);

Due to general 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!

Streamlike Usage

Besides the regular standard printf() functions, this module also provides fctprintf(), which takes an output function as first parameter to build a streamlike output like fprintf():

// define the output function
void my_stream_output(char character, void* arg)
{
  // opt. evaluate the argument and send the char somewhere
}

{
  // in your code
  void* arg = (void*)100;  // this argument is passed to the output function
  fctprintf(&my_stream_output, arg, "This is a test: %X", 0xAA);
  fctprintf(&my_stream_output, nullptr, "Send to null dev");
}

Format Specifiers

A format specifier follows this prototype: %[flags][width][.precision][length]type The following format specifiers are supported:

Supported Types

Type Output
d or i Signed decimal integer
u Unsigned decimal integer
b Unsigned binary
o Unsigned octal
x Unsigned hexadecimal integer (lowercase)
X Unsigned hexadecimal integer (uppercase)
f or F Decimal floating point
e or E Scientific-notation (exponential) floating point
g or G Scientific or decimal floating point
c Single character
s String of characters
p Pointer address
% A % followed by another % character will write a single %

Supported Flags

Flags Description
- Left-justify within the given field width; Right justification is the default.
+ Forces to precede the result with a plus or minus sign (+ or -) even for positive numbers.
By default, only negative numbers are preceded with a - sign.
(space) If no sign is going to be written, a blank space is inserted before the value.
# Used with o, b, x or X specifiers the value is preceded with 0, 0b, 0x or 0X respectively for values different than zero.
Used with f, F it forces the written output to contain a decimal point even if no more digits follow. By default, if no digits follow, no decimal point is written.
0 Left-pads the number with zeros (0) instead of spaces when padding is specified (see width sub-specifier).

Supported Width

Width Description
(number) Minimum number of characters to be printed. If the value to be printed is shorter than this number, the result is padded with blank spaces. The value is not truncated even if the result is larger.
* The width is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.

Supported Precision

Precision Description
.number For integer specifiers (d, i, o, u, x, X): precision specifies the minimum number of digits to be written. If the value to be written is shorter than this number, the result is padded with leading zeros. The value is not truncated even if the result is longer. A precision of 0 means that no character is written for the value 0.
For f and F specifiers: this is the number of digits to be printed after the decimal point. By default, this is 6, maximum is 9.
For s: this is the maximum number of characters to be printed. By default all characters are printed until the ending null character is encountered.
If the period is specified without an explicit value for precision, 0 is assumed.
.* The precision is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.

Supported Length

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_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

Upon successful return, all functions return the number of characters written, excluding the terminating null character used to end the string. Functions snprintf() and vsnprintf() don't write more than count bytes, including the terminating null byte ('\0'). Anyway, if the output was truncated due to this limit, the return value is the number of characters that could have been written. Notice that a value equal or larger than count indicates a truncation. Only when the returned value is non-negative and less than count, the string has been completely written. If any error is encountered, -1 is returned.

If buffer is set to NULL (nullptr) nothing is written and just the formatted length is returned.

int length = sprintf(NULL, "Hello, world"); // length is set to 12

Compiler Switches/Defines

Name Default value Description
PRINTF_INCLUDE_CONFIG_H undefined Define this as compiler switch (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H) to include a "printf_config.h" definition file
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_DEFAULT_FLOAT_PRECISION 6 Define the default floating point precision
PRINTF_MAX_FLOAT 1e9 Define the largest suitable value to be printed with %f, before using exponential representation
PRINTF_DISABLE_SUPPORT_FLOAT undefined Define this to disable floating point (%f) support
PRINTF_DISABLE_SUPPORT_EXPONENTIAL undefined Define this to disable exponential floating point (%e) support
PRINTF_DISABLE_SUPPORT_LONG_LONG undefined Define this to disable long long (%ll) support
PRINTF_DISABLE_SUPPORT_PTRDIFF_T undefined Define this to disable ptrdiff_t (%t) support

Caveats

None anymore (finally).

Test Suite

For testing just compile, build and run the test suite located in test/test_suite.cpp. This uses the catch framework for unit-tests, which is auto-adding main(). Running with the --wait-for-keypress exit option waits for the enter key after test end.

Projects Using printf

(Just send me a mail/issue/PR to get your project listed here)

Contributing

  1. Give this project a ⭐
  2. Create an issue and describe your idea
  3. Fork it
  4. Create your feature branch (git checkout -b my-new-feature)
  5. Commit your changes (git commit -am 'Add some feature')
  6. Publish the branch (git push origin my-new-feature)
  7. Create a new pull request
  8. Profit! ✔️

License

printf is written under the MIT license.

printf's People

Contributors

cz7asm avatar farrrb avatar leandros avatar mjasperse avatar mpaland avatar phillipjohnston avatar sgoll avatar vgrudenic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

printf's Issues

%g should omit trailing zeros

printf("%g", 0.0001) : 
expected:  "0.0001"
got: "0.000100000" 

printf("%g", 0.00001) : 
expected: "1e-05"
got : "1.000000e-05"

Rename functions and use macros to avoid conflicts with STD

In printf.h you would have something like this:

void x_putchar(char character);
int x_printf(const char* format, ...);
int x_sprintf(char* buffer, const char* format, ...);
int x_snprintf(char* buffer, size_t count, const char* format, ...);
int x_vsnprintf(char* buffer, size_t count, const char* format, va_list va);
int x_fctprintf(void (*out)(char character), const char* format, ...);

Along with a set of macros:

#define vsnprintf x_vsnprintf
#define snprintf x_snprintf
...

This is what tinyprintf does and it is very convenient because it avoids conflicts with the STD printf functions.

I'm actually coming from tinyprintf because it lacks float support and doesn't seem to be updated often.

Bug in # flag

sprintf("%#4x", 0x1234); produces `"0x34"
It is caused by wrong '0x' space compensation when width equals output

BTW: I'm fixing issues reported, no need to work on it immediately

Problems with zero precision

There are some problems with a precision of 0 (an annoying corner case).

The following crash:

  • printf("% .0d", 0); (should output )
  • printf("%+.0d", 0); (should output +)

This is caused by decrementing a 0 len here: https://github.com/mpaland/printf/blob/master/printf.c#L185

printf("%#.0x", 0); is apparently supposed to output an empty string, not 0x. The octal case is correct, though. It seems this was clarified in C11.

`fctprintf` needs void pointer argument

For fctprintf to be more useful, it would be great if it had a void pointer argument to customize what the given callback function actually relates to, e.g. with regard to a specific stream instance.

Right now, only a statically defined function can be passed to fctprintf which must behave identical (bar global variables which would defeat the whole purpose of having the callback) each call.

Think about the following signature and call examples:

int fctprintf(void (*out)(char character, void *user), void *user, const char *format, ...);

struct Stream
{
  // ...
};

void do_out(char character, void *user)
{
  Stream *stream = (Stream *)user;
  // Output character on `stream`.
}

Stream stream_1;
Stream stream_2;

fctprintf(do_out, (void *)&stream_1, "%s", "Hello 1");
fctprintf(do_out, (void *)&stream_2, "%s", "Hello 2");

Incorrect test case

The "float" test suite contains the following assertions:

test::sprintf(buffer, "%.9f", 42.8952);
REQUIRE(!strcmp(buffer, "42.895200000"));

test::sprintf(buffer, "%.10f", 42.895223);
REQUIRE(!strcmp(buffer, "42.895223000"));

The first assertion looks correct, but the second assertion appears to be wrong: the precision is set to 10 and there are only 9 digits after the decimal point.

I've checked this with GLIBC 2.23-0ubuntu10 and indeed, 10 digits appear after the decimal point.

left aligned fields don't obey field field widths

Attaching a test to reproduce and a proposed patch to fix.

Try running the test with linux system printf and the result is:

Print with precison -2:
|    9| |9 | |    9|
|   10| |10| |   10|
Print with precision -12:
|    9| |9           | |    9|
|   10| |10          | |   10|

With your printf:

Print with precison -2:
|    9| |9| |    9|
|   10| |10| |   10|
Print with precision -12:
|    9| |9  | |    9|
|   10| |10 | |   10|

Note the missing padding.

See the attached proposed fix.

printf.patch.txt
test_printf.c.txt

Scawy big unkown symbol UwU

Hii~~

This time the types are all right and pwoper,
but nullptr isn't anything in C.

Can we have NULL pwease?

printf.c: In function 'printf':
printf.c:650:41: error: 'nullptr' undeclared (first use in this function)
const int ret = _vsnprintf(_out_char, nullptr, (size_t)-1, format, va);

Use of sizeof() operator

Repeating yourself is generally bad and can easily lead to bugs when updating software. I suggest to change code in functions like _ntoa_long_long:

char buf[PRINTF_NTOA_BUFFER_SIZE];
....
do { 
...
} while (len < PRINTF_NTOA_BUFFER_SIZE);

to:

char buf[PRINTF_NTOA_BUFFER_SIZE];
....
do { 
...
} while (len < (sizeof(buf) / sizeof(buf[0])));

or if typing twice sizeof is too much, you may do:

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

and then

} while (len < ARRAY_SIZE(buf));

Specially important if this is automotive ready code.

printf("%s", NULL) - crash

Hi!

Some placeholder should be used for NULL %s argument to prevent printf from crash:

      case 's' : {
        const char* p = va_arg(va, char*);
        if (!p) p = "(null)"; // please add this

printf("%#.5o", 06143), printf("%#.0o", 0)

In this case (and wider precision) octal zero should be included in padding instead of adding it

printf("%#.5o", 06143)
expected: 06143
got: 006143

printf("%#.6o", 06143)
expected: 006143
got: 0006143

another partially-related case is

sprintf("%#.0o", 0)
glibc: "0"
got: ""

Not standard on prefix + precision

printf("%#08x %#0.8x\n",0x614e,0x614e);

// currently:
0x00614e 0x00614e

// standard;
0x00614e 0x0000614e

(edited to show same bad behaviour, but with precision <= 9 instead of 10)

Warning: comparing floating point with == or != is unsafe [-Wfloat-equal]

Compiling with GCC using -Wfloat-equal turned on, I get the following warnings:
printf.c:346:18: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
printf.c:363:20: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]

The code does has a compare to 0.5

Any ideas on how to resolve this?
Thank you for creating this wonderful function!

Passing negative precision is broken

An indirect precision (as in .*) is passed as a signed int. But this code interprets it as unsigned. C11 even specifies that negative precision is taken as if the precision were omitted.

Test case: printf("%.*d", -1, 1) should output 1.

need lib : stdio stdarg in the include code

Dear mpaland,
hi, i am using ur printf in Arm process and RiscV lib, to get the "printf " function with mimimum lib file
but i cannot get the stdio and stdarg in your code; so far as too many stdio and starg versions : MS VC6.0 /VC2.0 and so on.
so i'd like to get stdio/stdarg from the your repo in github
thank you!

Why did you rename the functions to have an appended _?

Just curious about the motivation behind the name change (to have functions appended with an underscore, and then redefined without the underscore).

#define printf printf_
int printf_(const char* format, ...);

This caused some problems with my library when I updated. I have a libc which I use for embedded systems, but on host machines I link against the standard printf. With the new naming convention, I can no longer keep the printf.h header included and link against the std library - printf_ is undefined, since the host library defines printf.

C++ header only version

Hi, excellent lib, clean and sober code, really good this is. Now.
I am wondering if you could consider cpp header only version?
Yes, I know that would be a "point-of-no-return" to C ..

Support for %zd

On latest libc's the %zd specifier can be used to print size_t.

Not zero-terminating on overflow

Couple of minor issues I've spotted:

  • snprintf is not zero terminating the buffer if it runs out of bytes, which is potentially very dangerous. Only size - 1 characters may be written
    • there are, in fact, multiple cases of those:
      • inside _vsnprintf, can easily be fixed
      • inside the different ntoa's / ftoa's, potentially a little more annoying to fix
  • snprintf's return value is indicating how much has been written, not how much would've been written (if the buffer length would've been ignored)
  • snprintf cannot be used to determine the required buffer length (that is, calling it with a NULL buffer and size 0)

Here are a couple test-cases to reproduce:

int size = test::snprintf(buf, 10, "hello, world");
REQUIRE(size == 12); // fails, 9 is returned instead

int size = test::snprintf(buf, 3, "%d", 10000);
REQUIRE(size == 2); // fails, 5 is returned and the buffer is overflowed
REQUIRE(strlen(buf) == 2);
REQUIRE(buf[0] == '1');
REQUIRE(buf[1] == '2');
REQUIRE(buf[2] == '\0');

Can you made it more configurable?

Hi,

On small targets I do not use Float, Long and I also do not need a big buffer size. Can you made the code more configurable. I want to use the source as submodule as it is without any changes if possible. So it would be gread if you can add this or something else which solves the problem

// buffer size used for printf (created on stack)
#ifndef PRINTF_BUFFER_SIZE
#define PRINTF_BUFFER_SIZE    128U
#endif

// ntoa conversion buffer size, this must be big enough to hold one converted numeric number (created on stack)
#ifndef NTOA_BUFFER_SIZE
#define NTOA_BUFFER_SIZE      32U
#endif

// ftoa conversion buffer size, this must be big enough to hold one converted float number (created on stack)
#ifndef FTOA_BUFFER_SIZE
#define FTOA_BUFFER_SIZE      32U
#endif

// define this to support floating point (%f)
#ifndef PRINTF_NO_FLOAT_SUPPORT
#define PRINTF_FLOAT_SUPPORT
#endif

// define this to support long long types (%llu or %p)
#ifndef PRINTF_NO_LONG_LONG_SUPPORT
#define PRINTF_LONG_LONG_SUPPORT
#endif

so no I can use

#include "printf_config.h" 
#include "printf.h"

In the printf config i use

#define PRINTF_BUFFER_SIZE 32
#define PRINTF_NO_FLOAT_SUPPORT
#define PRINTF_NO_LONG_LONG_SUPPORT

many thanks
cheers
mathias

Caveat: requires remainder, divide, and multiply instructions or software implementations

This came from this reddit thread https://web.archive.org/web/20200109232034/https://www.reddit.com/r/RISCV/comments/eme6qb/gcc_error_undefined_reference_to_umodsi3/

If the computer architecture does not include remainder, divide, or multiply instructions or software implementations this does not compile. Such instance would be a processor that only implements rv32I instruction set as seen in the reddit thread.

possible `%g` minor non-compliance

There is minor problem with %g specifier:

from https://www.gnu.org/software/libc/manual/html_node/Floating_002dPoint-Conversions.html#Floating_002dPoint-Conversions

The ‘%g’ and ‘%G’ conversions print the argument in the style of ‘%e’ or ‘%E’ (respectively) if the exponent would be less than -4 or greater than or equal to the precision; otherwise they use the ‘%f’ style. A precision of 0, is taken as 1. Trailing zeros are removed from the fractional portion of the result and a decimal-point character appears only if it is followed by a digit.

printf("%0-15.3g", -42.); :

"-42.0          "
glibc printf:
"-42            "

It seems to me that %g should use shortest representation, precision is only upper limit, # modifier may be used if decimal point is requested (unimplemented now)
(https://stackoverflow.com/questions/30658919/the-precision-of-printf-with-specifier-g)

Floats : out of range

Hi,

What is missing exactly to support double numbers ?

I see the message in the tests :

printf/test/test_suite.cpp

Lines 1016 to 1017 in 21a282a

// out of range in the moment, need to be fixed by someone
test::sprintf(buffer, "%.1f", 1E20);

Maybe for the moment, a message can be added to the caveats in the README :

Double precision is not supported and will result in an empty string.

Thanks !

line feed for printf("\n some text")

Hi,

Today I made at first test on my stm32 board with your lib. First impression works great an compiles without any warning on gnu_arm_gcc 5.x

One usage question... normaly I use :

printf("\nHallo Welt");

if I would like to print something at the beginning of a new line. But with your implementation I do also need some additional line feed - otherwise the line does not start at position 0 on a terminal:

printf("\n\rHallo Welt");

So I am wondering if the _printchar should handles this is the printf implementation responsible for that?

many thanks

Scawy big unkown type UwU

Hi!!

Using your kawaii pwintf impwementation,
the compiler gets all funny about some silly size_t.

printf.h:78:29: error: unknown type name 'size_t'
 int snprintf(char* buffer, size_t count, const char* format, ...);
                                       ^~~~~~

It even dwew a line for us owo
I did some wesearch and it said, there's no sizy-wizy_t in stdarg.h.
Instead, I moved stddef.h fwom pwintf.c to pwintf.h, because
stddef.h is like totally the hood of size_t.

This fixie wixies the pwoblem
xD *starts twerking*

Discard round-to-even code?

Round-to-even in %f is not working correctly, maybe it would be best to simply discard it completely? Opinions?

  unsigned long whole = (unsigned long)value;
  double tmp = (value - whole) * pow10[prec];
  unsigned long frac = (unsigned long)tmp;
  diff = tmp - frac;

The problem is that tmp value is multiplied by pow10.

With %.1f, 0.95:
0.95 is represented as 0.94999999999999996 in binary. This should be rounded down.
But (0.95 - 0) * 10 is 9.5, exactly representable in binary and tie-decision code takes place, rounding to even and outputting "1.0".

It may be possible to fix this double-rounding (at least in some cases), but IMO it's not worth the complexity. It may be simpler to just remove the tie-handling code and live with it

Suggestion: vprintf()

Hello,

I think you could add a vprintf implementation as below. I've done this for my libc, but feel free to incorporate it on your end. (vprintf is required by libc++)

int vprintf(const char *restrict format, va_list va)
{
	char buffer[1];
	return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
}

Add support for other standard length modifiers

The C99 spec defines the following additional modifiers:

hh     A following integer conversion corresponds to a signed char or 
       unsigned char argument, or a following n conversion corresponds
       to a pointer to a signed char argument.
h      A following integer conversion corresponds to a short int or unsigned
       short int argument, or a following n conversion corresponds to a
       pointer to a short int argument.
L      A following a, A, e, E, f, F, g, or G conversion corresponds to a
       long double argument.
j      A following integer conversion corresponds to an intmax_t or
       uintmax_t argument, or a following n conversion corresponds to a
       pointer to an intmax_t argument.
t      A following integer conversion corresponds to a ptrdiff_t argument,
       or a following n conversion corresponds to a pointer to a ptrdiff_t
       argument.

It would be good to support those for people that want a standard-compliant printf replacement.

h and hh should be relatively straightforward, as char and short decay to int in varargs; so they should just be accepted, but do nothing.

printf appends null terminator to formatted output

Hello,
Thank you for the fantastic work.
Should printf append a null terminator at the end of the formatted output? The standard seems to be unclear about this. The only statement I could find in the c11 standard regarding this is "The fprintf function returns when the end of the format string is encountered." [§7.21.6.1.2] (according to the standard, printf is equivalent to fprintf except for the first argument).

Some ideas / notes

There are some possible optimizations (at least for ARM CPU):

Only 4 arguments are passed in registers, remaining arguments has to be pushed on stack and stack frame may be created


out,buffer, maxlen are passed around a lot. This can be replaced by passing

struct out_object {
   void  (*fn)(out_object * out, char character, size_t idx);
}

struct out_object_buffer {
  struct out_object_base base;
  char* buffer;
}

static void out_buffer(out_object * out, char character, size_t idx) 
   struct out_object_buffer *self = (struct out_object_buffer*)out;
   self->buffer[idx] = character;
}

struct out_object_buffer_n {
   struct out_object_base base;
   char* buffer;
   size_t maxlen;
}

static void out_buffer_n(out_object * out, char character, size_t idx) 
   struct out_object_buffer_n *self = (struct out_object_buffer_n*)out;
   if(idx < self->maxlen)
      self->buffer[idx] = character;
}

In code, call out->fn(out, c, idx);. fn is only single memory fetch and first argument is single register move)

idx can be hidden in buffer object too (increment buffer in object, keep maxlen as pointer to end of buffer). This will avoid all idx handling in printf code, which must make no difference (idx must be incremented after each out call or _out_fct won't work)

BTW: inline makes no sense for _out_* functions, address for function is taken, so no inlining is possible unless _vsnprintf is inlined too


flags, base, prec, width, possibly negative (in flags?)may be packed into structure and passed by pointer. This will only need some care when this structure is modified.


There is subtle bug in _out_char - printf("X%cX", '\0'); will emit only 2 characters instead of three. Special-casing termination (possibly even as extra function pointer in out_object will handle this, it will improve code efficiency a bit (1 skipped test for each character) and as a bonus it will make it possible to implement buffering output function (flush on chunk size or end of format)


Another nice optimization would be to replace character output with write()-like function. This will considerably limit number of through-pointer calls and save some instructions (test buffer size only once per out call, then memcpy [possibly optimizing copy to use word access])
All that is necessary is to emit *format only when % is encountered and implement in-place reversing in out_rev and possibly use const char pad0[8]="00000000"; const char padSpc[8]=" "; for optimized padding.



I can (and probably will) make most of the changes, are you interested in merging them?

Digit missing from negative numbers in certain cases

Hi, it seems that when the width specifier matches the number of digits in a negative number, one of the digits gets removed from the output, i.e.

printf_ ("%2d", -123);   // --> prints -123 (ok)
printf_ ("%3d", -123);   // --> printf -23 ('1' is missing)
printf_ ("%4d", -123);   // --> printf -123 (ok)

The same issue happens with ("%1d", -1) or ("%2d", -12), for example.

The point where this happens is this line:

// when len == width, removes the last digit in the inverted output, 
// before writing the `-` character
if (len && (len == width) && (negative || (flags & FLAGS_PLUS) || (flags & FLAGS_SPACE))) {
    len--;
}

snprintf() and vsnprintf() return value comment

Hi, just a tiny remark: the comment for snprintf/vsnprintf seems to be incorrect:

(returns) The number of characters that are WRITTEN into the buffer, not counting the terminating null character. If the formatted string is truncated the buffer size (count) is returned

The description in the readme file is ok:

Anyway, if the output was truncated due to this limit, the return value is the number of characters that could have been written. Notice that a value equal or larger than count indicates a truncation.

I made a quick update so I'll place a PR (I didn't find the -1 being returned from anywhere in case of errors, so I didn't mention it explicitly).

Scawy big no functionality at all UwU

Senpai,

your little kawaii pwintf impwementation is getting out of contwol.
Pwease have a look at this:

The pwintf function:
int printf(const char* format, ...)
{
  va_list va;
  va_start(va, format);
  const int ret = _vsnprintf(_out_char, NULL, (size_t)-1, format, va);
  va_end(va);
  return ret;
}

The _vsnpwintf function:
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
{
  unsigned int flags, width, precision, n;
  size_t idx = 0U;

  if (!buffer) {
    // use null output function
    out = _out_null;
  }
...

Since we pass null fow the buffer, the output function is being set to _out_null,
which doesn't do anything. Pwintf has ceased functioning entirely.

Oh no!

fctprintf & null termination

In using the fctprintf printf function, I notice that the NULL terminator is passed to the out function. In my experience, printing into a buffer should result in a NULL-terminator, but printf itself does not print the NULL byte.

My personal opinion is that fctprintf should match the printf behavior (updated in #19). Curious about your thoughts on the matter.

%p format

For consideration: Maybe it would be useful to change %p format to be compatible with libc:

In the GNU C Library, non-null pointers are printed as unsigned integers, as if a ‘%#x’ conversion were used. Null pointers print as ‘(nil)’. (Pointers might print differently in other systems.)

Or make this compile-time configurable ...

hexa and 0

Hi,

It seems that printf("%#x", 0) outputs 0 instead of 0x0.

Is that normal ?

Thanks !

Return value off by one

The return value should return the number of bytes written, without the terminating zero (as mentioned by the printf spec and your source code comments).
Although, the following returns 10, instead of 9:

char buf[10];
int size = snprintf(buf, 10, "hello, world");
/* size is 10, but should be 9. */

floating point zero handling

When printing "%e" or "%g" with zero as argument, 0e-308 is assumed instead of zero

printf("%e", 0):
expected: "0.000000e+00"
got: "0.000000e-308"

printf("%g", 0):
expected: "0"
got: "0.000000e-308"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.