Hello Alejandro.

Alejandro Colomar via Mutt-dev wrote in
 <aeP4WD47Pch3b5UT@devuan>:
 |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).

Libraries seem to have detoriated thus, in that, i think compiling
on a twenty year old system will currently really fail, also
because of snprintf for example.  (Originally there was a "lazy
wrapper" which simply called vsprintf().)
But it is on the list and not forgotten.

That standard format I/O, for example, i never liked, i will
port the "format codec", one day.  (Just a(n) (restartable)
object that does it, being fed from the front instead of
calling pointer-to-function from the back, as some I/O libraries
implemented the *f() series, at one time, at least.  With it,
non-restartable (eg stack buffer) is then simply

        Txt::FormatDecoder      fd;
        ...
        if(_cplen == M1::ui4)
                _cplen = length(_cp);
        (void)fd.setup(_cp, _cplen, _fmt, _valistp)
                .setRestartable(fal0)
                .call();
        _cplen = ((fd.isFinished() || 0 != fd.numberOfConversions())
                        ? fd.numberOfAssignments()
                        : M1::ui4);
        return _cplen;

which is so much better than anything else thinkable.)

 |
 |>|> 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 *think* this is not true for 9front .. and .. note that Pike
said years ago that he ~"has not used a Plan9 system in decades".
So i am not an expert there (unfortunately), but upon interest
i would ask the guys there.
I bcc Cinap[uscore]Lenrek (at felloff dot net) who is at the very
core and heart of all that.  (Cinap Lenrek :sorry, i hope it does
not bother you.)

 |[...]
 |>|>  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.

I would use objects.

 |[...]
 |>|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:

Definetely not.

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

Pfff, in so far:

  /* ISO C variant unusable pre-C23! */
  #elif (!su_C_LANG && __cplusplus +0 >= 201103l) || \
          (su_C_LANG && defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 
202311l)
  # define su_CTA(T,M) static_assert(T, M)
  # define su_LCTA(T,M) static_assert(T, M)
  #else

Sorry but this is all i know.  I tried it, and it bailed.
Ah wait, i meant _Static_assert(), which was ISO C in 2011, and
that was completely unusable.  They should have used the C++
static_assert() right away.

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

Ok, ok.  But interesting is the history of that code place, which
seems to have been touched a little bit only by Kevin in 2022 and
2016, and otherwise mostly came from before Y2K.
So ok, it may have merits, especially in the world of little time
for hobby, patch pumpkins, and many people working on a codebase.
But whether it would have existed if Kevin would have implemented
it from scratch with the dynamic buffer object that he spliced
into the codebase everywhere (great!), or when the codebase would
have been implemented like that from the beginning, that is
another question.
Other than that, as the code and the bug is very local, yes, shit
happens; maybe lesser so with that new ISO C thing.

But i do not like it, it is not backward portable, and the bug as
such would never happen in C++ code, by the very reasoning of
Stroustrup from the mid-80s, so ISO C is *decades* too late with
such things.
And i started playing games with "char* <- char const*" now that
i program C, and quite a bit so.

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

They are not for me.  I want to compile with C++ when i can.

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

Ah, i later knew that would come, i should have given context.

 |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);
 ...

I have myriads of things like

  #define su_MEM_TALLOC(T,NO) su_S(T *,su_MEM_ALLOC_N(sizeof(T), NO))

aka the cast is already included.  And su_S() is static_cast<T*>
when compiled with C++, though, the missing automatic superclass
case unfortunately makes the code very ugly, and very often one
has to use R() (<> reinterpret_cast<>) where it would be a very
smooth transition otherwise.
Like i said, it is, to me, very unfortunate that ISO C ran wild,
and that ISO C++ ran wild long before that, in that it is not
a layer on top of the current ISO C standard.

 |And just to prove:
 ..

This works for my macros, too.  But it is a hell of a lot to
write, of course.

Btw the macros go over great lengths to support location info

  #define su_MEM_ALLOC_N(SZ,NO) su_MEM_ALLOCATE(SZ, NO, su_MEM_ALLOC_NONE)
  ...
  #define su_MEM_ALLOCATE(SZ,NO,F) su_mem_allocate(SZ, NO, F  
su_DVL_LOC_ARGS_INJ)
  ...
  EXPORT void *su_mem_allocate(uz size, uz no, BITENUM(u32,su_mem_alloc_flags) 
maf  su_DVL_LOC_ARGS_DECL);
  ...
  # define su_DVL_LOC_ARGS_DECL , su_DVL_LOC_ARGS_DECL_SOLE
  ...
  # define su_DVL_LOC_ARGS_DECL_SOLE \
        char const *su_DVL_LOC_ARGS_FILE, su_u32 su_DVL_LOC_ARGS_LINE
  ...
  # define su_DVL_LOC_ARGS_INJ_SOLE __FILE__, __LINE__
  # define su_DVL_LOC_ARGS_INJ , su_DVL_LOC_ARGS_INJ_SOLE

in a portable and ISO C89 compatible way.  One reason why i am an
absolute fan of the complete separation of debug/development and
shipout versions, which do not even have assert()s enabled, ..
what became "a thing" in the last years.

I guess the Linux kernel (which seem to have followed you --
i bet it was you -- in that malloc typeof() thing), as well
as anyone else, they all require ELF object knowledge and all
that to get to the same info i get for free (less memory usage).
Inclusive memory lingering around to catch double frees and all
that (to some extend).

 |(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.)

..but you do see it as a developer when you compile?
I do not run CI, so errors i actually hate, since i get that
fix+build, fix more-build thing, which is total horror.

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

Yes.  Kernel, too, and i had just seen the commit that made it
simpler to use for the generic "bin".  (By sheer chance.)

 |>|Have a lovely night!
 |> 
 |> You too, dear Alejandro.
 |
 |:-)
 |
 |Have a lovely night!

We could try this on a Monday!
Ciao,

--steffen
|
|Der Kragenbaer,                The moon bear,
|der holt sich munter           he cheerfully and one by one
|einen nach dem anderen runter  wa.ks himself off
|(By Robert Gernhardt)

Reply via email to