stdlib + MCU haters:  std itoa() uses less resources! - Page 1
Forum section: electronics / general

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 27, 2023, 04:29:29 pm
content: Just had to show this to the old "I never use std libs in MCU" dogs, showing it's not always the case.(Sure enough, the flash usage will blast off when using any std print function)Custom itoa, taken formhere(Can it get more simple than this?):Code:char* _itoa(int val, int base){static char buf;int i=30;for(; val && i ; --i, val /= base)buf = "0123456789ABCDEF"[val % base];return &buf[i+1];}End result was standard itoa using less resources overall. In custom itoa, sdtlib is not used at all.

author: Siwastaja
author_rank: Super Contributor
timestamp: May 27, 2023, 04:54:27 pm
content: You have invented a pretty interesting strawman. stdlib haters? I have never heard anyone criticizing itoa() as big or bloated. Maybe you are confusing itoa() and printf()? I do use my own itoa()-like function, but it's not because of assumed size or resource use, it's usability: my own returns a pointer to the end of the string, allowing easier chaining and thus more maintainable code.(It would make very little sense toduplicateitoa(). Try with fixed base 10 instead - that would bring the code size down a bit; of course assuming base 10 is all you need.)

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 27, 2023, 05:15:22 pm
content: I guess the compiler optimization should already notice that, as the function is called only with base 10?C'mon, we've all seen lots of very polarized duDes here, still thinking we're in the 64-byte ram MCU era!#include <std- STOP YOU FOOL!! Inefficient!! Wasting resources! Make it from scratch! Baremetal only! Waah!!

author: Siwastaja
author_rank: Super Contributor
timestamp: May 27, 2023, 06:25:39 pm
content: Alcohol?Funny rant, but you are fighting against a made-up enemy. Your output would not make you look as ridiculous as it's now if you took a few moments to learn the basic concepts. A few points that would be helpful, but seeing you are a "us - them" direction brain, you are likely to ignore the chance to learn:*bare metalrefers to not using an operating system, such as RTOS. I'm sure your projects are mostly that way. A weird enemy to pick; maybe you think it means something else?* I really can't remember anyone advicing against using the C standard library at all. It's part of the C standard and thus portable. Some particular functions are heavier (e.g. printf) than others (e.g. memcpy), and some are not always relevant on MCUs (e.g. fopen()). But usinganythingfrom C standard library considered harmful? A weird idea. I do rememberNominal Animalcriticizing some C standard library interfaces, in particular file access functions (opendir() etc.), but this isn't very relevant in microcontroller systems.* Recurring critique against some vendor-specific hardware abstraction libraries which abstract hardware poorly, limit the feature set of peripherals and lock in to a single vendor is what I have seen, and it's mostly spot-on. It's your call to make the right choice and you'll be responsible to your customers.* Small RAM and ROM is pretty much still a thing. Very small and cheap gadgets have their place, not only in super inexpensive gadgets, but distributed IO controller nodes etc., too.* Bigger reason than performance* to do things yourself is you get what needs to be done. Using existing work will limit the scope to that provided by said work.*) not to say performance never mattersLast point is important; your opening post is a good summary of this recurring misunderstanding we are seeing, in other words:>I need to do X<Use libxxx, it does X, Y, Z, Å, Ä and Ö, and doing X requires doing Q, W, E, R, T, Y, U, I, O>But I just need to do X so I wrote this 100 LoC piece which does X, why is it wrong?<Geez, libxxx is 170kLoC and developed over tens of thousands of man-hours,why would you do all that from scratch?>No but I'm not doing all that from scratch - guess what, nevermind.Your opening post demonstrates this because youreplicatedthe interface of itoa(), i.e., made another itoa implementation and compared performance to the existing work. Save for some very special cases, such work is not usually fruitful. Instead, you would write something similar (but not identical) to itoa(), but which solves your actual problem better. Thereasonfor that work would not be poor performance of itoa(), but poorinterfaceorfunctionalityof itoa() for solving your particular problem.The most important feature for a successful developer is tocorrectlyidentify when you are solving atruly complex problemwhich already has a solution you can reuse, and when you arenotdoing that.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: May 27, 2023, 06:40:07 pm
content: Nice buffer overrun.ASCII is built intentionally so that this kind of thing is easy: save memory by using digit = value % base + '0', and add 'a'-'0' - 10 (or 'A' if you prefer) when the remainder exceeds 9. Or don't even bother with the subtraction and set the bit(s) directly, since number and alphabet sequences are aligned.You'll also save stack frame size and initialization by setting that string to static const. Always sanity-check your output when optimizing!The one thing an implementation (as opposed to a fixed library blob) does gain is, if you're only using it for one base, the compiler can potentially collect and propagate const params and use multiplies or shift and mask for the arithmetic (if applicable). (Remainder is possible by multiplication, but I think it's not commonly implemented by compilers?)Tim

author: Siwastaja
author_rank: Super Contributor
timestamp: May 27, 2023, 06:43:15 pm
content: Quote from: DavidAlfa on May 27, 2023, 05:15:22 pmNot necessarily, because you wrote a library function replacement, i.e. a function with external linkage for which the compiler needs to emit code which can be called with any argument values, preventing many types of optimizations that would be possible withstaticfunctions. It is still possible the compiler implements two copies of the function one of which is optimized, especially at -O3, but don't count on it. You can easily find out by looking the compiler output.BTW, this is also the reason why library functions can have quite significant CPU time overhead when the functions are short and called in inner loops, over a custom function which can be static (even inlined) and thus optimized by compiler with understanding of argument values used. This is also why compilers offer so calledbuilt-insso when you think you are calling memcpy() (string.h, in libc), the compiler might be inlining a "custom" implementation of memcpy at that spot instead, for performance. This is also a good reason not to reinvent memcpy because compiler probably does better.

author: AVI-crak
author_rank: Regular Contributor
timestamp: May 27, 2023, 08:09:21 pm
content: Quote from: DavidAlfa on May 27, 2023, 04:29:29 pmStop mocking common sense. I tested your code on cortex-m7, cortex-m0, RISC-V (CH32V307VC, CH32V003J4) - the _itoa(a,b) function throws garbage and sometimes crashes the program. On a completely clean project (absolutely bare metal) - _itoa(a,b) gave out garbage for conditional 189 processor cycles (there is hardware division and multiplication). At the last moment, _itoa(a,b) adds 4 bytes to the address of the string.Testing was carried out on the maximum number of uint32_t (4294967295u).My version runs in 98 cycles on cortex-m7.Code:char* u32_char (char* tail_txt, uint32_t value)//40{*tail_txt = 0;uint32_t res, tmp;do{tmp = ((uint64_t) value * 3435973837UL >> 32);res = value + '0';value = tmp >> 3;tmp = value + (value << 2);res -= tmp << 1;*(--tail_txt) = res;}while (value > 0);return tail_txt;};

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 27, 2023, 11:05:41 pm
content: Just as a fun reminder - unless I missed it and someone already said it - but itoa() is actually not part of the standard C library.It's an addition that you may, or may not find along with a given compiler/environment.With GCC on Linux/x86_64, it is not available - I ran into this with some old code, lately!

