Author Topic: Best thread-safe "printf", and why does printf need the heap for %f etc?  (Read 16027 times)

0 Members and 1 Guest are viewing this topic.

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #75 on: August 18, 2022, 02:28:27 am »
So let me see if I understand the "problems" with printf():

  • The whole varargs thing with different types is dangerous (fixed in modern compilers via type checking the arguments against the format?)  And usually inefficient.
  • Bloat due to runtime selection of format - binary must include code for all possible types, whether or not they're used.
  • Output is permitted to overflow field widths.
  • Floating point output takes lots of RAM (common to all "correct" floating point output functions in all languages?)
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6231
  • Country: fi
    • My home page and email address
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #76 on: August 18, 2022, 05:14:10 am »
So let me see if I understand the "problems" with printf():
Plus, the format is fixed.  You cannot do your own extensions to it (except by prefixing or suffixing format specifiers that look like ordinary printable content).

As a practical and relevant example, take a look at the format and format_arg function attributes.

If the formatting string is a constant, the compiler may be smart enough to optimize this and generate code that would essentially look like the manual, separate function for each part of the format, but I'm not too sure about that.
I've never seen anything like that at all.  What most C compilers do do, however, is change printf() and fprintf() calls with a formatting string without conversions into fputs(), but that's it.

That's the kind of thing I had in mind when I said you'd just reinvented printf.
What an odd way to define "reinvent". :o

In that other thread, I explained when formatting strings are useful (localization).  It is not about ease of use, it is about providing useful functionality.

If you replace printf() with an interface that allows you to register a formatting function for a stack trace or processor state dump by just writing it as a function with a specific signature, and then use that formatter as part of your formatting strings everywhere in your firmware/application, is it reinventing?

For one, the printf() formatting specification cannot support that.  The interface you call "reinvented" can support that in plain ANSI/ISO C89.  With ISO C99 or later and ELF-compatible object formats, you can make it much more powerful.

I think you just do not think there is any problem in the printf() interface, and believe it is someone elses job to fix any implementation issues in it if anyone does have a problem.  That is a fallacy, because this thread exists: there are problems that cannot be adequately solved within the existing printf() family of functions.



Now, quite a few posts in this thread have pointed out that it is perfectly possible for a printf() to be thread-safe, and that it is not necessary for printf() to require heap.  It has also been shown that an implementation printing floating-point numbers can often –– given the formats and values typically printed –– work with very little run-time temporary RAM use (stack!); but, depending on the numerical value, the conversions can require well over a hundred bytes of stack space.

Let's say you make a variant of printf() that breaks the standard, and enforces explicit field widths.  That is, if you tell it to print say %+20.9f (±ddddddddd.ddddddddd), and the value cannot fit in that, it will return an error.  To make sure your floats don't exceed the valid range, you do
    if (val < -999999999.999999999f) val = -999999999.999999999f;
    if (val > +999999999.999999999f) val = +999999999.999999999f;
and then wonder why you still get errors.  (The reason is that 999999999.999999999f is the same as 1000000000.0f, and even the same as 1000000032.0f.)

Thus, the best an implementation could do, is support only a subset of floating-point formatting, specifically those with fixed width (even if it the output is not padded to that width) so that it can clamp any values outside that width to the maximum value (or an overflow string), so that everything it does support, can be done with strict, minimal stack use limits, in a re-entrant, thread-safe manner.

The goddamn annoying thing with that is that you will be reimplementing printf() from scratch, and instead of fixing the problems and giving the users of the function new ways to solve the problems that lead to this reimplementation in the first place, you'll be just patching issues with spit and bubblegum.

That is much, much worse than "reinventing" (replacing) an interface; I'm talking from experience here.  Not only are you not really fixing anything, just papering over the problems that halt the current project –– while doing at least the equivalent amount of work a replacement would need ––, but you will end up having to maintain and tweak it for years to come because none of the users of said bubblegum-fixed-printf() will be happy with the tradeoffs you chose.
It is exactly why developers who are stuck at papering over problems like that, instead of fixing them, are either sociopaths or burn out.

