Hello Steffen,

On 2026-04-20T18:15:01+0200, Steffen Nurpmeso wrote:
[...]
>  |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.

The alternative is risking buffer overflows.  I think not compiling on
a twenty year old system is acceptable.

If one is porting new software to such an old system, it would be
reasonable to first port a modern compiler and libc. 

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

Did you forget to include <assert.h>?

> Ah wait, i meant _Static_assert(), which was ISO C in 2011, and
> that was completely unusable.

Apart from the name, it's functionally identical to static_assert(3).

_Static_assert(3) is a keyword, and static_assert(3) was a macro in C11,
provided by <assert.h>.  In C23, static_assert(3) became a keyword too.

>  They should have used the C++
> static_assert() right away.

You mean the name?  You could include <assert.h> and get the C++ name.
Here are the relevant contents of the manual page.  I'm really surprised
to hear that you can't use it until C23.

        $ man -w static_assert \
        | MANWIDTH=64 xargs mansectf '(SYNOPSIS|DESCRIPTION|HISTORY)' \
        | cat;
        static_assert(3)    Library Functions Manual   static_assert(3)

        SYNOPSIS
             #include <assert.h>

             void static_assert(bool constant‐expression, const char *msg);

             /* Since C23: */
             void static_assert(bool constant‐expression);

        DESCRIPTION
             This  macro  is similar to assert(3), but it works at com‐
             pile time, generating a compilation  error  (with  an  op‐
             tional  message)  when  the input is false (i.e., compares
             equal to zero).

             If the input is nonzero, no code is emitted.

             msg must be a string literal.  Since C23, this argument is
             optional.

             There’s a keyword, _Static_assert(), that behaves  identi‐
             cally, and can be used without including <assert.h>.

        HISTORY
             C11.

             In  C11,  the  second  argument (msg) was mandatory; since
             C23, it can be omitted.

        Linux man‐pages 6.17‐11... 2026‐04‐20          static_assert(3)

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

This looks like an older version of my malloc_T() macro, which was:


        #define mallocarray(...)  reallocarray(NULL, __VA_ARGS__)
        #define malloc_T(n, T)                                        \
        (                                                             \
                (void)0,                                              \
                (T *){mallocarray(n, sizeof(T))}                      \
        )

The reason I added typeas() is to support allocating arrays of arrays.
That is, the above will not work for the following code:

        int (*p)[4];

        p = malloc_T(42, int[4]);

But with the version that uses typeof(), it works fine.  The reason is
that the macro expands internally T*, which would expand as int[4]*,
which is a syntax error.  But with typeof(), you have typeof(int[4])*,
which is correct.

[...]
> I guess the Linux kernel (which seem to have followed you --
> i bet it was you -- in that malloc typeof() thing),

I suspect they learnt from seeing some of my code, as I had shown my
work to them before they did it.  But they ddeveloped it independently.
And in fact, they goofed it importantly.  So much that their supposedly
safe macros are in fact massive foot-guns.  The original macros they
wrote were slightly dangerous, and Torvalds patched them with an
overload that made them the massive foot-guns they are now:
<https://lore.kernel.org/lkml/abhGS0n_RsUG97Ni@devuan/>.

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

If the project is clean from warnings, you may see it.  But I certainly
prefer enabling it as a hard error, which makes me see it.  My makefiles
often have a lot of compiler flags for enabling as many errors as
possible.

If it's an error (as it is now), I certainly see it, of course.

> I do not run CI, so errors i actually hate, since i get that
> fix+build, fix more-build thing, which is total horror.

What alternative exists?  If some code is bogus, I prefer a hard error,
and then fix stuff until it works.

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

That commit from Torvalds was horrible.
That macro is a massive foot-gun.
<https://lore.kernel.org/lkml/abhGS0n_RsUG97Ni@devuan/>.


Cheers,
Alex

-- 
<https://www.alejandro-colomar.es>

Attachment: signature.asc
Description: PGP signature

Reply via email to