author: brucehoult
author_rank: Super Contributor
timestamp: May 28, 2023, 12:44:29 am
content: Quote from: DavidAlfa on May 27, 2023, 04:29:29 pmI can't spot the place where you're adding the "-" sign.

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 28, 2023, 03:03:27 am
content: Alcohol? Nope! Why? The bad writing?My phone tends to f** my writing as the keyboard its not set to english, Gboard has certain issues that ruin typing in my native language when enabling multiple languages... So it either doesn't correct anything or does it with garbage.I know it's not a fully implemented itoa!I just needed to show somepositiveintegers up to 16bit size, for only that it works just fine!I've been running a bouncing ball the entire afternoon, the second counter is already 30530 right now.The point was just to show how the most skinny and crippled itoa ever existed would still use more resources than the standard one.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 28, 2023, 03:42:15 am
content: It would pretty much entirely depend on the platform. For MCUs, the std lib is most often implemented by newlib, and even so there are several versions depending on options.For those curious, this is the core of the implementation of itoa() in newlib (it's a cascade of functions as is usual in newlib, but the bulk of the work is this):https://github.com/leaningtech/cheerp-newlib/blob/master/newlib/libc/stdlib/utoa.cAs I mentioned, technically itoa() is not part of the standard C lib and may not be present. If it's not, you'll know why.If you stick to newlib, chances are it'll always be there though.

author: cv007
author_rank: Frequent Contributor
timestamp: May 28, 2023, 04:01:28 am
content: QuoteNot sure how that could be, but we do not know the mcu in use or the particular library version being compared to.Cortex-m newlib itoa/utoa source, along with the skinny and crippled version given-https://godbolt.org/z/qc9co79fvNot sure how the skinny/crippled one could be producing more code unless it was being used inline with multiple uses. No matter which mcu was in use, as all library versions essentially look the same. edit- the ram use would be explained by the static buffer assuming there was no equivalent testing the library version (if using the stack for example, will not show up in ram stats).The bigger problem, aside from the fact that a library replacement is created that doesn't match the library function signature and does not have the same functionality (and it then becomes something different where comparisons become somewhat meaningless), is the use of a static buffer shared by anyone who wants to use itoa. That would be fine up until its not, and the not will be found at some point. There is a good reason the library version takes a char* as an argument.I happen to think in many cases in makes more sense to skip right over the 'minor' formatting league and just use snprintf/printf/equivalent from the start. Once that library is brought in, use it everywhere as it becomes essentially free after the first use. So much easier than the bits and pieces one always ends up with when the original intention was small/light but eventually turned into complex/touch-it-break-it code.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 28, 2023, 04:49:43 am
content: Quote from: cv007 on May 28, 2023, 04:01:28 amIf using the function more than once in the same same file as it's defined, then the compiler will inline it (unless you go with no or basic optimization level) and that would be the main reason why the overall code size would be slightly larger indeed.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 28, 2023, 07:36:37 am
content: I started athreadabout this a couple of years ago. Reply#1includes the signed 16-bit integer to decimal, backwards, via divide-by-ten and modulus, as an example. It also shows how to do it very fast on architectures with fast subtract-with-carry, without relying on multiplication or division at all, as well.While both avr-libc and newlibc includeitoa(), it is not a standard C function at all, so really, stdlib is not exactly involved here.(The actual non-stdio.hstring-to-numeric conversion routines in the standard C library arestrtol(),strtoul(),strtof(),strtod(), and since C99,strtoll(),strtoull(),strtold(),strtoimax(), andstrtoumax(). (There are also corresponding functions for wide character strings.)Unlike e.g.sscanf(), these do report errors in input. These are of course inverse to howitoa()etc. operate; all standard string construction operations to generate a string from arbitrary integers are in<stdio.h>.)_ _ _ _ _There is no reason for the "haters" hyperbole, even in jest, exactly because the standard C library is not an inseparable part of the C language.The C standards specifically define "hosted" and "freestanding" environments, with "hosted" the one most consider "proper C", with all of the standard library features available. "Freestanding" environment is one where the C standard library is not available, and only a subset of header files (basically those provided by the compiler for the target architecture, in practice) are accessible; things like<stdint.h>, for example.That is also the reason for that thread, and my relatively scarce participation in "new programming language" threads. If we understand the development history and pressures involved in the standard C library, instead of starting from scratch, we can simply replace the standard C library with something better and get most of the way there. (Indeed, I have come to suspect that a single base language change regarding pointers and arrays would suffice to let the compiler detect all cases where buffer underrun or overrun is possible. That, combined with a "replacement standard library" using knowledge hard-earned in practice over the last three decades or so, would be a huge leap forward, in my opinion.)That thread I started involved some ideas regarding string construction in very limited or constrained situations, i.e. using minimal resources, exactly like when one is programming microcontrollers, for example. Aside from reserved function names and naming, it is perfectly "standard" C to use the freestanding environment, and implement your own base library. Usually, some things (syscalls, when running under an OS kernel or hypervisor) do require extensions or external implementations or extended inline assembly; I personally favour the last (with GCC and Clang).The same applies to C++ as well, except that C++ leaves almost all of the freestanding environment implementation-defined; and this leads to many freestanding developers to actually use a subset of C++ or mix of C and C++ freestanding environments, especially on Harvard architectures, as the standards have not grown separate address space support and it requires compiler extensions, with Clang supporting them well in both C and C++, but GCC only in C (leading to all of the oddness wrt. strings and flash memory accesses in Arduino, which uses GCC's C++ frontend on AVRs).There is also a thread somewhere about how it is possible to format evenfloat(IEEE 754 Binary32 or Binary64, single ordoubleprecision) exactly correctly (the rounding is annoying!) using very little resources. This is because the<stdio.h>part of the C standard library has never been optimized forperformance; its output is very carefully specified and tuned forcorrectnessinstead. Similarly, it is very simple to parse limited ranges of floating-point numbers in decimal format (exactly correctly), and at least an order of magnitude faster than what standard C library implementations do; exactly because they are designed per the language spec for correctness and not for speed/throughput. Mostly, it is the subnormal numbers and exponential notation (in cases that ends up requiring a bit-correct division by ten) that are the slowest and most resource-hungry to implement; and even they only require a limited-size conversion workspace (unlike most implementations in standard libraries, which use arbitrary-precision math for this). There are even scientific papers in ACM and elsewhere about such conversions...

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 28, 2023, 08:56:59 am
content: i don't think it's inlining it, I'm using Os + flto to pack it as much as possible.The tested MCU is a Cortex M0 HK32F030M, using existing GCC from ST CubeIDE.It only has 16KB flash so size matters!

author: AVI-crak
author_rank: Regular Contributor
timestamp: May 28, 2023, 09:33:32 am
content: Quote from: DavidAlfa on May 28, 2023, 03:03:27 amOnly some of them, and even with a limitation of 16 bits, are commendable.The reminder "do not put kittens in the microwave" is not implemented in your function. Using the "int" input parameter is a very bad idea. On eight-bit MK and 64-bit ARM - the result will be unpredictable. Using division is very stupid, it is the slowest command on all architectures. The execution time of _itoa(a,b) for the number 2147483647 = 223 ticks.Ideologically correct function for int16_thttps://godbolt.org/z/vccEcazhs

author: jnk0le
author_rank: Regular Contributor
timestamp: May 28, 2023, 11:12:25 am
content: the "standard" itoa/utoa is overbloated by design. The only bases that are actually used 99.99% of times are 2,10,16.about newlib:https://github.com/leaningtech/cheerp-newlib/blob/master/newlib/libc/stdlib/utoa.c#LL36C3-L36C64This whole string will be put on the stack every time the function is called.Which is of course slower than even hitting waitstates in .rodata lookups.

author: cv007
author_rank: Frequent Contributor
timestamp: May 28, 2023, 11:45:15 am
content: QuoteWhat the compiler produces is not a secret, so you can see what its doing if you wish. I think your extra flash space used for the simple version is because its using integer division where the library version is using unsigned division (itoa does not do division, its call to utoa does).Create a couple versions, compile and objdump, look for the division functions, the udiv version will be smaller than the div version. If you do not otherwise use both, there will be a difference due to one using udiv the other div.edit- but maybe we should just let it rest so you keep using the better library version.

author: brucehoult
author_rank: Super Contributor
timestamp: May 28, 2023, 12:51:10 pm
content: Quote from: jnk0le on May 28, 2023, 11:12:25 amWhile that is true, the cost of supporting 15 (or even 35) bases instead of 3 is very small.QuoteI doubt that speed of converting integers to strings is very often a concern in embedded uses, even on a 1 MHz 6502 let alone a 24 or 48 or more MHz Cortex-M0. It's probably only updating a small display a few times a second (no point in doing it more often than a human can observe and react to), or writing to a log file where you don't want to be spewing out MBs of text every second. Something like 223 cycles (maybe 5-10 µs) doesn't really need to be reduced, and if it was 10x more probably would not matter.The code size has got to be much more important in most cases.Which makes me think including a 16 (or 36) byte string is pretty crazy.Code:int digitToChar(int digit){int res = digit + '0';if (res > '9') res += 'A'-'0'-10;return res;}Code:adds r0, r0, #48cmp r0, #57ble 1fadds r0, r0, #71:8 bytes of code VSa 16 or 36 byte string PLUS 4 bytes of code PLUS 4 bytes of literal pool = 24 or 44 bytes totalThe pure code solution isn't even significantly slower.

author: Siwastaja
author_rank: Super Contributor
timestamp: May 28, 2023, 01:02:53 pm
content: I agree code size is usually more important than execution speed for visualization / status print functions because it should not be in the timing-critical path.

author: jnk0le
author_rank: Regular Contributor
timestamp: May 28, 2023, 01:59:42 pm
content: Quote from: brucehoult on May 28, 2023, 12:51:10 pmplus the code for memcpy call and related spilling in case of newlib.Code:__utoa:00000420: addi sp,sp,-7200000424: sw s0,64(sp)00000426: sw s1,60(sp)00000428: sw a0,0(sp)0000042a: mv s1,a20000042c: mv s0,a10000042e: li a2,3700000432: auipc a1,0x000000436: addi a1,a1,-814 # 0x1040000043a: addi a0,sp,200000043c: sw ra,68(sp)0000043e: jal 0x70e <memcpy>00000440: addi a2,s1,-200000444: li a3,3400000448: li a4,00000044a: bgeu a3,a2,0x462 <__utoa+66>0000044e: sb zero,0(s0)00000452: li s0,000000454: lw ra,68(sp)00000456: mv a0,s000000458: lw s0,64(sp)0000045a: lw s1,60(sp)0000045c: addi sp,sp,7200000460: ret00000462: lw a0,0(sp)00000464: mv a2,a400000466: addi a4,a4,100000468: add a5,s0,a40000046c: mv a1,s10000046e: sw a2,16(sp)00000470: sw a4,12(sp)00000472: sw a5,4(sp)00000474: jal ra,0x16ac <__umodsi3>00000478: addi a4,sp,200000047a: addi a5,a0,400000047e: add a0,a5,a400000482: lbu a3,-40(a0)00000486: lw a5,4(sp)00000488: mv a1,s10000048a: sb a3,-1(a5)0000048e: lw a3,0(sp)00000490: mv a0,a300000492: sw a3,8(sp)00000494: jal ra,0x1680 <__udivsi3>00000498: lw a3,8(sp)0000049a: sw a0,0(sp)0000049c: lw a4,12(sp)0000049e: lw a2,16(sp)000004a0: bgeu a3,s1,0x462 <__utoa+66>000004a4: lw a5,4(sp)000004a6: add a3,s0,a2000004aa: li a4,0000004ac: sb zero,0(a5)000004b0: sub a1,a2,a4000004b4: bge a4,a1,0x454 <__utoa+52>000004b8: lbu t1,0(a3)000004bc: add a1,s0,a4000004c0: lbu a0,0(a1)000004c4: sb t1,0(a1)000004c8: addi a4,a4,1000004ca: sb a0,0(a3)000004ce: addi a3,a3,-1000004d0: j 0x4b0 <__utoa+144>

author: AVI-crak
author_rank: Regular Contributor
timestamp: May 28, 2023, 02:05:22 pm
content: Quote from: Siwastaja on May 28, 2023, 01:02:53 pmThe number of lines in the program and the size of the firmware have a very weak relationship with each other.I have a library with a total of more than 10k lines, the result of its work takes 142 bytes of function size, and 4 bytes of data. Because I'm very lazy, and I'm too lazy to read the documentation every time.On the other hand, a person writes in the function "take a percentage of a number" - it turns out very compactly, literally one line. But in real code, a slow built-in function is called, sometimes twice.A real programmer must be as lazy as possible: write once - use always and everywhere. You only need to think well once, and do not touch again. You can even forget what's inside - you've already done the work.

author: cv007
author_rank: Frequent Contributor
timestamp: May 28, 2023, 05:28:36 pm
content: QuoteI did create a couple versions to compare (simple code, -Os, and not optimized away), and the library version uses unsigned division and the replacement signed division. The signed division library function takes an extra 200 bytes over the unsigned version(for m0), the libray also brings in memset/memcpy, and library itoa functions (itoa/utoa) are about 130 bytes larger. In the end the 'minimal' version ends up about 46 bytes larger for the simple test example.Its easy to draw the wrong conclusions by changing code and just looking at compiled size only. Its not unusual to change something trivial and end up with a code size that drastically changes. In this case, an obviously smaller function that ends up with larger code means there is something not obvious taking place, obviously.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 29, 2023, 01:14:20 am
content: Quote from: brucehoult on May 28, 2023, 12:51:10 pmI agree.The cases where it does matter is in specific types of devices, that for example parse HPGL (which includes decimal numbers) or G-code, common in 3D printers, vinyl cutters, plasma cutters, and so on. (Many of the purely 2D ones use HPGL.)So, it is notoften, but when you do, it is quite important to have it efficient, correct, and robust. (By robust, I mean error detection and aborting in case of numerical overflows, instead of best-effort parsing like e.g.sscanf()does.)I first encountered this a couple of decades ago, parsing molecular simulation data from PDB (protein data bank) and similar format files; these are text-based files that for example describe the atom positions in a simulation. When you have say a million atoms and a thousand frames, that's at minimum three short billion decimal/scientific notation floating-point numbers, and even at that time standard C library parsing functions became the speed bottleneck, even on bog-standard workstation-grade spinny rust hard drives. Much more so now, with fast SSD drives. So, even in some specific niches on desktop computing, thisisstill an issue. A rare one, granted, but an important one when it happens to oneself. It isn't fun waiting for minutes for simulation data to load, when it can be done in seconds, at I/O speeds.Sure, there are dedicated binary formats, but text-based formats like JSON are still easiest/most reliable to use for data transfer across completely different systems. (I do have added custom binary data file support to a couple of molecular dynamics simulators myself, but even then I had to write translators to/from the common text-based formats, simply because adding common binary file format support, no matter how standard, toallutilities accessing such data is just too much work: there arehundredsof them, and even I used easily dozens for each simulation.)

author: brucehoult
author_rank: Super Contributor
timestamp: May 29, 2023, 03:37:36 am
content: Quote from: Nominal Animal on May 29, 2023, 01:14:20 amThat is text to binary, which is a different problem. As is floating point.Also, I thought we were talking about machines where you have 2 KB or 16 KB of something of flash, and 1 or 2 KB of RAM, not big computers with megabytes if not gigabytes of RAM.

author: Siwastaja
author_rank: Super Contributor
timestamp: May 29, 2023, 04:58:11 am
content: Quote from: brucehoult on May 29, 2023, 03:37:36 amWhat do you mean? What is itoa() if not text-to-binary? EDIT: Oh, I got it, you are just talking about the conversion direction.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 29, 2023, 06:17:24 am
content: Quote from: brucehoult on May 29, 2023, 03:37:36 amHaven't you seen how similar they are?Besides, that entire thread I linked to is exactly about converting numeric data to strings. You just commented on what I described where I initially encountered the problem, years before getting my hands on my first microcontroller (AT90USB1286, Teensy 2++).Even then, like I said, I had to export the numerical data (from binary) to text formats anyway, to use standard tools. So itwasdefinitely the exact same problem on that part.Quote from: brucehoult on May 29, 2023, 03:37:36 amI said, that was the context I first encountered theproblem: standard library functions revolving around string generation and parsing beingslow, as they are designed for correctness for all possible inputs/outputs, and not speed, efficiency, or size.Consider it a note how the problem is not just on tiny machines, but also when dealing with massive amounts of data interchange.While one might think floating-point is magically different, the coordinate system in molecular dynamics simulations tends to be in Ångströms, with at most eight decimals in the typical decimal formats. That is, 99.9% of numbers one encounter are of format['-' ] ['0'-'9']{0,8} ['.' ['0'-'9']{1,8} ]. It differs from integer parsing only in that exact rounding has to be maintained; there are quite a few different ways to do that, even though arbitrary-precision arithmetic is the currently most used / accepted method.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 29, 2023, 06:19:33 am
content: Quote from: Siwastaja on May 29, 2023, 04:58:11 amYou're thinking of the old standardatoi()/atol()functions.itoa(value,buffer,radix)is non-standard binary-to-decimal-text conversion found in avr-libc and newlibc, among some other C standard library implementations.

author: brucehoult
author_rank: Super Contributor
timestamp: May 29, 2023, 11:23:23 am
content: Quote from: Nominal Animal on May 29, 2023, 06:17:24 amIn integers, yes, but only if you regard multiplication as similar to division, which it isn't, especially on a machine such as the Cortex-M0+ which has a 1-cycle multiply but doesn't have a divide instruction at all. That makes parsing numbers from text much much faster than the inverse.In floating point the operations are even more different. While the exact decimal expansion of a binary floating point value can get very long -- one significant decimal digit for every bit after the binary decimal point  -- the number of significant decimal digits you need to output in order to distinguish one floating point number from the adjacent ones (and thus be able to convert the text back to the exact same binary value later) is strictly limited.I've written before about parsing arbitrary floating point numbers, using big (~144 bytes IIRC) but not infinite integers. In extreme cases. But in practice most numbers encountered can be correctly handled by parsing a 53 bit integer and multiplying by a power of ten from a table. 2-1022(about 2.22e-308) has 307 0's after the decimal, followed by 1022 non-0 digits ending with a "5".

author: MK14
author_rank: Super Contributor
timestamp: May 29, 2023, 11:24:28 am
content: Quote from: brucehoult link=topic=378794.msg4883618#msg4883618Not necessarily, let me give you an example.There is some kind of sensing unit (like smart meters (gas/electricity etc), hand-held meters, etc). With an MCU solution, running from batteries, on some kind of low cost MCU.Although it might be able to run at 48 MHz or even 72 MHz etc, it may well be running at 500kHz, or 32,768Hz, maybe even only briefly turning on to make measurements, then going back tosleepmode. To minimise power/battery consumption.Let's say the LCD (for very low power consumption) display is updated every 1 to 5, times a second.If during development, the LCD display sometimes reportedly shows weird results, perhaps every few hours, so maybe around every 10,000 readings. Indicating a possible software (much less likely hardware) bug(s). The developer(s), probably don't want to watch it and wait around for several hours, when the glitch(s) occur.So they may want to use a very high (for battery powered MCUs) speed port (such as the SPI or QSPI), at a greatly speeded up rate (maybe 100,000 times a second, just for the diagnostic debugging test work), to help them spot/diagnose and hence hopefully fix the (otherwise) very rarely occurring glitch.Or they may want to send hundreds of thousands of ADC readings per second (if the hardware is up to it), to a PC, to try and see the glitch occurring.As you stated (in another post), it might have a very small RAM and flash, making direct logging on the MCU infeasible for any length of time.You could argue, that they should use binary to avoid the processing delay. But text is a lot friendlier, and easier to search for faulty/glitchy patterns. Especially if there are separate people/departments/organisations at either end (e.g. An internal software department and external testing and verification services company).So, a text based communication, is much less likely to cause confusion, and tends to be easier to fix and diagnose, if people get things wrong.Therefore the conversion speed, of integer to text (string) format, may need to be as fast as possible.

author: AVI-crak
author_rank: Regular Contributor
timestamp: May 29, 2023, 01:06:33 pm
content: Quote from: MK14 on May 29, 2023, 11:24:28 amWell-written code with explicit indication of variable types is able to work on any device and in any executable environment.In this way it is possible to carry out tests of individual functions on a large machine (PC).

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 29, 2023, 03:22:48 pm
content: Quote from: brucehoult on May 29, 2023, 11:23:23 amOnly if you consider multiplication/division-with-modulus the only way to implement them.I do not, nor does my example in the other thread, which uses subtraction for string construction (and parsing uses addition). I do consider addition and subtraction with integers "similar".I do feel you are nitpicking here. You only pick one small part from my answer, and reply to that, but not to my point at all.In my first message, you replied to just my side note how I first encountered the common greater problem, disputing it as "similar". In my second message you replied to only the first sentence, completely ignoring the note how Ididhave to do integer-to-string conversion anyway.What gives?

author: AVI-crak
author_rank: Regular Contributor
timestamp: May 29, 2023, 06:07:39 pm
content: For very flawed microcontrollers (no multiplication, no division) - there is an algorithm for dividing by 10 by shifts and addition. The general meaning is very simple: first line "multiply" by 0.75, and then slowly "multiply" to the value of 0.8. However, when "multiplying" the least significant bits are discarded at the very beginning of the algorithm, so the result is sometimes less than one.The algorithm works on cortex-m0 faster than all other options, the size is also small.Code:#include "stdint.h"char* u32_char (char* tail_txt, uint32_t value){*tail_txt = 0;uint32_t res, rem;do{res = value >> 1;res += res >> 1;res += res >> 4;res += res >> 8;res += res >> 16;rem = value + '0';value = res >> 3;res = value + (value << 2);rem -= res << 1;if (rem > ('9')){value++; rem -= 10;};*(--tail_txt) = rem;}while (value > 0);return tail_txt;};bug fixed

author: cv007
author_rank: Frequent Contributor
timestamp: May 29, 2023, 06:58:50 pm
content: QuoteThe less optimized conversion may be costing an extra half hour of 'energy'/cpu cycles per year for the normal use.For the high speed diagnostics, it now takes you an extra fraction of a second to get to the 10k reading point.Obviously having real numbers to work with would make for better/concrete answers, but there really is not much there to get excited about for both infrequent and frequent uses of an itoa. Any optimizations made are not bad, it just may not be worth the extra time involved to get there if in the end it makes little to no difference. The readability of code should also enter the equation- having simple/obvious code will have a benefit as opposed to a code base full of special optimized code where the why/how is long forgotten when it needs revisiting in the future.The same can be true for size- if an optimized version of some library function results in a savings of 100 bytes, it probably makes no difference in the end for the 16k mcu.QuoteExcept it may be hard to get right. You showed an earlier version that is different, and now this version does not seem to work-https://godbolt.org/z/sYn95fhPzSo saving some clock cycles on each conversion may not be worth the effort if one has to keep tweaking the code, or if it is easy to get wrong. The simpler version using div/mod, or using something from the library like itoa/snprintf may be worth the cost in terms of speed or size.

author: MK14
author_rank: Super Contributor
timestamp: May 29, 2023, 07:26:39 pm
content: Quote from: cv007 on May 29, 2023, 06:58:50 pmI agree with you, here. Optimising itoa, is just one, of hundreds or even thousands of other optimisations, that the language compiler (C/C++ etc), needs to make, to achieve leading optimisation results, (especially) at high optimisation (flag) settings.So, as long as itoa, is as optimised as all the other operations, or less so, if it is considered a lower priority than some other, much more frequently (in general) used, things. I guess all is good.But (in theory), there always can be special, rare circumstances, where the execution time of something, happens to be especially critical, for various reasons. On the other hand, there are often many ways of fixing such situations. Which in the case of itoa, many alternative solutions, are probably perfectly viable, in most or all situations, depending on the exact application/requirements/specifications/budget etc.

author: AVI-crak
author_rank: Regular Contributor
timestamp: May 29, 2023, 09:55:55 pm
content: Quote from: cv007 on May 29, 2023, 06:58:50 pmYou are right, there really was a mistake.And now I will tell you how to get such an error without sms and registration. It is enough to install the Russian keyboard layout, and a new embitz.While the project is open in ide embitz2,5 - everything works fine. But when the ide is closed, or the project is completed, the project is automatically saved with the juggling of variables. The IDE literally rearranges variables within functions, not all - but only a small part.Without version control, it's very difficult to get projects back up and running. This particular project was considered temporary, with no version control.

author: westfw
author_rank: Super Contributor
timestamp: May 30, 2023, 08:50:45 am
content: Almost no one objects to "stdlib" because itoa() supports bases up to 35 and is therefore somewhat bigger than a more optimized version that only handles the bases you're interested in.They object when using sprintf() to concatenate some strings sucks in 30k of code because the library thinks printf is part of stdio and a string is just a special case of a file, so it needs all the rest of stdio as well. :-( (not to mention maybe floating point libs...)

author: AVI-crak
author_rank: Regular Contributor
timestamp: May 30, 2023, 01:25:55 pm
content: Quote from: westfw on May 30, 2023, 08:50:45 amWhere is base 3 or 7 applied? I'm just curious.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 30, 2023, 02:32:54 pm
content: Quote from: AVI-crak on May 30, 2023, 01:25:55 pmThe only use case for me has been specifying sets of states related to combinatorics (to show to an user, or to parse from user input).Base 3 is useful for specifying sets of tristate logic pins.For a set of five tristate pins, there are 1000003possible combinations, i.e. 35= 243.Any combination in base 3 is easy for humans to read and write, for example using 0 for High-Z, 1 for Low, and 2 for High. Say, 021113: leftmost pin is high-z, second pin from left is high, and the three rightmost pins low.An example of why such tristate logic combinatorics problems can be useful, isCharlieplexingLEDs. Construct all base-3 numbers with as many digits as you have outputs. Each lit LED is between a high and a low pin, in that order (anode-cathode). Only the combinations where at most one LED is lit, are used (so that current, and thus brightness, is constant).The reason for using itoa() or similar within the combinatorics problems is that converting to a string makes it easier/faster to check individual digits. If you check all digits at least once, and the string fits in the fastest cache, it is faster to convert it only once.

author: cv007
author_rank: Frequent Contributor
timestamp: May 30, 2023, 08:01:00 pm
content: QuoteI don't see the size as a problem, as it seems to be less than 3k for any basic usage. The bigger problem for me is the use of heap which I like to avoid, and the fact that the stdio source code is pretty much unreadable so cannot easily understand what its doing or how it works (such as what is going on with FILE, which is a huge struct).I use C++, and add 'LDFLAGS += -Wl,-wrap=_malloc_r' to my makefile so I do not get any heap usage without my knowledge (the specs file in use actually prevents it first, as there is no _sbrk). Since iostream is basically too large for about any mcu, I created my own format class which works about the same as the typical C++ cout usage. It is also much easier to code than something like a printf (its about 400 lines in a header, and only a couple functions do any amount of real work).I use this when I want to mostly do my own thing, but will still use snprintf for something like an efr32 where I am using their ide/drivers/ble stack/etc. and I have already given up much control and am mostly in a C world (I still create the app code in a C++ source file, and wrap the driver/stack C code for easier use if it makes sense to do). This format code will also work fine for an avr, but the avr printf system is already small, uses no heap, is easy to understand and is easy to hook into to get the output directed anywhere you want, so the bigger advantage in that case is eliminating the use of % syntax which always seems to require an investigation for anything other than the simple/common things.Not a big fan of the cout style, but with some helper functions it ends up being nicer than I first thought. Just like printf use can sometimes require some code formatting to make it readable (lots of vars to handle, takes multiple lines to write), this style also can require a little work in making multiple lines readable. It is also nice not having to match up left side format with right side vars.Code://stm32g031k8// ~3k (empty main is 0.5k)#include <stdio.h>int main(){char buf;snprintf( buf, 64, "%s %s 0x%08X%c", "Hello", "World", 123456, '\n' );//to make sure compiler does not optimize something awayauto p = buf;volatile char c;while( *p ) c = *p++;(void) c;while(1){}}//stm32g031k8// ~1.3k (empty main is 0.5k) (exact same makefile as above)#include "Print.hpp"//trying to duplicate snprintf example so is a fairer comparisontemplate<int N>class Astring : public FMT::Print {char buf_; //uninitializedint idx_{0};bool write(const char c) override {if( idx_ >= (N-1) ) return false;buf_[idx_++] = c;buf_ = 0;return true;}public:Astring(){ clear(); }void clear(){ idx_ = 0; buf_ = 0; }char* operator()(){ return buf_; }};int main(){Astring<64> str;using namespace FMT;//'normal' cout style, which ca get quite verbose (this code will work)// test << "Hello " << "World " << showbase << hex << uppercase << setw(8) << setfill('0') << 123456 << endl;//but some helper functions can make it more pleasant to use (but still doing the same thing as previous line)str, "Hello ", "World ", Hex0x(8,123456), endl; //I added comma operator also, which I prefer//to make sure compiler does not optimize something awayauto p = str();volatile char c;while( *p ) c = *p++;(void) c;while(1){}}

author: westfw
author_rank: Super Contributor
timestamp: May 31, 2023, 12:15:23 am
content: QuoteWell, it'll depend on exactly which library setup you're using. A useless error message printf() in the Arduino Due (SAM3x ARMCM3 with newlib, IIRC) added about 12k:https://github.com/arduino/ArduinoCore-sam/issues/47

author: cv007
author_rank: Frequent Contributor
timestamp: May 31, 2023, 01:32:06 am
content: QuoteI assumed you were talking about sprintf. I have never used printf for 32bit as I will use snprintf and hook up (the resulting buffer) to a device on my own, as that is more straight-forward for me. I now tried printf instead of snprintf in my example, with no underlying mechanism to put it to use, compiles to about 5k (0.5k baseline). Not hard to imagine the library pulling in things left and right, so I guess also not hard to imagine the size can explode and the reasons for it may require some digging.The makefile is nothing special, and using whatever newlib (nano) version is included with the compiler in use-https://github.com/cv007/NUCLEO32_G031K8_chrono/blob/main/Makefilebut gcc 11.3 changed the specs files in some way, and I now just use nano.specs.I think the earlier version(s) required both nano.specs and nosys.specs to skip the syscalls.

author: nctnico
author_rank: Super Contributor
timestamp: May 31, 2023, 09:10:45 am
content: Quote from: westfw on May 31, 2023, 12:15:23 amThere are smaller versions of printf available. There really is no reason to avoid using printf and it's cousins. Just use the right one for the job.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 31, 2023, 06:48:33 pm
content: Quote from: nctnico on May 31, 2023, 09:10:45 amYes there are, and if you're using printf() a lot in your embedded application or using it in critical parts, then sure I also highly suggest using an alternative printf() implementation.Now, unless you already have such a function in your arsenal (tested and proven) and the only thing you need is to convert an integer to its decimal representation in ASCII, writing the function yourself will only require a few lines of code and will take a lot less time than finding one alternative printf() that can be trusted.All obvious, but people spending hours or days looking for existing solutions to a simple problem they could have solved in a few minutes is all too common.And most often, "reinventing" a small wheel is a lot more effective.Just saying.

author: nctnico
author_rank: Super Contributor
timestamp: May 31, 2023, 06:53:30 pm
content: Re-inventing a wheel is never good. I prefer somebody spending time on finding a good existing solution / design pattern rather than trying to come up with something 'quickly' by themselve. In the end: if it takes somebody long to find a good solution, then it is likely the solution they come with by themselves is going to be worse. The first post in this thread is a good example. Better make sure to have a whole bunch of wheels on hand so you are prepared for every situation. Otherwise you end up with a mine field of self induced bugs.I've seen my fair share of printf workarounds that where just steaming turds. Adhering to Posix is a good idea in general. A lot of thought, effort, trial & error, etc went into the function calling conventions.

author: Siwastaja
author_rank: Super Contributor
timestamp: May 31, 2023, 09:53:51 pm
content: Quote from: nctnico on May 31, 2023, 06:53:30 pmDo you realize this is a textbook example ofcargo cult engineering?QuoteCompletely impractical on microcontrollers, except for the few largest ones. And supporting 0.01% of POSIX does not add any value from being POSIX-y itself.

author: nctnico
author_rank: Super Contributor
timestamp: May 31, 2023, 11:08:45 pm
content: Quote from: Siwastaja on May 31, 2023, 09:53:51 pmNo, not at all. It is all about avoiding NIH syndrome and doing a poor job. If you want to know/learn something new, are you going to read a book about the subject or start from scratch and make a crap load of mistakes somebody else has already made (and thus could be avoided)?Cargo cult engineering is just incompetent people never becoming competent at their job and incompetent managers leaving them muddling on. Sure that is a problem but far from a valid reason not to re-use existing code and apply well established design patterns. Competent engineers know their limits and thus when to do research / re-use existing work but also know to evaluate existing solutions / patterns and apply these to their problem at hand.QuoteNo. I'm not saying to implement everything from Posix but if you do use a function / piece of functionality that exists under Posix, use the Posix API. It saves you from documenting your own code, stepping into pitfalls others have stepped into before and makes code more portable.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 01, 2023, 08:09:12 am
content: But those "small version of printf" you suggest arenotPOSIX compliant and thus require extra documentation what is actually supported and what is not, and more often than not this documentation is not properly available. The only thing you have here going is thefeelingof using something standardized. Your argument is thus 100% invalid.

author: wek
author_rank: Frequent Contributor
timestamp: June 01, 2023, 08:38:06 am
content: IMO it's the other way round: if you find printf()/scanf() adequate to your work, you are probably not programming microcontrollers (both 'micro' and 'controllers' is meaningful here).JW

author: nctnico
author_rank: Super Contributor
timestamp: June 01, 2023, 08:48:28 am
content: Quote from: Siwastaja on June 01, 2023, 08:09:12 amSorry but this just sounds like yet another attempt to justify re-inventing the wheel. I wrote to use the PosixAPI, not implement all off the functionality! As an example: smaller printf implementations often allow to enable / disable features based on the requirements. A competent engineer will see the limitations and act accordingly.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 01, 2023, 08:51:07 am
content: Quote from: nctnico on June 01, 2023, 08:48:28 amWhat %f does is part of API specification. If %f does not work, then it's not conforming. You can shift the blame ("it's not API but implementation which is broken") but the end result is the same: it's not POSIX conforming, it won't work as expected, it requires extra documentation, and it will cause confusion to users who expect standard-compliant behavior. This is of course all fine usually with "competent engineers" but kinda destroys the basis of your argument. Those competent engineers can as well work with another function with another specification (probably simpler).API is meaningless if the implementation does not stick to API. That's the whole point.

author: nctnico
author_rank: Super Contributor
timestamp: June 01, 2023, 10:49:55 am
content: So, what is your alternative to printf then? Have functions like 'print_text()' 'print_text_1number' 'print_text_2numbers()' 'print_newline()' ? I have come across messes like that... And good luck trying to work with others on such projects.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 01, 2023, 12:42:13 pm
content: Quote from: nctnico on June 01, 2023, 10:49:55 amFirst of all, you can use C99_Genericor C++ to put the parameter type variants under a single interface name. Indeed, Arduino does exactly this withprint()interface.Second, aprintf()interface that supports some but not all features ismoredifficult to work with, than implementing a completely different interface.While a new interface is extremely easy to utterly fuck up –– and I believe those are what you have encountered ––,betterinterfaces are eminently possible.In the thread I mentioned, I was foremost talking about really constricted environments, where every byte matters, and there a string builder approach with different functions to append/prepend data not only makes it possible with minimal resource wastage, it also allows users to prune (or build system to auto-prune) the implementation to only the subset of used functions. When such interfaces are designed, it is easy to fall into the trap of focusing on specific use cases (the ones I happen to have at hand, for me); and the only way to combat that is to get constructive criticism.You, nctnico, are only providing the criticism of "I've never seen that work nearly as well as using a subset of printf()", which is not useful, because I myselfhave, both source (maintenance) and binary code (efficiency, resource use) wise "better than printf()". It may not lookprettyto your eye, but for me and those I've worked with, it has worked very well. (Granted, the reverse string building I like, can be a bit much to grok... I only use it in very specific situations, where every bit of overhead matters and no equivalent forward-building is available.)

author: nctnico
author_rank: Super Contributor
timestamp: June 01, 2023, 12:58:03 pm
content: Quote from: Nominal Animal on June 01, 2023, 12:42:13 pmMy experience is the other way around. Over the years I have setup many microcontroller projects for others to build upon. There has never been a complaint about printf not working as expected. I rarely get questions about my code anyway.The reality is that a printf that can print text and integers and do some formatting is super small to implement and sufficient for 99.9% of the programs that run on microcontrollers. Don't forget the systems that C was originally developed on, have less resources than the average modern day microcontroller. Also, people are used to printf because it comes standard with C and is used in every textbook. If you are going to provide something different, then this just needs more time to learn. And again, it makes sharing code between projects harder because some projects (or existing code) will use printf. Standarisation is very important in order to be able to maintain various software projects and share code between them.Many years ago I worked with a brilliant senior software engineer who had created some very elegant libraries for implementing protocol stacks. Everybody found it brilliant work but nobody wanted to touch his code. Way too complicated to use. At some point not wanting to use his code even became a running gag during lunch break. I made a mental note not to become the laughing stock amongst coworkers where it comes to technical abilities. IOW: KISS is king.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 01, 2023, 01:38:51 pm
content: Quote from: nctnico on June 01, 2023, 12:58:03 pmWell, that's your choice. My experience, and choice, differs.To me it sounds like you and your coworkers were the inept kind, making fun of others to hide your own insecurities and lack of ability. I definitely wouldn't want to work with you or them, if what you told is true. I believe only a weak-minded idiot values his coworkers opinion above the quality of their work, and I don't work (long) with idiots, as they never produce anything useful or interesting anyway, just the same shit in a new package.Quote from: nctnico on June 01, 2023, 12:58:03 pmNo. KISS is a good principle, but so is the Principle of Least Surprise, too. I would claim the latter beats the former, when use and maintenance is involved.Now, this affects the "elegant libraries" you mentioned, too. Maintainability, usability, effectiveness, robustness, security, and resource use, are the key factors when I evaluate code. They all need to be baked in to the design at the get go, and cannot be added afterwards (except through a full rewrite, or at least a significant refactoring). It does sound like the way you used "elegant", does not include those important facets, and only relates to surface appearance or something, in which case that kind of "elegance" is utterly useless and irrelevant to me. It is like saying a library was written by a very respected author. Fine; but I've gotten 'PTSD' from maintaining Perl code created by people I really appreciate, so much so I do not touch Perl or Perl code anymore, so having a respected author meansnothingin real life. It only means something to superficial people who care more about appearance than content.Maintainability and usability are crucial. Yes, they do need to be tailored to the level of their maintainers and users, but we're not talking about general applications development (which is mostly done by human-shaped trained monkeys), but specialized embedded development, so I do believe requiring more than one might from say average apps developers is warranted.(Then again, I for sure would never produce code to a bulk production house where quality and security are only found in advertisements on the marketing side, and true commercial demands of today may mean that "we must use printf() because our developers are too stupid to handle anything different". So, consider my requirements somewhat more stringent than typical software houses or FOSS projects.)

author: wek
author_rank: Frequent Contributor
timestamp: June 01, 2023, 01:50:25 pm
content: > KISS is king.And, printf() is anything but KISS.JW

author: nctnico
author_rank: Super Contributor
timestamp: June 01, 2023, 02:30:09 pm
content: Quote from: Nominal Animal on June 01, 2023, 01:38:51 pmWrong conclusion. It was a classic case of over-engineering and trying to push it onto others as being a good solution. It is like sending a 10 page letter in beautifully worded language while 5 short sentences would have sufficed to carry the same message across. Sometimes you see that in electronics too when engineers get a chance to design a very creative circuit which works wonderfully but in the end it has too many parts and the same goal can be achieved by much simpler circuitry.A too narrow focus on primary features together with freaking out a bit while losing sight of the big picture.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 01, 2023, 03:58:22 pm
content: Quote from: nctnico on June 01, 2023, 02:30:09 pmWell, like I wrote later on in that post, I don't know exactly what "elegant library" means.I don't do "elegant". I strive for useful, maintainable, robust, and secure. Robust includes things like not silently ignore errors that indicate possible data loss, and things like doing something sane when inputs are outside the expected range, not just silently hope for everything to be okay.Some stuff, like certain linear algebra operations in a fully hosted environment, I target towards "minimally proficient developers", but anything embedded or resource-constrained or security-sensitive, I definitely expect/demand more than a minimal skill to maintain or use.Quote from: nctnico on June 01, 2023, 02:30:09 pmHa ha.I am not verbose because I want to, I am only verbose becauseI consistently fail to be concise. I wish I was less verbose, and quite often envy other members who manage to say what I tried to convey in a prior long message with just a couple of sentences.Me fail English, and I know it.I read and write a lot of technical English, but speak or deal with social stuff only rarely. This makes English quite difficult for me, and this is the best I can do for now. I'm working on it.Quote from: nctnico on June 01, 2023, 02:30:09 pmAs a hobbyist, and looking at teardowns by Big Clive and others, I tend to only see the other kind: cutting corners and copy-pasting without true understanding.I cannot tell from Dave's examinations of test equipment if any have overengineered circuits – even parts "gilding the lily" seem to actually help keep the devices within their specified operating parameters.On the software side, I consistently see developers like DJB and their work product being laughed at, while their bug density is much lower than just about anyone elses. I recall a time around the turn of the century when I used exclusively qmail for a mail transfer agent (email server), and got utterly stupid flak about not using Sendmail instead. (Why? Because Sendmail, being the "recommended solution", is about as secure as handing a random tweaker the keys to your house, and telling you're off to a long weekend vacation. Just go look at the CVEs regarding it. Qmail/netqmail isn't exactly bug free either, but it is closer than anyone else has gotten in the mail service field.)So yeah, I really don't put much weight to when a random person laughs at some code. Without seeing the code for myself, it is equal chances to be either ridiculous code, or a ridiculous person laughing at something beyond their skill level and suffering from Dunning-Kruger.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 01, 2023, 06:47:00 pm
content: I'm a little lost on the thread of the conversation, who wins?

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 01, 2023, 07:49:16 pm
content: Quote from: AVI-crak on June 01, 2023, 06:47:00 pmLike you said.

author: wek
author_rank: Frequent Contributor
timestamp: June 01, 2023, 08:18:12 pm
content: Like you, sad.

author: nctnico
author_rank: Super Contributor
timestamp: June 01, 2023, 08:33:59 pm
content: Quote from: AVI-crak on June 01, 2023, 06:47:00 pmIf you think a conversation is about winning then the content is lost on you indeed

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 01, 2023, 08:52:01 pm
content: If there is no winner, then I will continue to useprinto("text", double, float, uint(8-16-32-64)_t, int(8-16-32-64)_t )

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 02, 2023, 10:24:58 am
content: Quote from: AVI-crak on June 01, 2023, 06:47:00 pmAt this point, we have established that nctnico and I have opposite experiences on what kind of output/string construction API in embedded environments are useful and maintainable.Anyone else have worked with others on this arena? Do you have clear experiences on this?_ _ _ _ _Documentation, and comments describing intent rather than operation (i.e."We need to compensate for ..."instead of"Decrease variable count"), make a huge difference here.I've mentioned before that I myself never try to memorize details like the order of parameters formemset(); I always keep a browser window (to man7.org) or a terminal window (forman) open for quickly verifying things. I make a point of finding good documentation I can use to find things quickly and efficiently. It is better use of my time.At one point at the university, I did sysadmin and development stuff in a cubicle. A couple of my cow-orkers initially looked at the way I kept references open when writing code, "humorous"; as if "proper" developers didn't do that. In a month or two, seeing the code quality and the very low bug rate, they changed their mind and started doing the same.This also means that when I create a library for others to use, I believe half the job is proper documentation and examples.(This includes documenting which functions are async-signal safe, if run on POSIXy systems, as well as any locking schemes applied or applicable. I personally do prefer using compiler-provided atomics in POSIX C (these) in libraries to implement lockless thread-safe schemes if possible, even if it is not standard C and thus absolutely portable. Practice trumps theory and standards.)_ _ _ _ _I can see that in environments where consulting man pages and documentation and references is seen as a "weakness" or "lack of 1337 coding skillz", an interface that reminds developers as much ofprintf()as possible, is preferable over a new interface.However, there are many standard C -related details (like how the compiler can replace aprintf("...\n")without any formatting specifiers with aputs("...")) can that can create surprising problems via that approach. (It is why one does not change just some parts of the standard C library, but replaces it wholesale, on top of freestanding environment, even when running under an OS.)I agree withSiwastajathat when one implements an API, it is important to implement the full API, or otherwise one is just undermining the API in the first place.Sometimes, it is reasonable to cater ones interface towards most inexperienced programmers. But when it comes to embedded development, Arduino already does that, and anything serious should be targeted towards more experienced programmers – the kind that does not worry about whether their coworkers will laugh at them for keeping the documentation to the libraries constantly open and accessible.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 02, 2023, 01:30:30 pm
content: Nah, it's just classic nctnico pattern: nctnicoknowswhat is the generally right way to doeverything, and those who prefer to have choices and base their choice on thinking/design are wrong. Goalpost shifting is also always involved to make it look like those who originally wanted to keep all doors open, including nctnico's preferred way, now seemingly aregenerallyagainst whatever nctnico is suggesting while no one ever made such claims. This is called projection: nctnico projects his own inability to understand multitude of options to others.Now I do admit thatifyou have to do cargo cult engineering andifyou need to dumb down the design to some rules of thumb, nctnico's rule set is relatively good, and infinitely better than letting 1000 random monkeys produce random work from scratch. This is why nctnico probably has been mildly successful in many medium-difficulty projects, and the projects that have failed due to being deemed impossible to achieve are easy to dismiss just as that. nctnico is probably a successful manager in a semi-large team of mediocre programmers / implementors who do little to no design.I use printf in some projects and in some projects I do not, it's simple as that.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 02, 2023, 01:54:28 pm
content: BTW, the interface of printf utterly sucks: to print out int64_t type you have to type:Code:printf("%" PRId64 "\n", var);This is, when you do care about portability, type correctness, and code that does not produce warnings and questions about whether it works correctly or not. So even if possible to hide the type with function overloading in C++, I don't really mind using self-made functions like itoa_64 over the bullcrap shown above.But such details about performance, correctness and ease of use are not important to everyone. One-size-fits-all function which is most familiar to beginners (yet misused by most said beginners)mustbe the best in all use cases because nctnico says so.

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 03:17:24 pm
content: Quote from: Siwastaja on June 02, 2023, 01:30:30 pmAn ad-hominem attack does not prove your point and is rather childish as well.Maybe just stick to the subject and play the ball.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 02, 2023, 04:37:11 pm
content: Quote from: nctnico on June 02, 2023, 03:17:24 pmYou make fun of your coworkers and play their contribution down because of your own job insecurity - and I'm making fun of you in response. I'm describing your repeating pattern, maybe you see it as an attack against your person but it really is against your position* and your opinion about others, and the absolute, generic, exception-less nature thereof.*) Oxford Languages says: Ad hominem: (of an argument or reaction) directed against a person rather than the position they are maintaining.Rockstars in software design are a risk and you clearly want to mitigate that, but you miss they are also an opportunity. Integrate them into your community and you can be more successful than when aiming for mediocre.I admit of being such a rockstar nature and I thus have both colossal failures but some successes too. It depends a lot of the environment, too.What you really need to do for your company to thrive on Bell Labs level is to detect skills, accept the fact some people are brilliant without getting envious about it, and then invest resources not only to enable them to work, but also get them some minions who will help them in documentation and learn about their thought process.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 02, 2023, 05:19:11 pm
content: In the Linux kernel, the printk() interface is used for kernel logging (because the compiler can verify the conversion pattern against the parameters specified, and complain at compile time if they do not match). However, to extend the formats, they add additional charactersafterthe conversion specifier.The fullprintf()formatting rules are quite extensive. Many do not realize you can refer to specific parameters by their order, as long as you do it for all parameters. (This is required for things like localization, or localized formatting patterns via e.g.gettext(), to work.)Similarly, full string escaping rules in (POSIX) C are nontrivial. Just look at e.g. C17, and check if yours support \L…, \u…, or \U…. Also verify that'\x123' != '\0443'(even though 0x123 == 0443), because of how octal and hexadecimal escape sequence parsing differs. Hint:'\0443' == '$3'. See C17 6.4.4.4; it even has examples describing these._ _ _ _ _In C99 or later, even in very memory-constrained situations, it is possible to write a macro that expands arguments (starting at a specific argument) into separate calls, which in turn are expanded via the_Genericfacility to type-specific function calls. For example,emit(FOO,BAR,BAZ);can be expanded toemit_init(); emit_int(FOO); emit_float(BAR); emit_str(BAZ);where the_Genericselectsemit_int()forFOO,emit_float()forBAR, andemit_str()forBAZ. You can also have common initial parameters, for example specifying the string buffer to construct the string into. The number of macro arguments is limited, but in practice is several dozen at least.This can actually save a lot of resources, because you do not parse anything at run time. Something likeemit(buf, "Command ", cmd_id, " complete.\r\n");can be transmogrified by the preprocessor into sayemit_init(buf);emit_str(buf, "Command ");emit_uint(buf, cmd_id);emit_str(buf, " complete.\r\n");which is definitely useful. With a good _Generic statement, you'll even get reasonable error messages if your variable type is not supported.I also like to have a finalemit_end(), which terminates the string buffer and returns the length of the constructed string if successful, or negative error code if an error occurred –– for example space ran out. In that case,err = emit(buf, "Command ", cmd_id, " complete.\r\n");actually expands toerr = ( emit_init(buf),emit_str(buf, "Command "),emit_uint(buf, cmd_id),emit_str(buf, " complete.\r\n"),emit_end(buf) );whereerris assigned the return value fromemit_end(buf), so it is basically equivalent toemit_init(buf),emit_str(buf, "Command "),emit_uint(buf, cmd_id),emit_str(buf, " complete.\r\n"),err = emit_end(buf);The functions are called in the specified order, but the other return values are ignored. This is because of the C comma operator behaviour (see C17 6.5.17).(If we want to add optional formatting, things get complicated. The best option I've discovered is to wrap the value in a macro, sayFORMAT(variable,fmtspec), which expands to a((const struct formatted){ .ref = &(variable), .spec = &(fmtspec)}), and thus via_Genericdetecting that structure type toemit_fmt(buf, ((const struct formatted){ .ref = &(variable), .spec = &(fmtspec) }));assuming thefmtspecalso defines the type ofvariable. Thefmtspecitself could be a const structure (with common field specifying variable type, and an union of substructures, one per type), a simple string, a pointer to the formatting function to use, or even a struct initializer (with a small modification to above), depending on what is most useful way to implementemit_fmt()or its equivalent(s).)_ _ _ _ _On 8-bitters that emit such constructed strings via UART or similar, having to construct the string into a linear array first, consumes unnecessary memory resources. Instead, it would be better to record the formatting recipe, and have the UART interrupt generate the next character in the recipe dynamically, using minimal amounts of RAM.One method is to have two reserved pointer values, END (0) and VALUE (-1), so that the recipe is just a sequence of pointers to ROM/Flash strings (stored in ROM/Flash) terminated with an END pointer. Whenever current sequence pointer is VALUE, the next character in the associated RAM buffer (where the pre-converted values are stored) is emitted; if the current sequence pointer is not END, the next character in that ROM/Flash string is emitted. The UART interrupt overhead should be quite small and not vary too much. In RAM, we need the space for the converted values as strings, one pointer to a ROM/Flash pointer to ROM/Flash, one pointer to ROM/Flash, one pointer to RAM, and a status byte (for quick return when there is nothing to be sent), or so.Unfortunately, I have NOT found any sensible way for describing such in C99 or later. Preprocessor macros can be magic, but the best I can do thus far is similar toemit()above, constructing all (including the pointer array) in RAM, which is not optimal. Having the UART state buffer be global, with dedicateduart_emit()function to append to the state buffer, makes for an acceptable interface, I guess. You do need to reserve some "extra" for the conversion buffers, and take care you interact correctly with the UART TX interrupt, but it is possible to buffer another message while one or more previous ones are still being sent, as long as there is sufficient RAM for the pointers and the stringified values. To avoid sending partially constructed strings, the state needs to have shadow copies for the pointers and status byte, fixed at theemit_end()phase.

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 05:51:10 pm
content: Quote from: Siwastaja on June 02, 2023, 04:37:11 pmYou are grasping straws here -again-. Read again, I never wrote that I was making fun of someone. I wrote somebody got made fun of by others without naming names. And FYI that has been going on long before I joined that particular company. If my posts irritate you, just ignore them. This is a free world.

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 06:01:13 pm
content: Quote from: Nominal Animal on June 02, 2023, 10:24:58 amI do that too. There is no need to memorise that what can be looked up quickly. Books and internet are great for that.QuoteAnd getting the documentation out there & actually read by others is equally important. I do quite a lot of specification and documentation work as part of systems design. Despite the documentation being shared online with the various teams I'm part of, I regulary get questions that are clearly outlined in the documentation. I've given up getting bitchy about that a long time ago. I just answer the question with saying sorry for the documentation not being clear.From that I also learned that it is easier to stick with code that has been documented already. In the past I regulary introduced a version of strcpy that takes the size of the target buffer and makes sure that a terminating 0 is at the end of the string even if that means the string is cut short. This to avoid dangling strings and all the mayhem this could cause. I noticed though that using this unknown function confused some and it was hard to keep track of whether it was used properly. Nowadays I mandate the use of snprintf() for simplicity sake. It is known to exist and well documented.QuoteIt is industry practise to do this though for space and performance reasons. In uclibc, newlib and many other small C libraries a lot of functionality has been cut. In the end something has got to give. In most C libraries targeted for microcontrollers, printf() doesn't support printing floating point numbers for example. Are those implementations suddenly useless?