The above should explain why I say "fuck no" to "just fix printf()", and instead reach for something completely different.  printf() is a hammer, a tool.  Replacing it with a laser engraver is not reinventing a hammer, it is replacing one tool with another that is designed to be better at the task at hand (if you know what you're doing; not all developers do).
 
The following users thanked this post: DiTBho

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #77 on: August 18, 2022, 09:12:08 am »
Precisely! The problem isn't printf

the problem *IS* printf from its grungy interface down

I use 3 or 4 different printf implementations in my embedded projects depending on formatting features needed.

yeah, like those who like putting fresh paint on grungy railings and call it "well done job" ain't it?

Having basic type specific print functions is the worst idea ever. I've seen several people do that and it always ends in a mess.

oh, really?

It's not basic-type, it's show_${datatype} for *every single type*, including your typedef.
Everything that needs a format MUST have its format_datatype.

With Ada we have been doing show_${datatype} for years for things that must pass DO178B certifications, nobody has never claimed shit out of that.

In the end the C library designers where pretty clever

Clever, sure it's so clever that it’s incredibly easy to get undefined behavior, which, worse still, are quite a few cases of undefined behavior usually produce innocuous looking results, so it’s fairly common for bugs to exist for years before somebody tests under the right circumstances necessary to demonstrate the bug.
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #78 on: August 18, 2022, 09:34:24 am »
The whole varargs thing with different types is dangerous (fixed in modern compilers via type checking the arguments against the format?)  And usually inefficient

yup, when you design a language with pure interfaces in mind, you will find it *VERY* annoying that you have to waste time on type-checking the arguments against the format.

It's all wrong from the design point of view, and it's like telling everyone "stepping on shit brings good luck".

So you have what when you step on shit with Gcc v11? A verbose warning message, telling you messed up something in a printf?

        sorry, you stepped on shit at line 59310294231, you wrote "%s" but argument 2938109 is uint32 not a string

It's sooooooooooooo dangeroussssssssssssss, you could print the whole memory until putS will reach '\0', but don't worry Gcc keeps getting better an it will catch these bugs ... most of times,  and if not, well ...  anything that can go wrong will go wrong, what do you want? don't blame the compiler if step on shit, human!


  • Bloat due to runtime selection of format - binary must include code for all possible types, whether or not they're used.
  • Output is permitted to overflow field widths.
  • Floating point output takes lots of RAM (common to all "correct" floating point output functions in all languages?)

yes, yes, yes

plus, it misses decent support for fixedpoint, and - talking about my last project - the support for complex numbers requires patches an dirty hacks.
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26878
  • Country: nl
    • NCT Developments
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #79 on: August 18, 2022, 09:55:06 am »
Plus, the format is fixed.  You cannot do your own extensions to it (except by prefixing or suffixing format specifiers that look like ordinary printable content).
You can. Just write your own extension into printf. Your own printf will take presedence over the one supplied by the C library.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6820
  • Country: va
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #80 on: August 18, 2022, 10:09:58 am »
That's the kind of thing I had in mind when I said you'd just reinvented printf.
What an odd way to define "reinvent". :o

In that other thread, I explained when formatting strings are useful (localization).  It is not about ease of use, it is about providing useful functionality.

If you replace printf() with an interface that allows you to register a formatting function for a stack trace or processor state dump by just writing it as a function with a specific signature, and then use that formatter as part of your formatting strings everywhere in your firmware/application, is it reinventing?

'Reinvent' doesn't necessarily mean 'the exact same thing'. However, in the sense I used it, it was the single wrapper function vs many lines of multiple functions that I was focusing on.
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #81 on: August 18, 2022, 10:24:58 am »
You can. Just write your own extension into printf. Your own printf will take presedence over the one supplied by the C library.

I can do a lot of things, including something like this
(it's the fist step, NominalAnimal's solution will be the step++)

Code: [Select]
void cplx_fx1616_eshow
(
      p_char_t prologue, /* it can be empty, "" */
      p_cplx_fx1616_t p_data,
      p_char_t epilogue  /* it can be empty, "" */
);

with myC, there is a mechanism to auto-detect the datatype, therefore

Code: [Select]
void eshow
(
      p_char_t prologue,
      p_autotype p_data, /* C11 offers "_generic", it's different, it works at compile-time, but useful */
      p_char_t epilogue
)
{
      switch(type_of(p_data))
      {
            ...
            case p_cplx_fx1616 /* the auto-type is passed as p_datatype */
            {
                  void cplx_fx1616_eshow(prologue, p_data, epilogue);
            }
            ...
      }
}

so I can write this

        cplx_fx1616_t C;
        p_cplx_fx1616_t p_C;

        p_C = get_address(C);

        C'let(p_C, "1.1 i1.1"); /* it will invoke cplx_fx1616_let */
        C'pow(p_C, p_C); /* it will invoke cplx_fx1616_pow */
        C'eshow("X^X = ", "\n"); /* it will invoke cplx_fx1616_eshow */
                     or alternatively
        eshow("X^X = ", p_C, "\n");


I successfully used this approach for matrix computations, LUP decompositions, and complex number algebra.

C++ will look similar, C needs to be more verbose, but you can adapt it to C11 at least for the show wrapper.
« Last Edit: August 19, 2022, 08:21:10 am by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6231
  • Country: fi
    • My home page and email address
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #82 on: August 18, 2022, 11:25:02 am »
That's the kind of thing I had in mind when I said you'd just reinvented printf.
What an odd way to define "reinvent". :o

In that other thread, I explained when formatting strings are useful (localization).  It is not about ease of use, it is about providing useful functionality.

If you replace printf() with an interface that allows you to register a formatting function for a stack trace or processor state dump by just writing it as a function with a specific signature, and then use that formatter as part of your formatting strings everywhere in your firmware/application, is it reinventing?

'Reinvent' doesn't necessarily mean 'the exact same thing'. However, in the sense I used it, it was the single wrapper function vs many lines of multiple functions that I was focusing on.
The dictionary definition does imply 'substantially the same thing', though.

Anyway, do note that current standard/base C libraries do not expose those sub-functions at all, only the topmost string-formatting interface.
Thus, even if you bolt on top a string-formatting interface, it is substantially different from printf(), exactly because it is all about those sub-formatters, and letting the "end-user" implement their own if they wish –– and at minimum, set their own limits (like float precision and range) to those formatters, making the big difference here.

You can. Just write your own extension into printf. Your own printf will take presedence over the one supplied by the C library.
I can do a lot of things, including something like this
If you write a replacement, you're not extending anything.  You're just replacing something with something else.

If you implement a printf() that uses a different formatting than that specified in the C standard, the compiler won't see it your way, and will complain.  (Of course, you can silence those complaints.)  You cannot extend the formatting by adding a new feature, unless it is reinterpreting the meaning or intent of an already supported feature, because again, the compiler won't see it your way, and will complain.  You can replace it with something else, but even if it is almost exactly like the standard C printf formatting string, the compiler will still complain when it sees things that differ from the standard – or you drop all support for the compiler to check the formatting at all.

Therefore, no extension is possible: only replacement.
 

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26878
  • Country: nl
    • NCT Developments
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #83 on: August 18, 2022, 11:29:04 am »
If you implement a printf() that uses a different formatting than that specified in the C standard, the compiler won't see it your way, and will complain.
GCC seems to have already thought about that (see function attributes):

format (archetype, string-index, first-to-check)
    The format attribute specifies that a function takes printf, scanf, strftime or strfmon style arguments which should be type-checked against a format string. For example, the declaration:

              extern int
              my_printf (void *my_object, const char *my_format, ...)
                    __attribute__ ((format (printf, 2, 3)));
             

    causes the compiler to check the arguments in calls to my_printf for consistency with the printf style format string argument my_format.

    The parameter archetype determines how the format string is interpreted, and should be printf, scanf, strftime or strfmon. (You can also use __printf__, __scanf__, __strftime__ or __strfmon__.) The parameter string-index specifies which argument is the format string argument (starting from 1), while first-to-check is the number of the first argument to check against the format string. For functions where the arguments are not available to be checked (such as vprintf), specify the third parameter as zero. In this case the compiler only checks the format string for consistency. For strftime formats, the third parameter is required to be zero.

    In the example above, the format string (my_format) is the second argument of the function my_print, and the arguments to check start with the third argument, so the correct parameters for the format attribute are 2 and 3.

    The format attribute allows you to identify your own functions which take format strings as arguments, so that GCC can check the calls to these functions for errors. The compiler always (unless -ffreestanding is used) checks formats for the standard library functions printf, fprintf, sprintf, scanf, fscanf, sscanf, strftime, vprintf, vfprintf and vsprintf whenever such warnings are requested (using -Wformat), so there is no need to modify the header file stdio.h. In C99 mode, the functions snprintf, vsnprintf, vscanf, vfscanf and vsscanf are also checked. Except in strictly conforming C standard modes, the X/Open function strfmon is also checked as are printf_unlocked and fprintf_unlocked. See Options Controlling C Dialect.
format_arg (string-index)
    The format_arg attribute specifies that a function takes a format string for a printf, scanf, strftime or strfmon style function and modifies it (for example, to translate it into another language), so the result can be passed to a printf, scanf, strftime or strfmon style function (with the remaining arguments to the format function the same as they would have been for the unmodified string). For example, the declaration:

              extern char *
              my_dgettext (char *my_domain, const char *my_format)
                    __attribute__ ((format_arg (2)));
             

    causes the compiler to check the arguments in calls to a printf, scanf, strftime or strfmon type function, whose format string argument is a call to the my_dgettext function, for consistency with the format string argument my_format. If the format_arg attribute had not been specified, all the compiler could tell in such calls to format functions would be that the format string argument is not constant; this would generate a warning when -Wformat-nonliteral is used, but the calls could not be checked without the attribute.

    The parameter string-index specifies which argument is the format string argument (starting from 1).

    The format-arg attribute allows you to identify your own functions which modify format strings, so that GCC can check the calls to printf, scanf, strftime or strfmon type function whose operands are a call to one of your own function. The compiler always treats gettext, dgettext, and dcgettext in this manner except when strict ISO C support is requested by -ansi or an appropriate -std option, or -ffreestanding is used. See Options Controlling C Dialect.
« Last Edit: August 18, 2022, 11:31:38 am by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6820
  • Country: va
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #84 on: August 18, 2022, 11:36:45 am »
Quote
The dictionary definition does imply 'substantially the same thing', though.

Anyway, do note that current standard/base C libraries do not expose those sub-functions at all, only the topmost string-formatting interface.
Thus, even if you bolt on top a string-formatting interface, it is substantially different from printf(), exactly because it is all about those sub-formatters, and letting the "end-user" implement their own if they wish –– and at minimum, set their own limits (like float precision and range) to those formatters, making the big difference here.

I shall try to be clearer and specific. This lead from:

drop printf()

precisely!

it's simpler to have a show() method for each datatype.
- it consumes less resources
- it's less error-prone
- it's embedded-friendly
- it can be easily customized on demand

And from that I presumed he meant to do the kind of thing that I showed as:

Code: [Select]
puts("blah ");
show_type(val);
puts("blah blah: ");
show_type(stuff);
puts("\r\n");

And that looked daft to me. It would be better with a wrapper function to hid all that, so you could easily change the format string (or make it completely different). With a printf-alike wrapper you can change everything just by changing the string, which seems a natural way to progress. You start with a call, then some more and pretty soon you think "I should shove all that in a function".

Hence I suggested you've just reinvented printf. Not THE printf, but a printf-style function.

In that sense, even thought I disagree with the dictionary definition you've found, that would indeed be a pretty much exact replica of a printf style function, but it wouldn't be printf. That's as opposed to spelling out each string and value function each time, as illustrated above.

Hope that clears it up and we can move on.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6231
  • Country: fi
    • My home page and email address
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #85 on: August 18, 2022, 11:38:45 am »
If you implement a printf() that uses a different formatting than that specified in the C standard, the compiler won't see it your way, and will complain.
GCC seems to have already thought about that (see function attributes):
Nope.  What you can do with the format and format_arg function attributes, is to tell the C compiler that your own function uses the exact same string format specification than printf()/scanf()/strftime()/strfmon().

It does not let you add or change the string format specification.  You cannot extend it; you can only replace it wholesale with something else, and lose the support of the compiler in checking that it matches the arguments to your function.

You'd know this, if you'd ever used any of this in practice.  Instead, you're just wasting peoples time by making incorrect claims about things you know nothing about.  Please do something useful instead.
 

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26878
  • Country: nl
    • NCT Developments
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #86 on: August 18, 2022, 11:54:05 am »
It looked like a sensible extension at first glance. Maybe some compilers other than GCC offer such a function. But in the end it is just compiler checking which basically is an extra service to the programmer. It is what it is. Still, following what is established as a standard is way way better than coming up with something else which is non-standard. You also need to think about the fact that C certainly has it's warts and some programming problems are better solved using a different language rather than trying to contort C (and the supplied libraries) into something it isn't. The latter will only get ugly.
« Last Edit: August 18, 2022, 12:09:09 pm by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #87 on: August 18, 2022, 12:11:15 pm »
The latter will only get ugly.

appearing ugly-code is a matter of personal tastes.
being shitty because full of bugs is a matter of wasting human resources.

The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26878
  • Country: nl
    • NCT Developments
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #88 on: August 18, 2022, 12:23:17 pm »
The latter will only get ugly.

appearing ugly-code is a matter of personal tastes.
It is not about ugly code but creating an unmaintainable mess. 'I can do this better' and 'not invented here' is strong in some people while their documentation skills are not. That leaves a pile of code that is costly to maintain and the company is often better served by rewriting it in a way people actually understand what is going on following accepted language & library standards. Again, if resilience and reliability are a concern, then C is not the best language to start with as it takes quite a bit of experience & effort to write software in C that is hardened against faults & attacks.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6231
  • Country: fi
    • My home page and email address
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #89 on: August 18, 2022, 12:42:56 pm »
I shall try to be clearer and specific.
Thank you for the effort; I now see your point.

Code: [Select]
puts("blah ");
show_type(val);
puts("blah blah: ");
show_type(stuff);
puts("\r\n");
And that looked daft to me. It would be better with a wrapper function to hid all that, so you could easily change the format string (or make it completely different).
Sure; that exact interface would be utterly daft.

You wouldn't even need a wrapper function to hid all that, because a preprocessor varargs macro could split each parameter to a separate function call like expression, which the _Generic macro facility can expand to individual function calls based on the type of the argument.  In other words, letting the programmer write
    show("blah", val, "blah blah: ", stuff, "\r\n");
and have the preprocessor expand it into the code you quoted.

I thought I explained this in #67, where I said
Or, put more simply, the proper interface to formatting functions is not just show_type(variable); it is
    status = format_type(destination, value, options);
and it is exactly the options part that requires a non-printf interface.
(Requires a non-printf interface, because there is no way to augment/change/add to the printf string specification to support additional options, except by placing them outside the formatting specifiers like the Linux kernel does – it adds all kinds of variants for modifying how pointer types are shown –, and that seems more confusing and harder to maintain than any of the alternatives to me.)

The destination is a pointer to a structure that describes the buffering available for formatting, as well as options for flushing the buffer if it is not large enough; value is the value to be formatted, compatible with type, and options specifies the formatting options, including range limitation for floats, number of decimal digits, whether the output is padded or not, whether the output should clamp values outside the allowed range or error out, and so on, whatever the formatter supports.

This, too, can be made easier to use via preprocessor macros and _Generic, so that an uniform interface similar to
    status = format(destination, value);   or
    status = format(destination, value, options);
can be used; the former being shorthand, allowing omitting an unneeded NULL pointer for default options.

The overarching _Generic macro can be exposed in a header file, and omitted if the user wants to override it with a different one that supports a different set of available types, even on a file-by-file basis.  Note how this immediately lets the linker omit functions not used for formatting, giving developers in very tightly constrained situations very fine-grained control over all this.  I can imagine even having a small subset of interrupt-safe formatters in specific situations.

The format-string formatter, is then an alternative interface to the underlying formatting functions.  It could be for example
    status = format_using(destination, formatting-string, &value1, &value2, &value3);
where formatting-string is a string literal or string variable, containing both string data to be emitted and formatting specifications (including (optional) options in string form), or even
    status = format_using(destination, formatting-string, &value1, &value2, &value3, &options1);
if the formatting-string options part for the conversion of the first value says to take the fourth variadic argument as the options for it.

The main reason for one wanting such a format-string formatter is localization and multi-language support.

Hence I suggested you've just reinvented printf. Not THE printf, but a printf-style function.
Okay, I understand.

In a real sense, anything in C that takes a formatting string and optionally additional values to be formatted, is more or less printf-style; the important bits are in the details.  Anything that simply replicates the functionality of printf/scanf/etc. is "reinventing the wheel", and daft, yes.

Here, the key point is the individual formatting functions, with the "printf-style function" just being the highest-level abstraction, not the core interface.
The small individual-type formatting functions are an absolutely crucial detail, because everything builds on top of them: and the C printf interface just isn't designed to work this way at all.  (I guess one has to try it and see how futile it is!)

Note, in particular, how I defined an example of such a formatting string specification in a way that allows trivial parsing of the formatting string, without having to understand the exact details of the format.  I suspect even the options thing itself should be a structure with a pointer to a custom/private binary structure describing the formatting, a pointer to the C string specifying the formatting options if formatting-string interface is used, and a type identifier.  This kind of spec makes the formatting language extensible, even at runtime, with very little overhead.

Hope that clears it up and we can move on.
Indeed it does.  Thank you for spending the effort, I do appreciate it.  :-+
 
The following users thanked this post: PlainName

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #90 on: August 18, 2022, 01:32:37 pm »
It is not about ugly code but creating an unmaintainable mess.

WHICH mess?
I wrote 15 libraries this way, no mess at all.

Code: [Select]
procedure Test is

   subtype Index is Positive range 95 .. 1223;

   procedure Put_Line ( I : in out Index; Name : String; Phone : Natural; Address : String; T : in out Time ) is
   begin
      Put (I, Index'Width);
      Put (": ");
      Put (Head (Name, 10, ' '));
      Put (" | ");
      Put (Tail (Phone'Img (Phone'Img'First + 1 .. Phone'Img'Last), 13, '0'));
      Put (" | ");
      Put (Head (Address, 20, ' '));
      Put (Year (T), Year_Number'Width);
      Put ("-");
      Put (Month (T), Month_Number'Width);
      Put ("-");
      Put (Day (T), Day_Number'Width);
      I := Positive'Succ (I);
      T := T + Duration (60 * 60 * 24 * 3);
      New_Line;
   end;
(Ada)

Code: [Select]
   95: Ashley     | 0001033438392 | Wellington, New Zeal 2015-  5- 24
   96: Aloha      | 0001087651234 | Hawaii, United State 2015-  5- 27
   97: Jack       | 0001082840184 | Beijing, China       2015-  5- 30
 1220: Ashley     | 0001033438392 | Wellington, New Zeal 2015-  6-  2
 1221: Aloha      | 0001087651234 | Hawaii, United State 2015-  6-  5
 1222: Jack       | 0001082840184 | Beijing, China       2015-  6-  8

is it mess?  :o
« Last Edit: August 18, 2022, 07:17:51 pm by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #91 on: August 18, 2022, 01:50:59 pm »
that exact interface would be utterly daft.

Show_${datatype} is the simplest, most coherent and fastest replacement to printf.

I wrote several complex libraries this way, and understood why Ada used in Avionics is full of examples where people write firmware exactly this way.

eshow_${datatype} with prologue and epilogue strings proved to be even more practical because most of times it's what you need to log or print.

This stuff misses a generic way to express the format in a parametric way, which is step++, but the pre-processor must be banned and demolished entirely.
« Last Edit: August 19, 2022, 08:22:48 am by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6231
  • Country: fi
    • My home page and email address
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #92 on: August 18, 2022, 03:06:51 pm »
that exact interface would be utterly daft.
Show_${datatype} is the simplest, most coherent and fastest replacement to printf.
It would be a daft interface in C, because it can only operate on one output stream.

The underlying implementation does need some way to specify the output stream (preferably as the first parameter for simplicity), so that the same functions can be used for all I/O, and not have to duplicate the code for each.

Similarly, having an ability to pass the formatting options, even if usually NULL, lets you do Useful Stuff.  Consider the case where in most cases you use one way to format floats, except that in a few cases you use a slight variation.  Do you duplicate the code, or do you pass an options so you can use the same function for both?

Finally, you do want the formatting function to report success or failure, even if it is ignored in most cases.  Just consider how useful it is when debugging you notice that the call reported a failure, for example.

This means you end up with something that is closer to
    status = format_type(destination, value, options);
instead.

If you really want, you can then make trivial wrappers (functions or preprocessor macros) that supply the default destination.  If you want to allow the callers to specify either two or three parameters, with the two-parameter version being equivalent to passing 0 or NULL or some other default as the third parameter, you do need to use the preprocessor.  But the ordinary preprocessor can indeed do this.

Now, for your myC, designed for extremely tightly constrained situations (not hardware capabilities, but strict requirements on the human developers), if you restrict all textual I/O to a single stream, then it isn't daft.  But that is a completely different context, and a different programming language.  The topic at hand is C after all, and not myC.
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #93 on: August 18, 2022, 05:01:41 pm »
It would be a daft interface in C, because it can only operate on one output stream.

the fist attempt directly uses console_out, like printfs uses putch, the second attempt uses a smart buffer

not a problem at all :D

/*
* convert a datatype into safestring
* safestring knows its size and len
* return ans_t
*
* ans.is_valid
*   True  on success
*   False on failure
* ans.error
*   contains the reason of failure
*   - safestring has not enough buffer space
-   - other errors
*/
ans_t form_${datatype}
(
    p_safestring_t   p_safestring, /* target */
    p_char_t         prologue,
    p_${datatype }_t p_data,
    p_char_t         epilogue
);

I wrote it this afternoon, it works fine for me on both C and myC so it doesn't use too weird mechanisms.

The underlying implementation does need some way to specify the output stream (preferably as the first parameter for simplicity), so that the same functions can be used for all I/O, and not have to duplicate the code for each.

I waited because I want to use safestring as output.

Why safestring instead of an array of chars? because safestring is safer and doesn't need further - off by n-bytes from the boundary - checks.

If something, inside the form method tries to write out of the boundary, the function immediately returns an error, and It can be easily programmed, to invoke panic(module, fid, reason) and cpu_halt()!

p_safestring->on_error = NULL; /* it will returns */
p_safestring->on_error = panic; /* callback to panic */

(this is a precise attempt to mimic a functor, with Haskell, Scala, Java, ... it could be done better ...)
« Last Edit: August 19, 2022, 12:30:02 am by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14439
  • Country: fr
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #94 on: August 18, 2022, 05:04:00 pm »
While I favor using fully standard C, avoiding any weird assumption for UBs or relying on specific implementations, etc, as far as the core language is concerned, I have a different view for the standard lib.

C std lib is OKish - kinda - but frankly half of it is crap with clunky and unsafe interfaces, all having this whole legacy baggage.
There are some more recent additions of functions that are not as crappy as the older ones, but still fully inspired by the old ones, so look more like bandaids than anything else. Plus, looks like most developers actually rarely use the newer ones, just like many do not even fully use C99 features - there's enormous inertia there and lack of knowledge.

I have no problem using my own functions when it's more convenient/faster/more secure/etc. Now of course it depends on the platform and the project to some level. I do use printf() and the like for desktop applications/tools. Rarely on MCUs.

Among the rare functions that I use as is in almost all cases, there are memset(), memcpy(), memmove()... which happen to be compiled as inline code relatively cleverly in most cases.
« Last Edit: August 18, 2022, 05:14:08 pm by SiliconWizard »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14439
  • Country: fr
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #95 on: August 18, 2022, 05:11:43 pm »
If the formatting string is a constant, the compiler may be smart enough to optimize this and generate code that would essentially look like the manual, separate function for each part of the format, but I'm not too sure about that.
I've never seen anything like that at all.

Me neither. But in theory, this is fully possible. Compilers already have knowledge about some other std functions (admittedly much simpler) such as memcpy() that allows them to emit smart inline code, they could do the same for printf(). Compiler maintainers probably think that is not worth the trouble.

What most C compilers do do, however, is change printf() and fprintf() calls with a formatting string without conversions into fputs(), but that's it.

Yes. Well, to be more precise, they do this only if the string to be printed ends with a newline character ('\n'), since puts() automatically adds one. So Compilers can only transform printf() calls that have no formatting string *and* are called with a constant string - since compilers must know if the string ends with a newline or not. Then it does remove the newline char when storing the string constant.

 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #96 on: August 18, 2022, 05:44:43 pm »
Similarly, having an ability to pass the formatting options, even if usually NULL, lets you do Useful Stuff. 

this is step++, but I haven't yet found a definitive solution for "options".

I have a prototype that uses a string as option, so similarly to printf. I don't like it too much.

basically I need:
  • padding, "#<ch>,<width>" , useful for { uint64, sint64, uint32, sint32, uint16, sint16, uint8, sint8 }
  • base, { "bbin", "bhex", "bdec" }, useful for { uint64, sint64, uint32, sint32, uint16, sint16, uint8, sint8, p_* }
  • sign, { '|-', '|+' }, useful for { sint64, sint32, sint16, sint8 } + { fp32, fp64, fx* } and their cplx extensions  (sign '-' only when negative? sign always?)
  • integer digits, "i<n>;", useful for { fp32, fp64, fx* }  and their cplx extensions
  • fractional digits, "f<n>;", useful for { fp32, fp64, fx* } and their cplx extensions
(temporary solution)

Consider the case where in most cases you use one way to format floats, except that in a few cases you use a slight variation.  Do you duplicate the code, or do you pass an options so you can use the same function for both?

{ fp32, fp64, fx* } and their cplx extensions should share part of the formatting code if it's possible.

Finally, you do want the formatting function to report success or failure, even if it is ignored in most cases. 

One step per time, I needed to introduce ans_t and safestring, which offers native protection and native reaction on error, this way it's solved. I would like to use monads but it's too complex in C because functions with additional structures is completely unsupported.

Just consider how useful it is when debugging you notice that the call reported a failure, for example.

Technically, safestring could invoke an ICE break, which would stop the CPU and alerts the debugger.
It's why safestring has a built-in callback.

I implemented safestring  in C89 5 years ago, it helped writing the lexer and tokenizer of myC, plus many other libraries, including the last bZtree.

    status = format_type(destination, value, options);
instead.

At the moment

/*
* convert a datatype into safestring
* safestring knows its size and len
* return ans_t
*
* ans.is_valid
*   True  on success
*   False on failure
* ans.error
*   contains the reason of failure
*   - safestring has not enough buffer space
*   - other errors
*
*  options specify how to format data
*   - padding            "#<ch><width>"
*   - base               "bbin", "bhex", "bdec"
*   - sign               "|-", "|+"
*                        sign only when negative?
*                        sign always?
*   - integer    digits  "i<n>;"
*   - fractional digits  "f<n>;"
*  options is ${datatype}-specific
*  if not valid
*     ans.is_valid = False
*     ans.error    = option_not_supported
*/
ans_t format_${datatype}
(
    p_safestring_t   p_safestring, /* target */
    p_char_t         prologue,
    p_${datatype }_t p_data,
    p_char_t         epilogue,
    p_char_t         options
);

use the preprocessor.  But the ordinary preprocessor can indeed do this.

no-way, *for me*pre-processor is banned in C and not implemented in myC.
« Last Edit: August 19, 2022, 08:28:23 am by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6231
  • Country: fi
    • My home page and email address
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #97 on: August 18, 2022, 05:47:52 pm »
What most C compilers do do, however, is change printf() and fprintf() calls with a formatting string without conversions into fputs(), but that's it.
Yes. Well, to be more precise, they do this only if the string to be printed ends with a newline character ('\n'), since puts() automatically adds one.
To be even more precise, the exact call they compile to varies a bit.  If we look at say
    void foo(FILE *out) { fprintf(out, "Foo\n"); }
we'll see that GCC compiles it into a fputs("Foo\n", out); call, and Clang into a fwrite("Foo\n", 1, 4, out); call.

It's not strictly limited to printf() and standard output, it applies to all streams.  puts() is specific to standard output, though.
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3901
  • Country: gb
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #98 on: August 18, 2022, 06:11:17 pm »
puts() automatically adds one

that's part of the reason for the epilogue field  :D

Code: [Select]
void fshow_${datatype}
(
    p_char_t prologue,
    p_${datatype}_t p_data,
    p_char_t epilogue,
    p_char_t options
)
{
    ans_t    ans;
    p_char_t p_buffer;

    p_buffer = p_dev_ss_buffer_out->data;
    ans      = format_${datatype}(p_dev_ss_buffer_out, prologue, p_data, epilogue, options);
    react_on(ans);
    p_dev->context.IO.out_string(p_dev, p_buffer);
}

Written and tested today, fresh code, p_dev can be conIO on my Linux/MacMini-G4 or the serial on my embedded MIPS4++ board.

epilogue can do CR + LF, or just nothing :D
« Last Edit: August 18, 2022, 06:16:50 pm by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14439
  • Country: fr
Re: Best thread-safe "printf", and why does printf need the heap for %f etc?
« Reply #99 on: August 18, 2022, 07:29:07 pm »
So ${datatype} is some kind of macro parameter?
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf