I deliberately showed the representation of the string in natural form.
The interface and behaviour of functions named
itoa(),
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) in
this thread I started, as well as
replies #68 and
#80 in the current thread.
There are two ways to make the atoi-like interface safer, without wasting memory:
- const char *itoa(const int len, char buf[len], int val, const unsigned char radix);
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 *)buf to (const char *)buf + len - 2), or a constant pointer TOA_ERROR which points to say "(?)" if the buffer is too small (otherwise first char is set to NUL) or radix is invalid.
- int itoa(const int len, char buf[len], int val, const unsigned char radix);
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 (with buf[0] == '\0') in case of an error like invalid radix or 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 as
static in a header file, to simply check
len>1 and
radix, and immediately return the error; possibly check
val to handle negative values; and call the optimized radix-10, radix-16, radix-8, radix-2, or generic version (with a nonnegative
val). With
--ffunction-sections -Wl,--gc-sections the unused ones are then not linked in at all, and whenever
radix is 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_uidivmod per 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).
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.
This 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, and
int_fastN_t for
N = 8, 16, 32, and 64 if supported.
Again, my
preference aligns 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, or
you will lead others to write buggy code.
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.
First, 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 of
ys in
xs.
Using mine, if I have earlier checks that verify
-9999 <= x, y <= 99999, I could safely use in a function
char xs[6], ys[6]; (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).)
If you do everything correctly - you get about the same set of functions as mine. Because it's convenient.
No. I suspect a bit of Dunning-Kruger here, because "convenient to the library developer" != "correct".