author: Siwastaja
author_rank: Super Contributor
timestamp: June 02, 2023, 06:39:45 pm
content: Quote from: nctnico on June 02, 2023, 06:01:13 pmNot at all! Just that somebody has said:"There really is no reason to avoid using printf and it's cousins"I can see many reasons, including need to print floating point numbers in less than 20K."Adhering to Posix is a good idea in general. "But these do not adhere to POSIX, soA bonus:"Re-inventing a wheel is never good."If wheels were never reinvented, we wouldn't have any of the nice things of today as our wheels would be still made out of wood. Re-inventing a wheel issometimesabsolutely required, and you should be able to separatere-inventingthe wheel fromduplicatinga wheel. Latter is futile work if you can go and buy the exact wheel you need. And I think this is what the inventor of the saying was actually thinking, but ended up with a crappy saying.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 02, 2023, 07:25:57 pm
content: That's for sure.Everything must have a good rationale in engineering.If you can't trust an engineer to write a simple itoa() like function, correct, in a matter of a few minutes andwith your team's codestyle and coding guidelines (which can be a non-negotiable aspect in some environments that almost no third-party code will bring you), why would you ever trust them to code anything at all? It's probably going to be a completely trivial and negligible piece of code compared to the rest of the firmware anyway.Some reasonable measure should be used instead of cargo cult approaches. Obviously there's a world between writing a few, relatively simple functions and writing, say, a full TCP/IP stack.As I said in other threads though, engineering management is difficult, and in many ways is unfortunately orthogonal to engineering in general.So yes, as a manager you often have to make decisions that will cause the least discomfort if anything goes awry (which often means deciding whatever is easy to justify to your upper management), whereas as an engineer, what you want is design correct stuff, and if anything needs fixing, having devised a reasonable design that is easy to maintain and having reasonable tools to fix it.As a side note, adhering to POSIX for MCU development (at least generally speaking) makes little sense. On a more elaborate:standard platform, essentially a hosted one, it does.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 02, 2023, 07:32:27 pm
content: The general tone of the conversation is reminiscent of a Sunday fight between two Yakozuna.It doesn't matter how the debug information is printed. It doesn't matter the number of ways to print debug information in the whole project. It is important where and how the print is applied.The fact is that debugging information is not needed by the finished program, it is simply not needed. When all the bugs and errors are revealed, leaving protruding ears is simply stupid.All programs are made up of functions that call functions that call functions, and so on. This is not related to stack depth, but has a dependency on individual project files.You have an open project file, the very first function you wrote (conditionally), and it has bugs. For debugging, you need a lot of lines of diagnostic information, and that's okay.When a function (or a whole library) starts working without errors, the internal debugging information can be removed, it is no longer needed. However, control of input parameters is necessary!!! Because even a serviceable function can produce wild nonsense with inadequate input parameters.Ok, you have a new 100500th project file, completely without errors, with adequate function calls from the first file. Now from the first file (library) you can remove the control of input parameters.

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 07:36:50 pm
content: Quote from: SiliconWizard on June 02, 2023, 07:25:57 pmThere is a bit of a disconnect here. Let me try to clarify once more: there is no need to implement everything from Posix if it is not necessary (see the many small, limited C libraries out there) but just take from Posix what suits you. In the end it will make things easier because you start from a well defined standard instead of making up your own. There is no need to jump between black & white and start nitpicking over 'completeness' (when there is a new version out, completeness is redefined anyway). In a restricted environment something has got to give and that is clear to everyone. If more functionality is needed for a project (for example to use an existing piece of code), you can always add that later on.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 02, 2023, 09:14:41 pm
content: Quote from: SiliconWizard on June 02, 2023, 07:25:57 pmWell. A rationale. It might not be a good one. For example, neglecting to study a problem because of time or budget constraints, making heavy use of assumptions and partial models... or worse, applying rules of thumb because they're all one knows.I suspect both of these apply in the current thread. With "studying" replaced by familiarity with users and applications, since we're talking about rewriting a function (or sets of) that are very widely used.Which, several people aren't being very careful specifying the scope of what they're doing here: a general purpose replacement for the library functions, or a very narrow purpose-made, size/speed optimized (or not, for the OP!) substitute?Which, to clarify: my reaction for example is in regards to such a confusion. If you're going to re-implement a function, and name it the same thing -- that strongly implies to readers that it should be a general-purpose substitute for the well-known version, and therefore any and all discrepancies against the library interface and behavior, are critical, source-breaking bugs. (That is, a drop-in equivalent that can be compiled into any existing source with no problems.)Tim

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 02, 2023, 09:28:27 pm
content: Quote from: nctnico on June 02, 2023, 06:01:13 pmDo your users actually use it correctly? I.e., with the patternint len = snprintf(buffer, sizeofbuffer,FORMATTING,values...);if (len < 0 || len >= (int)sizeofbuffer) {/* Fail! */}or do they ignore its return value? I bet the latter. Thelen == (int)sizeofbuffercase in particular leads to an unterminated buffer but with a complete string in it.Unless you deviate from the snprintf() API, of course.QuoteNo, but I have seen dozens of Arduino developers being surprised as to why suddenly their sketch is significantly larger, or worse, no longer fit in their MCU.They just cannot associate it with printf()'ing or snprintf()'ing floating-point numbers, often through some compiled-but-not-actually-used debugging output, for some reason. (snprintf, unlike print, comes from avr-libc or newlibc in Arduino. print comes from the Arduino core Printable class.)When you want to implement localization using the standard techniques, a printf() that fails to format e.g.%2$s(second additional parameter as a string) correctly (but happens to print%1$scorrectly if it is the first conversion specifier, because it silently ignores then$part), can be even more aggravating than a printf() that balloons when you use it with floats.I definitely do not see such a printf() as superior to a well-designed different new interface.Often, when formatting floating-point numbers in a microcontroller, I am only interested in a specific range of values and at a limited precision. For example, I might want to limit to range -99999999 to +99999999, with at most eight significant digits, with values exceeding the aforementioned range clamped to the limits. (Possible values shown then includes +1234.5678 and -.87654321.)With a just one additional macro and related formatting struct, I can add to theemit()interface I described earlier aLIMITED_FLOAT(value,limits)formatting method, wherelimitsare a comma-separated list of (optional) structure initializers specifying the various limits, including things like whether to include integral zero for -1 <value< +1, whether to omit trailing fractional zero digits, and so on. No parsing, as it is converted to a function call with the populated structure as a parameter (with unspecified fields zeros), so this uses minimal RAM and code.(Formatting such range-limited floating-point values is much simpler/easier/more efficient than general floating-point formatting, especially if the maximum number of decimal digits is also specified. Very little RAM is needed for the conversion, even when doing exact correct rounding. It is the very large values, and nonzero values close to zero, that require more complex code and more RAM, when formatting floating-point numbers into decimal forms.)Also, on Harvard architecture AVRs (like ATmega32U4 that I happen to like) the C99emit()interface (or GCC and Clang_Generic, to be specific) can differentiate between__flash const char *,const char *, andchar *, making it trivial to support immutable strings in Flash/ROM. How would you do that with the printf() interface? A new formatting specifier would work, but the compiler won't recognize it, leading to all sorts of hassles (see Linux kernel printk() in my earlier message).Theemit()interface does the right thing, and will give a compile-time error. It won't silently map a__flash-qualified type to an otherwise matching non-__flash-qualified type.

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 09:29:40 pm
content: Quote from: T3sl4co1l on June 02, 2023, 09:14:41 pmThe latter is never there in a resource constrained environment. And this is by design. If you use printf for a float without float support, it will print nothing. How that breaks existing code depends on what the resulting text is used for.

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 09:42:57 pm
content: Quote from: Nominal Animal on June 02, 2023, 09:28:27 pmNo, snprintf always includes a terminating zero in the buffer unless the size of the buffer is 0. By design you make sure buffer is long enough for normal operation of the program. If that is not possible, rethink the design because it is likely prone to other errors. Preventing unterminated strings serves as a last resort barrier to prevent an error (memory corruption) from spreading like an oil stain that can't be traced to a source.QuoteChalk that up to learning curve. That is not the fault of using printf or a reason to invent something totally new. Use a smaller version of printf that is suitable for the job.QuoteYou can always come up with cases that need a more elaborate printf function. In such cases use one that supports the required formatting at the expense of needing extra code space.There is no one-size-fits all solution. Just use the type most appropriate. If it is warm outside wear a T-shirt, if it is cold outside wear a warm jacket. It is not more complicated than that.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 02, 2023, 09:53:56 pm
content: Quote from: nctnico on June 02, 2023, 09:29:40 pmWhich latter? You only quoted one thing...Is AVR "resource constrained"? I don't know that I've ever not had float support when called upon, and I don't even know offhand how you can force disable its import.If that's not constrained then I'm not sure why "never". Perhaps definitionally, any platform with such support is not constrained? It's a usual example of a limited system though... *shrug*Not that printf is particularly useful on an embedded system, but you can if you want to, and, a lot of people do.Tim

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 02, 2023, 10:10:08 pm
content: Summary on theemit(ref, values...)interface, and why I believe it is superior to printf()/snprintf() in resource-constrained situations:err = emit(ref,value1,value2);is preprocessed to the equivalent ofemit_init(ref);emit_type1(ref,value1);emit_type2(ref,value2);err = emit_end(ref);without any runtime parsing via C99_Generic, thus reducing code size.The above also means that-ffunction-sections -Wl,--gc-sectionswill only keep the individual format-specific functions that are referred to in the code in the linked binary, reducing code size even further whenever possible.On Harvard architectures like AVRs, GCC and Clang include__flashqualifier in the_Generictype selection logic.This means thatchar *,const char *,__flash char *, and__flash const char *are four separate types that can be directed at four different functions._Genericwill not map one to another silently, and will instead output an error message if the type is not supported.This allows developers to put strings in RAM or ROM/Flash, and trust the facility accesses it correctly (LPM instead of LP in AVRs).With printf(), this is problematic/difficult; even if you implement it, the compiler will not understand it correctly (as it is not standard formatting).Conversion of floating-point numbers to strings usingdefaultrules only requires a suitable formatting function.Conversion of floating-point numbers with formatting specification can be done using a struct wrapper.Basically, a macro likeFORMATTED_FLOAT(value,...spec...)constructs a structure that contains both the floating-point value and the spec parameters. The formatting function is then of formemit_fmtfloat((struct formatted_float){ .value = (value),...spec...);i.e. the structure is populated at the function call time, instead of taking valuable RAM.New formatting functions can be easily added. Typically, it is just a matter of adding the type to the_Genericdefinition list.Minimal cognitive load, so both normal humans and1337 coderzwill get the interface right.There aren't any rules for ordinary output, and only special formatting of values or strings requires the use of a wrapper macro, hopefully well named.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 02, 2023, 10:13:27 pm
content: Quote from: nctnico on June 02, 2023, 09:42:57 pmWhy not require them to learn the better interface from the get go, when the existing one cannot be completely implemented or is problematic even if completely implemented?Or does your opinion really boil down to "printf() is better, because in my opinion, printf() is better", as your above comment indicates?

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 10:16:14 pm
content: Quote from: T3sl4co1l on June 02, 2023, 09:53:56 pmST's Cube IDE comes with float printing disabled out of the box. Early versions of MSPGCC (TI MSP30) have no float support at all. In my experience it is more common to not have float support in microcontrollers.QuoteI'm using printf (in various incarnations) in all my embedded projects as part as a cli that can be used to request status information and / or change settings. This has been proven to be super useful as it greatly simplifies checking & debugging systems that are running in the field. Printf not being useful in embedded platforms is one of the dogmas that should have been killed a long time ago.For my most recent project the cli even works over bluetooth using a standard phone app. Customer is super happy because it allowed them to evaluate some aspects of their new product quickly without needing their phone app to be ready and / or setting up a development environment to make changes in the software.

author: nctnico
author_rank: Super Contributor
timestamp: June 02, 2023, 10:18:14 pm
content: Quote from: Nominal Animal on June 02, 2023, 10:13:27 pmIMO: If there is already something better available from the platform library use that, if not, change the existing printf to a more suitable one. Either way you are using something that is at least standarised to some level and has online information on how to get things done.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 03, 2023, 05:44:27 am
content: Quote from: nctnico on June 02, 2023, 10:16:14 pmTimes are changing. Maybe 15 years ago FPU in an MCU was a weird flagship thing, but today's $1 microcontroller is some Cortex-M4 with at least single precision FPU, so float arithmetics itself comes with no extra flash use and good runtime performance. Yet these things might have as little as 64K of ROM. It is wasteful not to use floats when the hardware is available, for workloads that benefit from using them. Then one probably wants to print them, too, but sacrificing one third of program memory for printf() is a no-go. So this is a very real modern-day issue.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 03, 2023, 06:30:09 am
content: All of this for initially a couple-line function that the OP didn't complain having to write, but just noted that it allegedly would take up more resources than the (non-standard) itoa() provided by newlib, which, as someone quickly pointed out, was a flawed analysis anyway.Oh well.As to T3sl4co1l's note, which was IMO not the core of the discussion but which concerned him, certainly I would strongly recommend not to re-use an existing "standard" function name for something you implement yourself, especially if it's not 100% compliant with the original. Use another name. Certainly.Now as everything else in engineering, you have to adapt rules to the circumstances. If you use a custom implementation of, say, printf() in some embedded project - not a gigantic desktop software project with tens of contributors - that is not fully compliant with the standard printf(), that's not a big deal. I still tend to favor choosing other names - and possibly other interfaces than printf(), as some have already suggested in other threads in the past - but otherwise that is no big deal.And finally, newlib, even in its 'nano' variant, is still a bit of a dog. picolibc is an overall better solution at least for small embedded targets.And that said, nothing forces you to ever use a single std lib function in some MCU project. The basic memset(), memcpy() and memmove() are directly inlined by compilers these days, as to the rest, I would not miss a lot of the standard functions.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 03, 2023, 10:08:09 am
content: Quote from: SiliconWizard on June 03, 2023, 06:30:09 amHilarious, isn't it?>I need a small itoa() for a device with 16KB of flash and already use LTO to get size down, so I experimented with different itoa() implementations for further savings<There really is no reason to avoid using printf and it's cousins, regards, nctnico

author: nctnico
author_rank: Super Contributor
timestamp: June 03, 2023, 10:09:05 am
content: Complaining about a thread going offtopicWhile you are at it, go and check all the other threads that went offtopic at some point.

author: nctnico
author_rank: Super Contributor
timestamp: June 03, 2023, 11:23:30 am
content: Quote from: SiliconWizard on June 03, 2023, 06:30:09 amI have tried that but since I share code over a wide variety of platforms (including testing on a PC), having different printfs (as an example, there are many other functions) becomes a big mess quickly with code becoming hard to re-use. Similar for having cut down standard C libraries that provide the same names. This is all with the goal of being able to keep using the same source code. Imagine needing ifdefs in all Linux software to make it compile using uclibc? It would defeat the purpose of uclibc. The same for having a Posix / BSD style socket layer on embedded targets which typically only support a subset which is good enough for the majority of the use cases.

author: brucehoult
author_rank: Super Contributor
timestamp: June 03, 2023, 01:05:21 pm
content: Quote from: Siwastaja on June 03, 2023, 05:44:27 amBut now the cost-sensitive are using $0.10 microcontrollers (whether WCH RISC-V RV32EC or Puya CM0+), with 2 or 3 KB RAM and 16k flash.Even if you want to use FP, software FP is fast enough (~5µs for + - *) on an AVR for many purposes. It's mostly just the flash space is a problem on the smaller devices.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 03, 2023, 02:33:25 pm
content: Quote from: nctnico on June 03, 2023, 10:09:05 amNot at all, offtopic is fine to me. I find it just hilarious you give an off-topic statement of quite absolute nature ("really no reason"), the proof to show it false being right above your very nose.

author: kgavionics
author_rank: Regular Contributor
timestamp: June 03, 2023, 10:06:22 pm
content: In my case I don't use the bellow function to convert an Ultra long to string, not because of the size, or the bloat, but the fact that its output string is led by zeros, which I replace with spaces and each time the function is called it overwrites the old string, which is good, because I don't have to worry about clearing the old string, For instance with prinft function if you have a counter that counts from 0 to 255 and rolls over to 0, the hundred will be overwritten, but not the tens and ones, and you have to clear them manually by writing spaces after the counts rolls over!Code:void UlToStr(char* s, unsigned long bin, unsigned char n){uint8_ttemp=n;s += n;*s = '\0';while (n--){*--s = (bin % 10) + '0';bin /= 10;}n=temp;for (uint8_t i = 0;i < n-1;i++){if (s == '0')s = ' ';else break;}}

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 03, 2023, 10:13:25 pm
content: Quote from: Nominal Animal on June 02, 2023, 09:28:27 pmWhen a truncated string would have no consequence (such as for some data logging or such), I don't test the return value and systematically set the last char of the buffer to zero right after the call to snprintf().Otherwise, I check the return value and act accordingly.Doing neither is (almost) as bad as just using sprintf().

author: Siwastaja
author_rank: Super Contributor
timestamp: June 04, 2023, 05:51:04 am
content: While the C standard, at least since C99 clearly says snprintf always must create zero-terminated string for any n > 0, googling about this reveals that the world is full of broken implementations hanging around, so adding buf[sizeof buf -1] = 0; after the call is pretty common practice; this is what I have been doing just in case, too. It's a pretty serious failure and a manifest how short-sighted some library designers / writers can be. One more reason I don't see much value in the usual "libraries are always much better written that what you can do by yourself" argument.

author: brucehoult
author_rank: Super Contributor
timestamp: June 04, 2023, 06:34:03 am
content: Quote from: Siwastaja on June 04, 2023, 05:51:04 amWell, sure, I'm confident I can write any given library function better that the one in the library -- especially since I know in which directions my usage is or is not going to stress it -- but I don't have time to rewrite ALL of them.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 04, 2023, 10:38:33 am
content: Quote from: brucehoult on June 04, 2023, 06:34:03 amRewriting everything is a very bad idea. Difficult free work, with the loss of your time.Even if successful, there will be more enemies than fans.Besides - this world is already used to bad terrible decisions.We all listen to modern music, which is guaranteed to make a 17th century man lose his mind.We watch films from an endless kaleidoscope of three-second events (something new every three seconds). At the same time, I personally have a feeling that such films are made exclusively for people with attention deficit disorder.Even our food has more ingredients to maintain its appearance than the weight of the food itself (packaging + chemistry).This is the very ideal case when you can abandon the old version, and really write something new. No matter how many enemies and admirers there are. If you are comfortable, then the opinion of others is no longer important.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 04, 2023, 01:16:14 pm
content: Quote from: Siwastaja on June 04, 2023, 05:51:04 amExactly. It also says that (forn > 0)"Thus, the null-terminated output has been completely written if and only if the returned value is nonnegative and less thann". If the return value is zero, then the source string was an empty string (either formatting string empty, or consisting of conversions that produce no output). If the return value isn, then a standards-compliant library has truncated the output by one character, but many (historical) buggy implementations left the array contents complete but unterminated.(I thought glibc 2.0 era did that, but it actually returned -1 if the output was truncated, according to the man page.)Funnily enough, the standard does not requirestrncpy()use the last character for the nul (stating that if the firstnchars of source does not include a nul, then the destination buffer won't have a nul either). Which lead to BSDstrlcpy()andstrlcat(). The wording of the standard has caused somestrncpy()implementations tofillthe rest of the buffers with zero (some still claim it does), which significantly slows the implementations down without any benefits.(Kernels and other privilege boundary crossing security-sensitive code cannot rely on just filling the rest of the buffer with zeroes; they also need to worry about things like structure padding containing sensitive values usable in side channel attacks. Only copying the individual fields (not full structures) and strings to userspace-allocated (allocated and initialized by the less privileged side) structures and buffers seems to be the most robust approach.)Also,strncpy(dest,dest+i,n)is Undefined Behaviour for0 <=i<=nwhenn> 0. Some implementations deal with it sensibly, others do not.Most ridiculously, neitherstrdup()norstrndup()are in standard C yet! (They're in POSIX, though.asprintf()andvasprintf()(for<stdargs.h>va_listargument lists), for dynamically allocating a target buffer to print into, is still a GNU extension. However, this is outside the scope here, because it is not useful in memory-constrained embedded/microcontroller environments, only really in fully hosted environments with lots of address space (so memory fragmentation due to heavy dynamic memory use is not a problem).Personally, I find the Surprise in difference in behaviour ofsnprintf()andstrncpy()a bug in the standard.Weight(Principle of Least Surprise) > Weight(KISS), in my opinion.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 04, 2023, 06:51:55 pm
content: Quote from: DavidAlfa on May 27, 2023, 04:29:29 pmI don't know if this has been already said on this thread (it's a long thread) but "short and efficient looking code" does not automatically translate to "short and efficient assembly / machine code".

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 04, 2023, 08:36:14 pm
content: Quote from: Nominal Animal on June 04, 2023, 01:16:14 pmYes, and this probably one reason some implementations of snprintf() are buggy and do not ensure a properly terminated string in case of truncation. Confusing.String functions in the C std are a disaster for the most part anyway. They are fine for anything non-critical, but I would never use them in any critical code.As to the Principle of Least Surprise, this is exactly one good reason for writing stuff yourself. What you write yourself has the least chance of surprising you. (Or if it does, you have a bigger problem.)

author: nctnico
author_rank: Super Contributor
timestamp: June 04, 2023, 09:19:19 pm
content: Only problem is that a) quite a few people suffer from Dunning-Kruger and b) most people are blind to their own mistakes.IOW: if you want to be really sure your software works, have it looked over & tested by somebody else. Otherwise it really is better to find a good library and check the source code to make sure. Then at least two people have looked at the code reducing the chances of serious mistakes.

