Hi Steffen, On 2026-04-18T02:10:17+0200, Steffen Nurpmeso wrote: [...] > I never used the standard library until i took maintainership of > that MUA, really. (fprintf(3), but only as the last possibility > in case of debug havoc.) We had (or could) -nostdlib (on Linux; > but needed -ldl), dtors, ctors, (ifuncs), this all not used > (-fno-rtti and all that), so it was wonderful.
Hmmm, then yes, that would count as C89. It's quite rare, as these days
almost every program uses POSIX or C99 library stuff, such as
snprintf(3).
[...]
> |> In my opinion the best C would be the one from Plan9.
> |
> |I have mixed opinions about Plan9 C. The lack of 'const' is really bad.
>
> They have const. (Ritchie said this in the 80s right; yes,
> constant data, in rodata.)
The const qualifier is ignored by the Plan9 C compiler. Quoting
'How to Use the Plan 9 C Compiler' from Rob Pike:
The compilers do their own register allocation so the register
keyword is ignored. For different reasons, volatile and const
are also ignored.
It's essentially as if the C compiler did '#define const'.
[...]
> |> I do not
> |> need more, but what i need, that we do not have from ISO.
> |
> |I'm working on several things that might be quite interesting.
> |
> |There's the _Countof() operator which I added recently. Now I want to
> |extend it to array parameters.
> |
> | int
> | func(int n, int a[n])
> | {
> | return _Countof(a); // equivalent to 'return n;'
> |}
>
> I must say i was running away somewhat from the examples that
> cheered "generic programming in C". After so-and-so-many typeof()
> constructs on a single line i get confused,
You get used to it... But writing library code really needs that for
safety reasons. Then it's a matter of abstracting at the right point to
minimize confusion.
[...]
> |Things I find essential from modern dialects include static_assert(3),
>
> And that was just unusable from ISO C when it came, for example.
> It was a brainfart. They should simply have taken the exact
> variant that C++ had introduced, at first.
I don't know how it was in C++ originally, but from what I can see in
cppreference, it was added in C++11, and it had the same form that it
has in C11. Essentially, it behaves as if:
void static_assert(bool constant_expression, const char *msg);
(Except that you can use it in file scope, and a few other places
where you couldn't use an expression.)
How was it unusable?
> |_Generic(3), and a few others. They allow writing const-preserving
> |string APIs, for example, as we've seen in this thread. That's an
> |important improvement over C99.
>
> I am not of this opinion. I would think they buy nothing.
> I really tried hard to think if i ever encountered a problem in
> the few years i do such things (it was always C++, with a nice
> string object here, otherwise), and i could not remember one.
We have a real bug that was found here in mutt(1) thanks to C23's
const-preserving strchr(3). Look in the stable branch for this:
commit 3a8dfafc61535270a8a379d0ca9d0ab66edeafc6
Author: Kevin J. McCarthy <[email protected]>
Date: 2026-02-25 11:15:20 +0800
Fix browser examine_directory() writable buffer parameter bug.
Despite being "const char *", the directory "d" parameter was being
modified inside examine_directory() if the directory no longer
existed. The code climbed the directory path until it found where
it
still existed, using the strchr() return value as "char *" to modify
the "const char *" parameter.
Unfortunately, it modified the buffer data directly without updating
the dptr. This could lead to buggy behavior, because subsequent
buffer operations would append to the dptr, unaware that the string
was nul terminated earlier.
Thanks to Rene Kita for finding this issue and to Alejandro Colomar
for his research into the problem.
> I think you use these functions pretty locally, and if you really
> leave that place with a type-qualifier-borked memory buffer,
> especially when passing it around to interfaces that you do not
> control, you should not program in C. Such things just do not
> happen in C++; even if only have one for example find()
> implementation internally, i can multiplex many variants on top
> (string&,string&), const.., (string&,char*), const..,
> (string&,char*,size_t), const.., all with one implementation, and
> the callees can take appropriate steps to ensure "buffer safety".
> This is why i do not like the C interface at all.
Most of the type-generic macros I write in GNU C23 can be as nice as
you'd have them in C++. And we're working in the C Committee to have
nicer ways of expressing these things with less code.
> |You may also be interested in the magic from this macro:
> |
> | #define typeas(T) typeof((T){0})
> | #define mallocarray(...) reallocarray(NULL, __VA_ARGS__)
> | #define malloc_T(n, T) malloc_T_(n, typeas(T))
> | #define malloc_T_(n, T)
> | ( \
> | (void)0, \
> | (T *){mallocarray(n, sizeof(T))} \
> | )
> |
> |which is used as:
> |
> | int *p;
> |
> | p = malloc_T(42, int);
> | if (p == NULL)
> | goto fail;
> |
> |This vastly improves the safety of malloc(3) calls compared to anything
> |you can do with the raw function.
>
> Oh, i have similar ones but without typeof() magic. (sizeof(),
> though. But that is very old.) Of course, this is a longer road
> to go to use them, but so it is.
sizeof() can't give you certain safety guarantees.
This malloc_T() pins the type passed as second argument with the type
of the assigned pointer 'p'. In other words, this results in a compiler
error:
long *p = malloc_T(42, int);
And just to prove:
alx@devuan:~/tmp$ cat m.c
#include <stddef.h>
#include <stdlib.h>
#define typeas(T) typeof((T){0})
#define mallocarray(...) reallocarray(NULL, __VA_ARGS__)
#define malloc_T(n, T) malloc_T_(n, typeas(T))
#define malloc_T_(n, T) \
( \
(void)0, \
(T *){mallocarray(n, sizeof(T))} \
)
int
main(void)
{
long *p = malloc_T(42, int);
}
alx@devuan:~/tmp$ gcc m.c
m.c: In function ‘main’:
m.c:8:1: error: initialization of ‘long int *’ from incompatible
pointer type ‘int *’ [-Wincompatible-pointer-types]
8 | ( \
| ^
m.c:6:27: note: in expansion of macro ‘malloc_T_’
6 | #define malloc_T(n, T) malloc_T_(n, typeas(T))
| ^~~~~~~~~
m.c:16:20: note: in expansion of macro ‘malloc_T’
16 | long *p = malloc_T(42, int);
| ^~~~~~~~
(With older compiler versions, you may need to pass
-Werror=incompatible-pointer-types explicitly. For example, in gcc-11,
I get a warning by default, but it's not a hard error by default.)
If you write this malloc_T() in a library once, and then use it
everywhere, you shouldn't worry too much about it having too much
typeof magic.
[...]
> |Have a lovely night!
>
> You too, dear Alejandro.
:-)
Have a lovely night!
Alex
--
<https://www.alejandro-colomar.es>
signature.asc
Description: PGP signature