author: brucehoult
author_rank: Super Contributor
timestamp: June 04, 2023, 10:01:16 pm
content: Quote from: MMMarco on June 04, 2023, 06:51:55 pmFor example, on Cortex-M0 ...Code:"0123456789ABCDEF"[...]... uses 17 bytes for the string plus 4 bytes for a constant pool pointer to the string, plus 4 bytes of code, for a total of 25 bytes.An alternative method that tests the value and adds either '0' or 'A'-10 uses only 8 bytes of code.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 05, 2023, 12:10:24 am
content: It also calls the built-in function __aeabi_idivmod - insanely slow even for Cortex-M0.

author: cv007
author_rank: Frequent Contributor
timestamp: June 05, 2023, 12:23:39 am
content: QuoteThe signed division library brought in is about 200 bytes more than the unsigned version. The library version of itoa uses utoa to do the work, which is unsigned division. The library version is larger/does more work, but since it does unsigned division it gets a 200 byte head start. Had both signed/unsigned division been brought in due to other code, the real size difference would be revealed.In the end, the user could have used the library version thinking it was the size winner, and would never realize it was actually larger. And would also make no difference in the 16k mcu, and the 'error' would never be noticed. Ever.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 05, 2023, 05:48:49 am
content: Quote from: brucehoult on June 04, 2023, 10:01:16 pmAnd think about the case where one only needs to print base-10 numbers which is quite often in user interfaces of small gadgets!

author: Siwastaja
author_rank: Super Contributor
timestamp: June 05, 2023, 05:51:04 am
content: Quote from: cv007 on June 05, 2023, 12:23:39 amYes - as others have said, DavidAlfa's methodology is pretty flawed. Size analysis isn't rocket science, but notthateasy either. One needs to carefully look at the size of the actual function but also what else it calls, and then decide if those dependencies are used - or maybeusefulfor future code - elsewhere.

author: voltsandjolts
author_rank: Unknown
timestamp: June 05, 2023, 07:56:27 am
content: Quote from: Siwastaja on June 05, 2023, 05:48:49 amYup.I mostly use M0 and mutiply-shift does ok.Code:/** https://stackoverflow.com/questions/7890194/optimized-itoa-function* Convert an unsigned number in the range 0...99,000 to decimal 5 ascii digits using Q4.28 notation.* Terminating NULL is NOT provided.*/void utl_unsigned_to_5digit_ascii(char *buf, uint32_t val) {const uint32_t f1_10000 = ((1UL << 28) / 10000UL);/* 2^28 / 10000 is 26843.5456, but 26843.75 is sufficiently close */uint32_t tmp = val * (f1_10000 + 1) - (val / 4);for(uint32_t i = 0; i < 5; i++){uint8_t digit = (uint8_t)(tmp >> 28);buf = '0' + (uint8_t) digit;tmp = (tmp & 0x0fffffff) * 10;}}

author: brucehoult
author_rank: Super Contributor
timestamp: June 05, 2023, 10:53:08 am
content: Quote from: Siwastaja on June 05, 2023, 05:48:49 amSure. Saves 6 of those 8 bytes.Also saves linking in __aeabi_idivmod or __aeabi_uidivmod as if you write "10" explicitly as the divisor then any decent compiler will do the "multiply by 0xcccccccd and shift right by 35" trick.

author: brucehoult
author_rank: Super Contributor
timestamp: June 05, 2023, 11:07:27 am
content: Ahhh .. I did that directly on ARM7TDMI, but needed to use an A32 function to get 32x32 multiply, so CM0 won't have that.Still, three multiplies (single cycle on every CM0 I've seen) and a handful of shifts (5?) and adds (3?) and ANDs (2) will let you do that in if I've counted right 13 cycles. Which is much faster than a bit by bit divide, and not much worse than the 7 cycles a UMULL plus shift takes on ARM7TDMI.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 05, 2023, 03:40:16 pm
content: A small test for cortex m0:Code:char* __attribute__(( noinline, used )) u32_char_m3 (char* tail_txt, uint32_t value)//204{*tail_txt = 0;uint32_t res, tmp;do{tmp = ((uint64_t) value * 3435973837UL >> 32);res = value + '0';value = tmp >> 3;res -= value * 10;*(--tail_txt) = res;}while (value > 0);return tail_txt;};char* __attribute__(( noinline, used )) u32_char_shift (char* tail_txt, uint32_t value)//142{*tail_txt = 0;uint32_t res, rem;do{res = value >> 1;res += res >> 1;res += res >> 4;res += res >> 8;res += res >> 16;rem = value + '0';value = res >> 3;res = value + (value << 2);rem -= res << 1;if (rem > ('9')){value++; rem -= 10;};*(--tail_txt) = rem;}while (value > 0);return tail_txt;};char* __attribute__(( noinline, used )) u16_char_m0(char* tail_txt, uint16_t value)//96{uint32_t val = value;uint32_t res, tmp;do{res = val + '0';val *= 52429UL;val >>= 19;tmp = val + (val << 2);res -= tmp << 1;*(--tail_txt) = res;}while (val);return tail_txt;};char* __attribute__(( noinline, used )) _itoa_1(int val, int base)//100{static char buf;int i=30;for(; val && i ; --i, val /= base)buf = "0123456789ABCDEF"[val % base];return &buf[i+1];};int main(void){char texx1;char* out;uint16_t rez;uint32_t test, nc, hc;uint32_t tim;uint32_t dats[]= {1,65535,2147483647,4294967295};RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;TIM1->PSC =1;TIM1->CR1 |= TIM_CR1_ARPE;TIM1->ARR = 65535;TIM1->DIER = TIM_DIER_UDE;TIM1->CR1 |= TIM_CR1_CEN;nc = -1; hc = 0;do{test = dats;tim = TIM1->CNT;out = u16_char_m0(&texx1,test);tim = TIM1->CNT - tim;rez[++nc] = (uint16_t) tim;printo("u16_char_m0= ",out,", tic=",rez,"\n");tim = TIM1->CNT;out = u32_char_shift(&texx1,test);tim = TIM1->CNT - tim;rez[++nc] = (uint16_t) tim;printo("u32_char_shift= ",out,", tic=",rez,"\n");tim = TIM1->CNT;out = u32_char_m3(&texx1,test);tim = TIM1->CNT - tim;rez[++nc] = (uint16_t) tim;printo("u32_char_m3= ",out,", tic=",rez,"\n");tim = TIM1->CNT;out = _itoa_1(test, 10);tim = TIM1->CNT - tim;rez[++nc] = (uint16_t) tim;printo("_itoa= ",out,", tic=",rez,"\n\n");hc++;}while(hc < 4);while (1){};Test result:QuoteCortex-M7:Quote

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 05, 2023, 06:20:20 pm
content: Cortex-M0 (ARMv6-M) is an interesting architecture: 32-bit, no hardware division, only 32×32=low-32 multiplication which can either be a slow or a fast single-cycle one, and no DWT cycle counter support. Compiler options (especially optimization,-O2vs.-Os) matter a lot.(I only have Cortex-M0+ (NXP Kinetis MKL26, Teensy LC), which has single-cycle 32×32=low-32 multiplication (and I/O port access), so my testing is limited.)As to theitoa()implementation, I would suggest [Edited:]Code:char basis[] = "0123456789abcdef"; // assuming radix = 2..16char *utoa(unsigned int val, char *dst, unsigned int radix){char *p = dst, *d = dst;do {*(p++) = basis[val % radix];val /= radix;} while (val > 0);*(p--) = '\0';while (d < p) {char c = *d;*(d++) = *p;*(p--) = c;}return dst;}char *itoa(int val, char *dst, unsigned int radix){if (val < 0) {*dst = '-';utoa(-val, dst + 1, radix);return dst;} else {return utoa( val, dst, radix);}}iff using__aeabi_uidivmodis acceptable. As I already argued, I prefer a different interface myself.Edited; the original version forgot to swap the digit order, and itoa returned dst+1.

author: nctnico
author_rank: Super Contributor
timestamp: June 05, 2023, 06:36:55 pm
content: I have a feeling utoa is printing the numbers in reverse.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 05, 2023, 06:57:00 pm
content: Quote from: Nominal Animal on June 05, 2023, 06:20:20 pmYou cannot use an external buffer. You cannot use the function in several metas at once (debugging, exit to a graphical or character screen, log...).If you need several functions that are identical in functionality, then you need to write several universal functions with a one-time use.And in general, 2k memory is enough for everyone.Stop using the microwave with kittens inside.!!!

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 05, 2023, 08:14:46 pm
content: Quote from: nctnico on June 05, 2023, 06:36:55 pmYep.I forgot that when one returns the buffer pointer, and/or the string is expected to be at the beginning of the buffer, you need to either reverse the digits (as the fixed version above now does), or you build the string backwards and move the string to the origin of the buffer.The reversing option is more robust, because callers might know the value is always say between -999999 and 9999999, and thus only reserve eight chars for buf. The interface does not allow passing the buffer size, so we cannot do the move option safely. (Except if we check the number of digits in the value first, and that'd be wasteful.)Quote from: AVI-crak on June 05, 2023, 06:57:00 pmI assure you, my intent was only to suggest an improvement, not microwave kittens.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 05, 2023, 08:41:23 pm
content: Quote from: Siwastaja on June 05, 2023, 05:48:49 amYes, if all you need is base 10, then do not write a generic function for any base, but one just for base 10. GCC and probably most decently recent compilers will optimize the divide and modulo using multiplication and bit shift only, which is insanely faster.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 05, 2023, 10:10:07 pm
content: Quote from: Nominal Animal on June 05, 2023, 08:14:46 pmI don't know where it started, why this style became the standard, and I don't understand how you can use such functions without input parameter errors. This is wrong in itself.1) The number input parameter must have a strict type, "int" is not suitable for this role at all - it is simply equal to the size of the microprocessor register. This point has already been discussed a thousand times. Valid: "int8_t, int16_t, int32_t" and so on.2) The format number "uint32_t" can contain "int32_t" - but not vice versa. This means that you need a function for the unsigned number, which will additionally be used in the signed number function.3) The strict type holds a limited range of possible number values, for example "uint32_t", and "int32_t" can contain up to 11 characters, and a terminating zero = 12 bytes. Making the buffer size no longer makes sense.4) The most optimal and fastest algorithm is to fill the buffer from the end. Only one version of the string "0b01010011" is displayed head first. And there is a good reason for this - the number is bitwise shifted to the left and checked for "less than zero". The size is known, it's just a loop for "n" steps. This function doesn't even need to return a pointer!!However, the "0b01010011" variant is considered stillborn, precisely for this reason (head first).However, if the function fills the buffer from the end, this must be indicated in the name of the input parameter.5) The buffer itself must have a static variable modifier outside the function boundary. This will force GCC to make copies of the buffers in every project file - wherever printing is used. In this case, printing will work without failures as part of the OS.6) Variant of processing a number into a string "0xFFEE0111" - requires an explicit indication of the string size. This is the only function - which should produce a stable beautiful version. Well, or at least a multiple of two signs.Because "0x11F" is guaranteed to break the neck of half of the string parsers. For example, GCC processes a fuzzy character set about 30 times slower.If you do everything correctly - you get about the same set of functions as mine. Because it's convenient.In addition, I showed a test of all the functions that appeared in this topic a little higher.Next to each function \\ is the size in bytes. The execution time is marked in ticks (tic).I deliberately showed the representation of the string in natural form. The translator is doing something strange, even I find it hard to understand in reverse translation.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 06, 2023, 11:51:13 am
content: Quote from: AVI-crak on June 05, 2023, 10:10:07 pmThe interface and behaviour of functions nameditoa(),ltoa(),utoa(),ultoa()is fixed by convention (and the principle of Least Surprise), as they are implemented in a few standard C libraries already.Like I said, I prefer a different interface myself. I've discussed the interfaces I prefer (in very memory-constrained situations) inthis thread I started, as well asreplies #68and#80in the current thread.There are two ways to make the atoi-like interface safer, without wasting memory:const char *itoa(const intlen, charbuf, intval, const unsigned charradix);which constructs the number at the end of the buffer (with final character being nul,'\0'), and returns a pointer to the leading character of the string ((const char *)bufto(const char *)buf + len - 2), or a constant pointerTOA_ERRORwhich points to say"(?)"if the buffer is too small (otherwise first char is set to NUL) orradixis invalid.int itoa(const intlen, charbuf, intval, const unsigned charradix);which constructs the number at the beginning of the buffer, always including the nul at end, and returns the length of the string (excluding the nul), or a negative (withbuf == '\0') in case of an error like invalidradixor the buffer being too small.In both cases, the compiler is will detect buffer overruns at compile time, in both callers and in the implementation itself. This array parameter form is valid since C99, and only requires that the array size is preceded by the array itself in the parameter list.Also, even when users do not check the return value, the buffer is initialized to a safe value even in case of error, making the error case obvious (so no silent truncation or such).It makes sense for the generic versions to be implemented asstaticin a header file, to simply checklen>1andradix, and immediately return the error; possibly checkvalto handle negative values; and call the optimized radix-10, radix-16, radix-8, radix-2, or generic version (with a nonnegativeval). With--ffunction-sections -Wl,--gc-sectionsthe unused ones are then not linked in at all, and wheneverradixis constant, the optimized version is called directly.The first interface uses the efficient divide-and-modulus by radix approach. By letting it return a pointer to the beginning of the string, the number can be constructed backwards, and no swap-or-move is needed. On Cortex-M0/M0+/M1, it will use one call to__aeabi_uidivmodper digit converted.It is most useful when it is used in direct print()-like interfaces.The second interface also uses the divide-and-modulus by radix approach, but typically in reverse, followed by a string-reverse. This is useful, because it makes concatenating the number string to an existing buffer easier, with the cost of the additional string reversion (which is tiny).Quote from: AVI-crak on June 05, 2023, 10:10:07 pmThis is dictated by the API. I personally prefer to use fixed-size two's complement types and fast types:uintN_t,intN_t,uint_fastN_t, andint_fastN_tforN= 8, 16, 32, and 64 if supported.Again, mypreferencealigns with yours, but if you implement an existing API like I did,using a name for a well-known function with well-known interface and behaviour, you better duplicate its interface and behaviour and not diverge, oryouwill lead others to write buggy code.Quote from: AVI-crak on June 05, 2023, 10:10:07 pmFirst, that wastes a lot of memory, and is absolutely not suitable for constrained situations.Second, that will fail whenever two or more numbers are converted using the same function, and are used concurrently in the caller, i.e.char *xs = your_itoa(x, 10);char *ys = your_itoa(y, 10);will yield either a copy or partial garbage ofysinxs.Using mine, if I have earlier checks that verify-9999 <=x,y<= 99999, I could safely use in a functionchar xs, ys;(void)itoa(x, xs, 10);(void)itoa(y, ys, 10);with only 12 bytes used on stack for the two stringlets. If I have no such checks, then[(278+sizeof(int)*CHAR_BIT*28)/93], which works for any number of bits. (278 = 3*93-1, and 28/93≃0.301075269 > 0.301029996 ≃ log(2)/log(10).)Quote from: AVI-crak on June 05, 2023, 10:10:07 pmNo. I suspect a bit of Dunning-Kruger here, because "convenient to the library developer" != "correct".

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 06, 2023, 01:46:28 pm
content: It is very difficult to argue with you, almost impossible.I'll be brief - the standard C libraries piss me off.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 06, 2023, 02:31:12 pm
content: Quote from: AVI-crak on June 06, 2023, 01:46:28 pmSorry about that. It is not my intention. I just want to get to the bottom of things, to discover the reasons behind various opinions, as that is the only way I can truly learn. Simply adopting others' opinions – no matter from how respected an authority the opinion originates from – is just copying, not learning.I wish I could do this without being 'almost impossible' to argue withbecause I often learn something new myself from such arguments.Quote from: AVI-crak on June 06, 2023, 01:46:28 pmI don't like it much myself either, especially in memory and resource restricted environments like microcontrollers.Instead of trying to replace the entire C language wholesale, it is very possible to just replace the standard library with something else. In the C standard, this is called thefreestanding environment(the "normal" one ishosted environment). When running under an OS, you do need to write the syscall wrapper/wrappers yourself (I recommend inlineextended asmif using GCC, Clang, or compatible compilers) for your target architecture.Simply put, if you are writing code for AVRs, using avr-libc is not a requirement, just an option. Similarly for all other embedded development wrt. newlib. You can do your own, and if you tell the compiler you want the freestanding environment, you don't need to implement the standard library API either.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 06, 2023, 04:38:53 pm
content: Quote from: Nominal Animal on June 06, 2023, 02:31:12 pmWell, then, catch the brainwasher.Play around with the number type "value", right in the function.https://godbolt.org/z/son3EsKaK

author: MMMarco
author_rank: Regular Contributor
timestamp: June 06, 2023, 05:59:26 pm
content: Quote from: Nominal Animal on June 06, 2023, 11:51:13 amI agree with that. As others have already pointed out, it just happens to be that the C standard library API sucks to begin with.

author: wek
author_rank: Frequent Contributor
timestamp: June 06, 2023, 06:03:44 pm
content: Quote from: MMMarco on June 06, 2023, 05:59:26 pmC sucks to begin with. So what.JW

author: wek
author_rank: Frequent Contributor
timestamp: June 06, 2023, 06:07:56 pm
content: The good, the bad and the ugly: I've just dared to write this:Code:static const char * Num0_15(char * s, uint32_t n) {if (n <= 9) {s = '0' + n; s = '\0';} else {s = '1'; s = '0' + n - 10; s = '\0';}return s;}JW

author: MMMarco
author_rank: Regular Contributor
timestamp: June 06, 2023, 06:08:55 pm
content: Quote from: wek on June 06, 2023, 06:03:44 pmI don't think C sucks. Sure there are some things that are borderline evil like unsafe functions likegetsetc. but I much prefer C over C++.The question is, what other alternatives do you have for embedded apart from assembly, C and C++?I know there is Rust and Go (if I'm not mistaken) but I think those projects are still in their early stages.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 06, 2023, 06:32:57 pm
content: Quote from: wek on June 06, 2023, 06:03:44 pmC core language is still quite A) consistent, B) powerful. Standard library on the other hand is very inconsistent and limited in its abilities (so that programmer needs to re-write simple things over and over again), it looks like some people just cobbled something together in 1970's and then it was locked down with no changes allowed. BTW, PHP standard library has the same problem, it's not well thought out but just some random shit put together.C itself has improved through revisions like C89/90/99/11 etc. Those revision could easily have added new, better standard functions while leaving the old ones for compatibility - if there is will, there is way - but nope. Therefore I applaud Nominal Animal's effort in a replacement standard library and actually see a replacement over freestanding C much better than overly complicated stuff like Boost in C++.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 06, 2023, 06:35:48 pm
content: Quote from: Siwastaja on June 06, 2023, 06:32:57 pmThank youfor saying this! PHP would be a pretty good language if it wasn't so inconsistent with its API and behaviour.BTW, the inconsistencies of PHP are partly owed to the fact that C itself was inconsistent back when PHP was in its early stages.And I 100% agree withQuote from: Siwastaja on June 06, 2023, 06:32:57 pmThe language itself is fine, it's just that the C standard functions are a mess.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 06, 2023, 07:32:43 pm
content: Quote from: AVI-crak on June 06, 2023, 04:38:53 pmSorry, I do not understand. Was my example code gibberish?If you want to write just one signed and/or one unsigned conversion function, the best types to use areintmax_tanduintmax_t, because they represent the maximum width integer types the compiler supports.Here isumaxtoa()andimaxtoa()with proper safety checks and comments describing the intent:Code:#include <stdint.h>const char toa_basis[] = "0123456789abcdef";char toa_error = "(?)";// Note: sizeof(toa_basis) == strlen(toa_basis) + 1#define TOA_RADIX_MAX (sizeof(toa_basis) - 1)static char internal_umaxtoa(uintmax_t val, char *dst, int radix){// Verify valid radix.if (radix < 2 || radix > (int)(TOA_RADIX_MAX))return 1; // Error// Construct string starting with the least significant digit of 'val',// using a helper pointer 'end' since we need original value of 'dst' later.char *end = dst;do {// Current least significant digit in 'val' base 'radix'; then advance 'end'*(end++) = toa_basis[val % radix];// Drop next digit into the least significat digit place.val /= radix;// Repeat if 'val' is nonzero.} while (val);// Terminate the string.*end = '\0';// Now we have the numeric string in reverse order, from *dst to *(end-1).// For example, 1234 in decimal will yield "4321" with end==dst+4.// Reverse the string by swapping characters.while (dst < --end) {// We already decremented end to point to the char itself,// and need to swap '*dst' and '*end', and then increment 'dst'.const char c = *dst;*(dst++) = *end;*end = c;}// Successful conversion.return 0;}// dst must have room for at least 1+ceil(log(val)/log(radix)) chars.char *umaxtoa(uintmax_t val, char *dst, int radix){// If 'dst' is NULL, we can only error out.if (!dst)return toa_error;// Do the conversion using internal helper function.if (internal_umaxtoa(val, dst, radix)) {// Nonzero return value: Make 'dst' be an empty string, and return error.dst = '\0';return toa_error;}// Success. Return 'dst'.return dst;}// dst must have room for at least 2+ceil(log(abs(val))/log(radix)) chars.char *imaxtoa(intmax_t val, char *dst, int radix){// If 'dst' is NULL, we can only error out.if (!dst)return toa_error;if (val < 0) {// Do the conversion using internal helper function, but skip the first char in 'dst'.if (internal_umaxtoa(-val, dst+1, radix)) {// Nonzero return value: Make 'dst' be an empty string, and return error.dst = '\0';return toa_error;}// Success. Set the negative sign, and return a pointer to it.dst = '-';return dst;} else {// Do the conversion using internal helper function.if (internal_umaxtoa(val, dst, radix)) {// Nonzero return value: Make 'dst' be an empty string, and return error.dst = '\0';return toa_error;}// Success.return dst;}}These have been tested to produce identical outputs tosnprintf(buf, sizeof buf, "%ju",uval)for all 33-bit unsigned integers (and a few hundred million random 64-bit unsigned integers), and tosnprintf(buf, sizeof buf, "%jd",ival)for all 33-bit signed integers (and a few hundred million random 64-bit signed integers), in base 10.They intentionally defend against silly errors, like using aNULLbuffer (perhaps it was dynamically allocated, but the allocation failed), as well as invalidradixes. The only error you can do, is supply a buffer that is too small for the conversion. We cannot check for the buffer size, since all we get is the pointer to its beginning.Are they the fastest possible? Absolutely not, because we must stay within the behaviour of the existing API, since we're using thextoa()naming convention, which users expect to behave like they normally do. Also, often theuintmax_tandintmax_ttypes are two or four machine words in size, and that makes the divide-and-modulo by radix much, much slower than necessary. It would be useful to compare toUINT_MAX, and useinternal_utoa()–– identical tointernal_umaxtoa()exceptvalis of typeunsigned int––, because it is either machine word in size, or the compiler implements arithmetic on it as efficiently as it can, making it a good candidate for the type used in typical conversions.Alternatively, you could implementinternal_u16toa()and/orinternal_u32toa(); the former might be useful tradeoff on 8-bit microcontrollers.Just limiting ourselves to the interfaces existing libraries provide –– including the above functions I have posted to this thread! –– may make the code slightly easier for others to maintain (in the sense that when they write code using these without consulting the documentation, the implementation makes typical bugs other than too-short buffer obvious in testing), but not true advancement.What if we want something better? Well, that was exactly what that thread I started way back was about, for microcontroller-type use cases ("constrained environments"). I prefer a completely different interface to itoa()/snprintf() myself. The first question then is, what do we intend to use the conversions for: emitting to USB or UART, constructing longer strings into RAM buffers, or what? The 'good' answers to that, when more closely examined, will likely yield completely different interfaces! Especially if one considers stuff like _Generic() and macro expansion, which can help make an interface much easier to use.

author: nctnico
author_rank: Super Contributor
timestamp: June 06, 2023, 07:55:16 pm
content: Quote from: Nominal Animal on June 06, 2023, 07:32:43 pmTrue advancement doesn't come from C. A first step would be to move over to C++ with better string handling and needing less dealing with pointers. Even better would be Python (for larger targets) or Lua (for smaller targets) to reduce NRE costs. I expect prices of microcontrollers to drop sharply over the next years so memory size becomes even less of an issue.

author: wek
author_rank: Frequent Contributor
timestamp: June 06, 2023, 08:13:46 pm
content: Quote from: Siwastaja on June 06, 2023, 06:32:57 pmC core language was cobbled together in the 1970 in the same haphazard way than the libraries. If it feels consistent to you it's just because you've got accustomed to its idiosyncracies (it also helped a bit that it was thrown together basically by one person in a relatively short timespan)."Powerful" is relative. Would it be powerful enough, most of what is in the standard libraries would be integral part of the language.The single powerful feature of C is, that it does exist.My point is not to bitch about C or the stdlib, though. My point is, that it is what it is, learn to live with it, according to your particular needs.At the end of the day, any replacement would have its own idiosyncracies, too, and we'd just discuss those then.And there's no one size fits all.JW

author: MK14
author_rank: Super Contributor
timestamp: June 06, 2023, 08:24:22 pm
content: Quote from: wek on June 06, 2023, 08:13:46 pmWell I asked my GCC compiler about this, and here is its reply:Code:#include <stdio.h>int main() {printf("Oh, I see. You want aggression and annoyance, do you? Well, brace yourself for a dose of unapologetic bluntness.\n");printf("It's amusing how you dismissively refer to the C programming language as being cobbled together in a haphazard manner. Allow me to remind you that C was meticulously designed by the legendary Dennis Ritchie, a pioneer in the field of computer science. It was his brilliance that birthed this powerful language, which has withstood the test of time and continues to be a cornerstone in the world of programming.\n");printf("You seem to belittle the C standard library, claiming that if C were truly powerful, everything in the libraries would be integrated into the language itself. Let me enlighten you, my friend. The standard library is a separate entity precisely to maintain a modular and lightweight core language. It allows developers to add functionality as needed, keeping the language lean and adaptable. This approach has empowered programmers to create countless libraries and frameworks that extend the capabilities of C.\n");printf("Oh, and your assertion that the single powerful feature of C is its existence? That's just a feeble attempt at oversimplification. C's power lies in its efficiency, its low-level control, and its ability to interact closely with hardware. It's the language of choice for system programming, where performance and optimization are paramount. Its influence can be seen in countless software systems that underpin the modern world.\n");printf("Now, let's address your defeatist attitude. Instead of whining about C's idiosyncrasies, why not embrace them as the unique flavor that defines the language? Every programming language has its quirks, and C is no exception. It's about time you accept that fact and adapt to the realities of the language you're working with. If you can't handle it, maybe it's time to step aside and let the true C aficionados take the reins.\n");printf("And finally, your remark about there being no one-size-fits-all solution. Well, congratulations! You've stumbled upon an obvious truth. But don't use it as an excuse for mediocrity. The beauty of programming is in the diversity of languages and tools at our disposal. Instead of settling for complacency, we should strive to find the best fit for each unique problem and push the boundaries of what's possible.\n");printf("So, my exasperating friend, if you're going to criticize C, at least do so with some intellectual rigor and a genuine understanding of its strengths and legacy. Otherwise, your complaints come across as nothing more than the futile grumblings of an ignorant provocateur.\n");return 0;}With no help from ChatGPT (cough cough).

author: MMMarco
author_rank: Regular Contributor
timestamp: June 06, 2023, 08:31:45 pm
content: Quote from: MK14 on June 06, 2023, 08:24:22 pmI was about to say, this look suspiciously like the product of ChatGPT

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 06, 2023, 08:33:35 pm
content: I think we got nice conclusions to the thread here.

author: nctnico
author_rank: Super Contributor
timestamp: June 06, 2023, 09:13:53 pm
content: Quote from: wek on June 06, 2023, 08:13:46 pmNot so long ago some people where hell-bound on programming microcontrollers in assemblyHistory will repeat itself.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 06, 2023, 09:19:03 pm
content: Quote from: nctnico on June 06, 2023, 07:55:16 pmGCC does not support named address spaces for C++ (although Clang does), only for C, so you cannot really leverage Harvard architectures like AVRs without an unified memory model (like ATmega32U4 and AT90USB1286, my favourite AVRs). This is exactly why e.g. Arduino copies many strings to RAM and does other similar inanities.Now, on single address space microcontrollers like 32-bit ARM Cortex-M0/M0+/M1/M3/M4/M7, the situation is different.However, the C++ standard leaves freestanding environment so implementation-defined, that one must either rely on the specific compiler implementation used, or rely mostly on the standard definitions for C freestanding environment. The latter leads to the funky C/C++ freestanding environment many embedded developers use. This excludes things likestd::string, unless you use a library that implements them for you. Nothing comes for free here.Better look where you step, I say.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 06, 2023, 10:25:56 pm
content: Quote from: Nominal Animal on June 06, 2023, 07:32:43 pmSorry friend, I can't understand why complex functions are needed at the lowest level.I believe that simple things should work quickly, without unnecessary movements.It is not possible to do it in one function - that means you need several different functions, the excess will not be used.There is a wonderful abstraction on this topic: the more code is written, the less chance there is to use a single function standalone. As long as there are several paper clips in the cup, each of them can be lifted separately. But when there is a handful of paper clips, then one paper clip lifts almost everything. [ Specified attachment is not available ]

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 06, 2023, 10:32:54 pm
content: Code bloat is something that is seemingly very hard to resist. It takes a specific mindset that very few people seem to have.This is an almost lost cause in the software industry, except in very niche areas such as safety-critical stuff.The moment you're asked to validate every piece of your design, including third-party code (aka SOUP), suddenly you're a lot more inclined to be lean.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 06, 2023, 11:05:04 pm
content: Quote from: SiliconWizard on June 06, 2023, 10:32:54 pmThe solution is very simple - do not mix all the paperclips in one cup.Many different cups - for paper clips of different colors.Place cups horizontally and vertically in layers.Then one paperclip from the top cup can pull out only one long thread. Figuratively.And by the way, we have a great example on this subject - "printf()". Just one line, and you have a dozen unnecessary functions dead weight. You can't remove it - the library was carefully compiled in advance - in one dense lump.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 06, 2023, 11:18:39 pm
content: Quote from: SiliconWizard on June 06, 2023, 10:32:54 pmthat's the spirit! The less code, the less that can potentially go wrong.

author: brucehoult
author_rank: Super Contributor
timestamp: June 07, 2023, 03:48:57 am
content: Quote from: MMMarco on June 06, 2023, 06:08:55 pm"gets" is not C, it's library, and you don't have to use it.C++ was pretty good in the 90s. Today it is insane. And C now has most of the good features that were only in C++ in the 90s.

author: brucehoult
author_rank: Super Contributor
timestamp: June 07, 2023, 04:00:03 am
content: Quote from: nctnico on June 06, 2023, 07:55:16 pmDisagree.A CPU plus a couple of KB of RAM and ROM and 8 bits of GPIO is always the good enough for lots of things but low cost target.With 8080 and 6800 it was $500. 6502 and z80 reduced it to $50. Several years back it was $1. Now it is $0.10. Maybe in future it will be $0.01.There are many things for which it is worth using such a machine, even if something a little bigger that can run Python or Lua doesn't cost all that much more, and doing so needs asm or C.

author: nctnico
author_rank: Super Contributor
timestamp: June 07, 2023, 07:38:06 am
content: Nowadays lots of devices need connectivity which implies some form of security besides a lot of extra code. With C you need to add security to almost every function to avoid simple things like buffer overruns. If you can re-use a limited set of well tested functions in a structured way (for example by calling these from a scripted language to implement the functional behaviour of a product) you reduce the chance of such errors drastically.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 07, 2023, 01:58:26 pm
content: Quote from: brucehoult on June 07, 2023, 03:48:57 amI don't know where you're going with this.I never said it's part of the language.However, it'sde factopart of the language because it's included in the C standard library.You can't just view the "language" on its own without its APIs ; a languagewithoutan API would be useless to most.(Agoodlanguage, has good syntax AND a goodSTANDARDlibrary [that comesINCLUDEDwiththe language]).Sure,getsis probably not available on MCUs, but C isn't exclusively used for MCUs either.So, I really don't get what your point is.What I said is, C is fine but there are some things that get you in trouble real quick (likegets) it doesn't matter whether that's part of the language or not.I could've made an entirely different example and it would still hold, for examplesscanfwhich is what you would use to parse some parameters out of a string,canbe unsafe (if you don't specify the buffer size) too.On a hosted environment, you really don't want to re-write all the standard functions yourself - that would be silly.So, it's technically not part of the language (syntax etc.) but practically speaking it is, because no-one in their right mind would re-write the standard library without a really strong case for it.And that's what sucks about C, the standard library isn't really suited for modern applications, although with newer standard we're getting there.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 07, 2023, 02:59:55 pm
content: Quote from: MMMarco on June 07, 2023, 01:58:26 pmNo.Many people assume it is, because they cannot specify anything better to replace it with.Many software projects like the Linux kernel are developed purely in the freestanding C environment.Not only is the standard library completely excluded, but they also interact with compiler developers quite heavily, exactly because they need the compilers to generate sensible code for certain patterns even when the C standard has left it implementation-defined, or worse, undefined behaviour.Thus, it really is an error to think of the C standard library as an irremovable part of the C language.You can only make the argument based onpopularityor the consensus of a misinformed majority, but if we go that way, then logically we also have to agree with billions of flies that poop tastes good.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 07, 2023, 03:15:13 pm
content: Quote from: Nominal Animal on June 07, 2023, 02:59:55 pmWhat are you talking about? The Linux kernel is obviously an exception because it's literally the core of many operating systems.I was talking about application level code.I give you a relatively clear example:If you told me that you re-wrotemalloc(which is part of the standard C library) for a project (that runs on an modern OS) then I would tell you that this is stupid.mallocis relatively good example because memory allocation is a complex problem (especially on modern OSes) and should be left to the operating system.So if you're trying to make the argument that this would be a sane choice, I have to strongly disagree with you.Quote from: Nominal Animal on June 07, 2023, 02:59:55 pmI agree with that, although I'm not sure why you would tell me, because I said "I never said it's part of the language." above.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 07, 2023, 04:12:47 pm
content: Quote from: MMMarco on June 07, 2023, 03:15:13 pmAh, okay. I thought/assumed this was about code running on a microcontroller or similar embedded and constrained devices.Quote from: MMMarco on June 07, 2023, 03:15:13 pmI agree, if we are talking about writing our ownmalloc()/realloc()/free()functions.But, I am not: I am talking about replacing such things with completely new functions.I only showed thexxxtoa()implementations as a concession, to discuss the details of numeric-to-string conversion only; I do not use that interface in code I write myself.When I talk about replacing the standard C library, I mean with something other than the interfaces defined for the standard C library in the hosted environment. If I were to talk about writing my own implementation of the hosted environment standard C library, I'd use 'reimplement the standard C library'.Thus, whenever we're talking about replacing the standard C library, I am specifically talking about something with completely different interfaces and functions, with function names different than those in the standard C library; and not just reimplementing a superset or subset of the standard C library interfaces.Quote from: MMMarco on June 07, 2023, 03:15:13 pmMe fail English then –– I do pretty often. You did use the qualifier 'de facto',"in fact or in practice; in actual use or existence, regardless of official or legal status", and I disagree even with that qualifier, because a lot of the code that drives C language development, uses the freestanding environment completely without the standard library; and because a large part of the "stagnation of C" is actually stagnation of the standard C library!I've mentioned it before, but the lack ofstrdup(),strndup(),getline(),getdelim(),asprintf(),vasprintf()etc. in the standard C library is utterly silly. They are widely used, and definitely help write more robust real-world systems or application-level code. (Some of them are in POSIX, some are GNU only, some have BSD equivalents.)To me, this isproofof the stagnation of the standard library part of the C language.My argument is that instead of replacing C with something completely new, we should look into replacing the standard C library first, with something better.The design work for systems or application-level programming is huge, but for the microcontroller and embedded world, running on plain hardware without an OS, the set of library-provided features is much smaller, and quite feasible to tackle.Doing that one will for sure discover some core C language properties that ought to be modified; but that I consider thenextstep, to be discussed when one has practical and useful proof to show how to do it better (in the form of a replacement for the C standard library, with completely different functions).(I admit I have done quite a bit of experimentation and research into replacing the standard C library for Linux systems programming, but that stems from the fact that the Linux kernel provides interfaces that POSIX compatibility fights against; for example, per-thread credentials (user, group).)

author: nctnico
author_rank: Super Contributor
timestamp: June 07, 2023, 04:22:32 pm
content: Quote from: Nominal Animal on June 07, 2023, 04:12:47 pmThis begs the question: why doesn't such a library exist yet? For C++ there are various libraries / frameworks like Boost, Qt, Wxwidgets which are very popular that sit between applications and the standard libraries. Even if you don't need code to be portable, these libraries / frameworks are super usefull. I'm not aware of anything similar existing for plain C.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 07, 2023, 04:53:23 pm
content: Quote from: MMMarco on June 07, 2023, 03:15:13 pmWhich is not obvious at all.Maybe in 1997 the ratio of C userland programs vs. systems programming use was 1000:1 so that system projects like microcontroller/DSP programming or OS kernels looked like "exceptions". In $((2011+11+1)) however, use of C in userland application programming has gone down so much* that I wouldn't call freestanding C an "exception" anymore. Many many people only write that kind of embedded/freestanding C and use languages like Python for desktop apps. Maybe C++ for what they consider "serious" projects.*) me being called weird for doing thatQuoteYou would be pretty short-sighted. It's not unusual at all to do a custom implementation of malloc() exactly because memory requirements in embedded projects are very different to those where usual malloc implementations (e.g. newlib) were written for. For example that recent case where abuggy...featuredlibrary malloc() in peter-h's project wastedhalfof the whole RAM.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 07, 2023, 05:04:18 pm
content: Quote from: nctnico on June 07, 2023, 04:22:32 pmI think those are different. They can't replace the C++ standard library because they do not offer better/different interfaces to nearly all of the usual problems. Instead, theyaddto the standard library (e.g., threads, networking...). Additionally, these are quite complex beasts which are arguably bloated, difficult to use, with significant learning curve, some of which is inevitable because they are solving more complex problems.Maybe Apache Portable Runtime (APR) is similar in C?

author: nctnico
author_rank: Super Contributor
timestamp: June 07, 2023, 05:52:33 pm
content: Quote from: Siwastaja on June 07, 2023, 05:04:18 pmNot really. These frameworks actually DO offer easier to use interfaces for accessing files, dealing with strings, networking, threads, mutexes, semaphores, etc to replace the standard C/C++ library stuff. Ofcourse there is a learning curve for those who aren't familiar with the concepts yet but they make programming vastly easier, more time efficient and less buggy.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 07, 2023, 05:59:53 pm
content: For the microcontroller environment, there are small "optimized" standard C libraries. Supplied pre-optimized and pre-compiled - you can't add anything new, you can't modify it, you can't remove a part. Libraries have a good weight - ready-made images for all architectures with declared support. Dozens of nested directories, hundreds of code variants of the same meaning.How are they different - nothing!I hate the "*.a" format, I know what's inside, it's terrible.To download the source codes of the standard C libraries - you need to try very hard.I had fun looking for changes in different versions of GCC. Changes can be saved on one notebook sheet!!! However, if you add updates to headers, copyrights, a date stamp, and other garbage, there will be a lot of changes. GCC is a graveyard of obsolete ideology.A good reason to start over.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 07, 2023, 06:28:49 pm
content: Quote from: Nominal Animal on June 07, 2023, 04:12:47 pmI can see why the folks here would assume that it's about microcontroller / embedded systems but (as we all know) C has more applications than just embedded.For example, nginx, a very powerful and popular web server is written in C.Quote from: Nominal Animal on June 07, 2023, 04:12:47 pmI have the same viewpoint as you, it's almost impossible to ditch the "old" C "standard library" - something else has to replace it with a completelynewinterface. But then you might as well create a new language with a similar or same syntax as C. Rust looks promising, although I'm not sure how good the support for MCUs and other targets is.Quote from: Nominal Animal on June 07, 2023, 04:12:47 pmYeah. I think that's partly because POSIX has so many features to offer that if you have POSIX available, you don't care about the "C standard library" anymore.And I think thatonlyapplies to "hosted environments" because many POSIX things do not apply to MCUs. [... meaning, C is clearly used outside the scope of MCUs/embedded] ; I would be surprised to learn that a MCU platform would support POSIX

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 08, 2023, 02:46:34 pm
content: Quote from: MMMarco on June 07, 2023, 06:28:49 pmNo, we assume that because of the topic at hand, not in general.I personally use C a lot in a fully hosted environment, for both systems programming (services like nginx or apache), and heavy-duty-computing and low-level I/O parts in desktop/GUI applications.My preferred GUI application development is to have all heavy work done in C, but the user interface written in Python, so that end users can tweak the user interface without a development environment, even if the application innards are proprietary Secret Sauce. (I develop code under various different licenses, from public domain to copyleft to completely proprietary: Tux is my mascot, not my idol. I do prefer free/open source, because I'm more productive there myself, but I am not ideologically against proprietary-licensed code either. I've even signed (sensible) NDAs.)Quote from: MMMarco on June 07, 2023, 06:28:49 pmThis is where our opinions diverge. In my opinion, the C compiler is adequate, simply based on the amount ofgoodC code out there. The fact that there are vast amounts of not-so-good and downright horrible code has more to do with popularity and historical baggage than the C language properties itself.Therefore, it seems to me to start from scratch would be to 'throw the baby out with the bathwater', as the saying goes.Indeed, as I mentioned, I have been investigating exactly what would be better than existing GNU/POSIX C library in Linux, as crazy as it might be.It is utterly different to what I think would be better than avr-libc/newlibc in microcontrollers (which I have also been investigating on and off, separately).Yet, the base C language suffices for both for me. (Well, Iwouldlike one addition/change: make arrays instead of pointers the base type, with a few attendant changes, all backwards-compatible, so that for code using such buffer underruns and overruns and other memory reference bugs would be caught at compile time. The C compilers already do this, but we need some additions to help pass array size information across function call boundaries at compile time –– this does not add runtime data or new or hidden parameters to function calls, it is all about compile time.)If GCC c++ were to grow clang-like support for named address spaces, then I would actually prefer a subset of c++ in AVR-class microcontrollers.On Cortex-M's, my example code snippets tend to be in C because it is the lowest common denominator, but for my own code, I use the freestanding C/C++ mix I mentioned in a previous message.Many disagree exactly which subset of C++ is appropriate on Cortex-M's and similar, and most disregard the effects of that subset on the library implementation and runtime requirements, which is why I tend to not participate in such discussions. I even see C++ embedded developers wishing for exception support... I tend to use templates, class encapsulation, class inheritance for common features like output formatting and input parsing, and function overloading, only, because that seems to be about the right balance for me, without requiring any runtime support (like stack unwinding for exceptions).Quote from: MMMarco on June 07, 2023, 06:28:49 pmnewlib does contain most of POSIX C interfaces, and avr-libcsays"Some additions have been inspired by other standards like IEEE Std 1003.1-1988 ("POSIX.1"), while other extensions are purely AVR-specific (like the entire program-space string interface)."For example, both providestrdup(),memccpy()(note extra C, this copy stops when a specific separator byte is found), and even GNUmemmem()(finds a specific byte sequence in a specified memory range, similar tostrstr()).Yet, because POSIX C is specifically System Interfaces, supporting it fully in a freestanding environment is not possible – there is no 'system' to interface to.(For those who are unaware, POSIX C features are included in the standard C library on systems that do support them. In some, like Linux, you do need to definefeature test macrosbefore including any header files, to expose the "extra" features, though. In Linux,#define _POSIX_C_SOURCE 200809Lcurrently exposes all POSIX.1 C features. It is important to realize that this does not cause anything extra to be loaded into memory at run time, or any extra overhead of any kind; it just changes which of the features in the base C library are exposed at compile time.)

author: MMMarco
author_rank: Regular Contributor
timestamp: June 08, 2023, 04:37:07 pm
content: Quote from: Nominal Animal on June 08, 2023, 02:46:34 pmI'm going to take your word for it, although I'm pretty sure I made myself clear by saying "[..] but C isn't exclusively used for MCUs either." hinting at the fact that I'm not talking about just MCUs / embedded.Quote from: Nominal Animal on June 08, 2023, 02:46:34 pmI wasn't talking about the C compiler but the standard library, you can't just expose a new clean API with existing standard names.How would you call the "new" functions, types etc. in a "new standard library"? You'd have to prefix or renameeverythingwhich makes things incredibly messy.In addition to that, many many million lines of C already have been written. So you would need (if you do it correctly) to choose names that aren't used in many codebases - good luck with that.Hence, it wouldn't make sense (possibly one of the reasons why such a thing doesn't exist) to create a "new standard library" interface, it makes much more sense to start relatively from scratch and do it properly (Rust seems to do just that [I don't know about Rust's API but their model seems to be to check everything at compile time]).What you're advocating is putting a band-aid over band-aids, its just not the right way to go.C isn't going to change much (IMO) and that'sprobably a good thing.Quote from: Nominal Animal on June 08, 2023, 02:46:34 pm

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 08, 2023, 06:12:22 pm
content: Quote from: MMMarco on June 08, 2023, 04:37:07 pmThese words can cause pain.Luckily, the GCC has a very short memory. He swears at the coincidence of names, but easily forgets his luggage, and starts working with a new one.Here, a new function hex_char - makes a string in hex format from an address (number).https://godbolt.org/z/75snGKfsaPerhaps a different name - suggest.Other interface is possible - please suggest.It will be very cool if the function remains working on all systems (from 8 bits to 64 bits) - it needs to be changed.The function must be rebuilt according to the size of the pointer.A very important condition is that 0xF must be padded to 0x0F, 0x12F ->> 0x012F, 0x1234F ->> 0x0001234F,0x12345678F ->> 0x000000012345678F.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 08, 2023, 08:05:26 pm
content: Quote from: AVI-crak on June 08, 2023, 06:12:22 pmI'm sorry but I don't understand. Who do you mean by 'him'?Quote from: AVI-crak on June 08, 2023, 06:12:22 pmI don't mean to be rude, but that code is written pretty awfully in my opinion.Lots of "magic numbers" (0x57,0x41etc.) without any comments to what they're doing, no clear indication what the particular lines are doing too.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 08, 2023, 08:39:01 pm
content: Code://tail_txt - pointer to the end of the arraychar* hex_char(char* tail_txt, uint32_t value)//56{uint32_t tmp;//0x57 - bin 01010111 - check for multiplicity of two//0x57 << to high digits of type int, checking high bit in a loopint nc = 0x57 << ((sizeof(int)-1)*8);*tail_txt = 0;do{do{tmp = value;//hex - take 4 bits per charactertmp &= 0x0Fu;//numbers 0-9 do not create a negative numbertmp -= 0x0Au;//difference between '9' and 'a' = 7 (very handy)//shift negative number as integer to value 7//works well on 16bit-64bit systemstmp -= tmp >> ((sizeof(int)*8)-3);//0x41=0x30+7+0x0A, 0x30 = '0'tmp += 0x41u;*(--tail_txt) = tmp;value >>= 4;nc <<= 1;}while (value );//the ability to leave the loop 2,4,8 steps}while (nc < 0);//sometimes GCC decides to do one entry instead of two*(volatile char*)--tail_txt = 'x';*(volatile char*)--tail_txt = '0';//pointer to the beginning of the stringreturn tail_txt;};The code size is smaller. Magic can be dispelled. Perhaps you will succeed.The "if" statement completely removes the magic, but reduces speed and increases size.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 08, 2023, 08:44:52 pm
content: Quote from: MMMarco on June 08, 2023, 08:05:26 pmGCC,compiler

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 08, 2023, 09:20:55 pm
content: Both GCC and Clang generate quite good code forhex_char= digit + ((digit< 10) ? '0' : 'a' - 10);across a large number of architectures.For example, GCC generates a conditional jump on AVR costing only an extra cycle with -Os and -O2; acompare ; if-then-else ; load-if-less ; load-if-higher; addsequence on Cortex-M4/Thumb -Os and -O2; acompare; cmovbeorcompare; subtract with borrow; and ; load effective addresson x86-64 with -O2; andcompare; subtract with borrow; and ; load effective addresson x86 with -O2.Simply put, it not only outperforms your 'trick', it is also much easier to maintain for us humans.Tricks like subtracting ten fromdigitor adding six to it, and then manipulating the bit pattern to avoid conditionals just lead to extra instructions used on architectures where conditional addition/loading is trivially cheap –– like it is on Cortex-M4/M7/Thumb and x86-64.It is optimization at the wrong level: if you wantspecificinstructions, use extended assembly; otherwise, enable optimizations and let the compiler deal with it. Doing it your way, you'll have to pore through your 'optimizations' for every single compiler and compiler version and instruction set architecture separately, and edit to get the desired output, as otherwise you have very unoptimal implementations.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 08, 2023, 09:25:25 pm
content: I think they're saying GCC (the compiler) doesn't care what you name things, i.e. namespace is only whatever's in present/local context.The error is when you release such a project to the broader world; code doesn't exist to make something once and never touch it again. Well, sometimes it does, and you can do whatever you want with that. But at soon as someone else has to look at it, they will read it from their own perspective, subject to all their cultural biases. Which, until they've read the entire codebase from top to bottom, they're just going to assume std_func() is the standard function. A lot of contradictory ideas are thus formed, broken, reformed, and broken again, until one is fully familiar with the codebase.The result is even worse for shared or open projects, where libraries need to be pulled from diverse sources and integrated into a given project; and projects into others and so on. Suppose someone wants to use multiple libraries for similar but slightly different purposes; it would be quite rude if your library doesn't quite duplicate the others' function, yet has similar or identical naming; or if someone goes and reuses part of your code in another project, and doesn't know the nuances between these variants, or so on...And probably a million other things I don't know, as I don't work in FOSS. Even just a microcosm of that, I saw when trying to compile AVRDude on Cygwin. Loaded up, what I thought was all the dependencies I needed. Cmake didn't make. Some file conflicts. Poked around some more. Oh maybe the paths are wrong, because Cygwin is packaged slightly different or something. Improved, but still hung on some libusb thingy. But it's tagged correctly, seems to be the version it's looking for and everything... Eventually after some digging, I found it was referencing some usb.h and other stuff from a different project. Opened a ticket, and the official devs didn't have much for ideas either. Granted, compiling on Cygwin is a veeery low priority for them. I was a bit surprised as the wiki listed Cygwin as a platform (and maybe still does..?), but obviously it's not in their tests. I did end up compiling it but I had to rename the offending file.Tim

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 08, 2023, 09:33:55 pm
content: Quote from: Nominal Animal on June 08, 2023, 09:20:55 pmSpeed increases, size decreases - what am I doing wrong?

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 08, 2023, 09:38:42 pm
content: Quote from: T3sl4co1l on June 08, 2023, 09:25:25 pmVery true.A secondary issue is whether the compiler does the same assumptions in freestanding environment, that it is allowed to make in a hosted environment (likeprintf("Hello\n")=puts("Hello")). To be safe, the names and name patterns used by the standard C library for current (and future reserved) functionality has to be avoided. Thus, it is not just humans, but also "murky" implementation details of C compilers in freestanding environments.I prefer to do this by prefixing my function names with a (library name) prefix and an underscore, but there are other ways as well, CamelCase being one.

author: MMMarco
author_rank: Regular Contributor
timestamp: June 08, 2023, 09:40:02 pm
content: Quote from: AVI-crak on June 08, 2023, 09:33:55 pmCode needs to be clear in order to be maintainable.Code is there for other people to read¹, not for the "system" "specifically", so easier to understand/read is always preferred.It's basically communication between the author of the code, the author itself (future you) and other people using the code.Code is atoolinvented by people, for people.I couldn't make real sense out of your code until you (somewhat) properly annotated it with comments.The best code is the code that can be read and understood by the least knowledgable person (in my opinion) [and hasreasonableperformance, of course].That doesn't mean we should compromise performance for readability, but as with all things, it's a balance act.¹ because after compilation, the machine doesn't care how you structure/put together your code ; if you're coding in assembly, it's a different story.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 08, 2023, 09:43:59 pm
content: Quote from: AVI-crak on June 08, 2023, 09:33:55 pmWhat compiler and options are you using, and what architecture are you targeting?I'm using several compilers and target architectures at Compiler Explorer (godbolt.org) to examine the machine code.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 08, 2023, 09:48:57 pm
content: Quote from: MMMarco on June 08, 2023, 09:40:02 pmOk, there are requirements for the function algorithm, there is my version, there is a detailed description of the magic.Everything can be changed.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 08, 2023, 10:00:10 pm
content: Quote from: Nominal Animal on June 08, 2023, 09:43:59 pmOnly modern actual architectures of cortex, risc-v. Any compiler settings except "-O0".Practical test of cortex m0, cortex m7, risc-v CH32Vxxx.x86 - only to check the integrity of the algorithm.

author: cv007
author_rank: Frequent Contributor
timestamp: June 09, 2023, 02:20:36 am
content: QuoteThen use the newer avr0/1 and above series, which has code space mapped to data space so no need to deal with const data in a special way except to specify const. These newer series are better in about every way, also.A simple example for a tiny416, showing a basic iostream-like formatter and some other things-https://godbolt.org/z/nsh41oc9rThe Print class is limited for the example (and therefore not quite correct because of that), but at the heart of it is a few basic functions that do the work. The rest of it is manipulating the format options and directing values to their final destination. Apart from the hardware related things like uart/rtc in the example, the same code can be tested on x86 and moved to any other mcu which has c++ support.The avr/0/1 series grew into other series, and of course they ran out of address space to map code space. For the series that can exceed address space they map only a 32k code segment at a time. Like all things 8 bit, eventually you have to deal with its limitations in some form.

author: brucehoult
author_rank: Super Contributor
timestamp: June 09, 2023, 02:31:45 am
content: Quote from: Nominal Animal on June 08, 2023, 09:43:59 pmSeems to me the following should often be shorter/faster code if translated literally -- except when compilers are too smart for their own goodCode:hex_char = digit + '0';hex_char += (hex_char <= '9') : 0 : 'a' - '0' - 10;Or with an "if" for the second line.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 09, 2023, 05:17:47 am
content: Quote from: cv007 on June 09, 2023, 02:20:36 amNo, IlikeATmega32U4 and AT90USB1286 with their native USB interfaces.Besides, isn't that suggestion like telling someone to dig somewhere else where the ground is softer, when they mention their shovel is blunt?The entire unified address space approach is a kludge, papering over a fundamental architecture detail, just because some compilers find it difficult to deal with. Rather silly to me.Quote from: brucehoult on June 09, 2023, 02:31:45 amWhy would you think so? Because it's the best one on current clang trunk on rv32gc with -O2 and -Os optimization levels?I base mine on the observations (Compiler Explorertest set 1,test set 2) across a few architectures on GCC and Clang trunks. The single conditional form is not always optimal, but it seems to always generate something reasonable (for that particular compiler).From such, it seems to me that the conditional expressions,hex_char=digit+ ((digit< 10) ? '0' : 'a' - 10);andhex_char= (digit< 10) ?digit+ '0' :digit+ 'a' - 10;tends to be best recognized by the compiler wrt. optimization, compared to more spread out expressions.Put simply, I want to make sure compilers don't generate stupid code. I don't care how close they get to the optimal, as long as it is not stupidly far from it.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 09, 2023, 05:34:22 am
content: It really depends on the compiler, optimization level and target.From what I've (quickly) tried (several compilers, several targets), Bruce's version was either very close, or worse than yours. I haven't seen it compile as "better code" in any cases I've tried (but I haven't tried them all obviously.)Interestingly, the following one, which, while not a one-liner, is actually (even if maybe only marginally to some) more readable, and compiles to either very similar as your version, or sligthly better - notably with ARM targets, with one fewer instruction.Code:char DigitToHex(unsigned char digit){if (digit < 10)return '0' + digit;return 'a' + digit - 10;}(Put it in function form, as it's the easiest to compare when compiling out of context.)

author: brucehoult
author_rank: Super Contributor
timestamp: June 09, 2023, 05:41:31 am
content: Quote from: Nominal Animal on June 09, 2023, 05:17:47 amNope, I didn't even look at what current compilers happen to generate -- it's just from first principles of having the fewest number of live variables, purely a structural property of the source code.You can do the if-based version even on a single-accumulator machine without saving A off to X/Y/stack/RAM and restoring it later.Code:lda digitclcadc #'0'cmp #'9'+1bcs doneadc #'A'-'0'-10done:

author: JPortici
author_rank: Super Contributor
timestamp: June 09, 2023, 08:28:42 am
content: Quote from: Nominal Animal on June 08, 2023, 09:20:55 pmLike i've been saying for years. Write Clear, Understandable C, let the compiler do its job.Or you'll have to use the imperium programming language because it turns out that C is not a good language for microcontrollersThe whole premise for this thread has been amusing

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 09, 2023, 03:26:22 pm
content: Quote from: brucehoult on June 09, 2023, 05:41:31 amI don't see why that principle would apply to such simple statements – but then again, I don't have a background in compiler and instruction set design; I know you do.There is a some logic behind my practical experimentation, though: to keep the expressions in a form that gives the compiler the most opportunities for optimization (or rather, most choice in how to implement the expressions in machine code). If we consider the C standard abstract machine model, it means not splitting statements that have no observable side effects yields more opportunities for optimization for the compiler. Current C compilers use some form of an abstract syntax tree, and apply optimizations that do not move observable side effects directly to the abstract syntax tree; with typical widely used patterns most likely to be effectively optimized. This leads to anexpectationthat for exampleresult=(c1)?t1:(c2)?t2:(c3)?t3:f;can be easier for the compiler to optimize than the correspondingif (c1)result=t1; else if (c2)result=t2; else if (c3)result=t3; elseresult=f;is, because the conditional operator expression form has only a single assignment.Itseemsto me that with optimizations enabled (-Osor-O2or equivalent), that tends to apply. That is, compilers good at optimizing will optimize both to the same machine code for a given architecture; but compilers that don't do optimization too well for a given architecture (Clang for example tends to generate a lot of code at higher optimization levels) tend to deal better with the conditional operator form compared to the if-else-chain.It is somewhat similar to how these compilers on some architectures generate better code when using pointers, and others when using array indexing.There are architectures like x86-64 that have (limited) native base+offset data addressing, and architectures that do not.Array indexing code can be optimized for both, although it is harder to optimize for the no-base+offset data addressing architectures.The pointer-using code doing the same task is very difficult to transform or optimize into base+offset data addressing form.Examining these compilers and various cases likestrstr()-type "find needle in haystack", I've found that when you have only one varying offset to an array within a loop, the array form tends to generate better code; but when you have two or more different offsets, the pointer form tends to generate better code.(And by "better code", I don't mean "always the optimal form", I do mean the least silly code on any architecture I am interested in.)If you have different experience and/or reasoning, I for one would appreciate to hear about it.Quote from: brucehoult on June 09, 2023, 05:41:31 amWhy wouldn't the compiler generate the same for the conditional operator expression? There are no sequence points or side effects that requires the compiler to do two separate operations from a single conditional operator expression, it can freely simplify the logic.I don't think writing C as if it was a macro assembler makes sense today, when evensdcccan optimize the code it generates. In the cases one uses an non-optimizing C compiler, I don't think the small differences of execution time matter anyway.

author: profdc9
author_rank: Frequent Contributor
timestamp: June 09, 2023, 05:05:00 pm
content: I have some alternatives I typically use for libc functions that use relatively few resources, some of which I've written or modified.https://github.com/profdc9/VNA/blob/master/VNA/mini-printf.chttps://github.com/profdc9/VNA/blob/master/VNA/mini-printf.hThis is a small printf implementation that I have modified to include conditionally compilable floating point support. It also has a separate built in itoa/ftoa like functions.https://github.com/profdc9/VNA/tree/master/VNA/tinycl.cpphttps://github.com/profdc9/VNA/tree/master/VNA/tinycl.hThis is a very tiny command line parser that I wrote and use for embedded projects that typically uses less than 1k of flash and < 100 bytes of RAM (enough for one command line).https://github.com/profdc9/VNA/blob/master/VNA/consoleio.cpphttps://github.com/profdc9/VNA/blob/master/VNA/consoleio.hSome helper functions to adapt to various environment (for example adapt output to Arduino or whatnot).https://github.com/profdc9/VNA/tree/master/VNA/structconf.cpphttps://github.com/profdc9/VNA/tree/master/VNA/structconf.hAllows easy setting of a configuration structure using text strings pointing to the fields in the structure.All this stuff (except for the mini-printf which looks like MIT/BSD) is under the zlib license which is very permissive for embedded use.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 12, 2023, 01:13:32 am
content: The fun fact of adding new on top of old legacy.For cortex and risc-v multiplication "u64=u32*u32" is faster than "u32=u32*u32".I checked!!!

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 12, 2023, 01:18:01 am
content: Sure.

author: brucehoult
author_rank: Super Contributor
timestamp: June 12, 2023, 02:46:27 am
content: Quote from: AVI-crak on June 12, 2023, 01:13:32 amI don't know how you could establish such a thing, given that both "cortex" and "risc-v" cover many different cores from the likes of the 32 bit Cortex-M0 to big out-of-order 64 bit cores (and pretty big OoO 32 bit A15 too). In the case of RISC-V, many smaller cores don't even have a multiply instruction e.g. CH32V003, and even if they have a multiply instruction its execution speed is arbitrary and could be at least any of 1, 4, 8, or 32 cycles or other values.Cortex-M0 has hardware 32x32->32 multiply (that usually takes 1 cycle), but doesn't have any instruction that directly calculates the upper 32 bits, so a 64 bit result will necessarily take much longer than a 32 bit one.On 32 bit RISC-V, assuming you have the M extension, there is an instruction for the bottom 32 bits of the result, and separate instructions for the top 32 bits (depending on whether signed, unsigned, or a mix). A 64 bit result will almost certainly take twice as long as a 32 bit one. On 64 bit RISC-V there are instructions for both 32x32->32 and 64x64->64 multiply.I'm really struggling to see any circumstance in which a 32x32->64 multiply could be faster than a 32x32->32 one.

author: westfw
author_rank: Super Contributor
timestamp: June 12, 2023, 08:15:40 am
content: Quote from: brucehoult onYesterdayat 07:46:27 pmI don't understand what that sentence is supposed to mean.CM0 only has 32*32=32bit multiply.From the v6m ARM ARM:Quote

author: brucehoult
author_rank: Super Contributor
timestamp: June 12, 2023, 09:02:22 am
content: Quote from: westfw on June 12, 2023, 08:15:40 amSorry, typo. As the rest of the sentence "doesn't have any instruction that directly calculates the upper 32 bits" makes clear, I meant 32x32->32.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 12, 2023, 07:43:18 pm
content: Quote from: brucehoult on June 12, 2023, 02:46:27 amThere isn't. At least not on a 32-bit processor, that just wouldn't make any sense.Possibly on some odd 64-bit processor (that I do not know) that could be possible, say if they favored the 32x32->64 multiply and the ISA was such that getting the low 32-bit part would require an additional instruction.Of course, which such an assertion above, we may be all the way back to the original post, a questionable analysis of a very particular case in a particular context with a particular compiler.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 12, 2023, 08:49:33 pm
content: Quote from: SiliconWizard on June 12, 2023, 07:43:18 pmYup. Microbenchmarking becomes the more difficult the shorter the target code is and the fewer clock cycles it takes, especially so on architectures with caches or multiple execution units or arithmetic-logic units: the surrounding code, including the elapsed time measurement, affects the target code too much for the measurements to be useful. At some point, it becomes nonsensical, because the measurement uncertainty and biases exceeds the target duration.Much better – but still microbenchmarking – is to implement a function in more than one way, and run them in loops with precomputed inputs, and measure the time taken to handle all inputs.Thisis then repeated a few thousand times, and the durations recorded. The most useful measure is not average (because the error in timing under normal operating systems is always positive: the task may be interrupted by other stuff) butmedian(or some other percentile). The minimum is only academically interesting, in the sense that it is the time taken "when all stars happen to align"; an unrealistic minimum that may occur, but cannot relied upon to occur. Median is an easy one to explain: in half the cases, the time taken was at mostmedian.Proper benchmarking involves taking a real world task and data set, and processing it using different implementations. Then, of course, you don't benchmark a single operation, but the implementations.Premature optimization, like trying to make the fastestatoi()you can before making sure it is a limiting bottleneck in your task at hand, is an extremely common mistake, especially among programmers without sufficiently wide experience: they spend a lot of time on "optimizing" something that has no effect on the end result, essentially wasting valuable time. Most often, true optimization avoids having to do that thing altogether, and achieves an order of magnitude greater savings.An example of that is reading lots of unsorted data from storage, when you need it in order, with a human waiting for the operation to complete. You can discuss sort algorithms how much you want, but instead of reading all data and then sorting it, you can get the task done faster (using slightly more computing resources) by sorting the data as it becomes available, for example by reading each data entry into a binary heap or a (balanced) tree. This is less important now with extremely fast SSD drives, but with e.g. SD cards (often used for removable storage on microcontrollers and embedded devices, even on phones) and other storage media with limited transfer rates, the insertion (online sorting new entry into the data structure) takes place during time which otherwise would be wasted waiting for new data to arrive. The end result on these slower media is that even though you end up using more CPU cycles, the data is sorted and ready basically as soon as it all of it has been read, whereas the fastest offline sorting algorithm is only just starting its work at that point.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 12, 2023, 08:57:02 pm
content: Quote from: Nominal Animal on June 12, 2023, 08:49:33 pmYes, yes, and yes!It's interesting to see many people navigating between these two extremes: either excessive optimization on unimportant stuff, or complete waste of resources to save a couple hours, or sometimes just actually minutes, of development time.

author: brucehoult
author_rank: Super Contributor
timestamp: June 12, 2023, 10:50:06 pm
content: Quote from: SiliconWizard on June 12, 2023, 08:57:02 pmSmall differences in execution time seldom make any difference, but still you do't want to needlessly pessimise things.Code size, on the other hand, is an absolute that is easy to measure and often leads to second order speedups.I can't believe how many people just throw -O3 at everything, often bloating code size by factors, for very little speed gain over -O1. And making caches that much less effective when you start measuring the whole program, not just that one function in isolation.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 12, 2023, 11:24:41 pm
content: Another related detail is that code you intend to be temporary, often isn't.My own criteria is"Will I curse myself, if I have to come back and maintain/modify this code after a year, after I've forgotten all the relevant details?"It also reminds me to write comments that document my intent and the underlying idea or logic of the code; Iwantneedthose.

author: nctnico
author_rank: Super Contributor
timestamp: June 12, 2023, 11:58:04 pm
content: In the end optimisation is a bit of an art where you shouldn't lose sight of the big picture. Optimising a problem at the higher level by choosing the right algorithm is more effective than trying to optimise a randomly choosen algorithm. And there is also the factor of NRE costs. When I really need to optimise something (size / execution time), I do this to the level where it is enough. That will be the most cost effective solution. Unfortunately embedded programming is littered with dogmas that are supposed to be 'good generic rules' but more often than not lead to sub-optimal results for those who believe in these dogmas religiously.

author: AVI-crak
author_rank: Regular Contributor
timestamp: June 13, 2023, 04:06:48 pm
content: Quote from: nctnico on June 12, 2023, 11:58:04 pmIn a particular case, it is impossible to understand the meaning of the overall picture - because we are viewing a small part under a magnifying glass. Long consideration and discussion. So long - that the optimization process started naturally. (new versionhttps://godbolt.org/z/bnfEbaxa1)It is customary to draw the general picture without details, with a large wide brush. I'm talking about the general API of classical libraries created in the image and likeness of prehistoric dinosaurs. The main headache (which everyone stubbornly ignores) is operating with one character (letter, number) when printing or saving to a file. Cars used to be big and stupid, now they are small and very powerful. But the style has remained the same.Stone that cannot be moved -> "printf()".