Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Most of these are due to the cruft added in C99 and later.

Compile-time trees are possible without compound literals.

More than twenty years ago, I made a hyper-linked help screen system a GUI app whose content was all statically declared C structures with pointers to each other.

At file scope, you can make circular structures, thanks to tentative definitions, which can forward-declare the existence of a name, whose initializer can be given later.

Here is a compile-time circular list

   struct foo { struct foo *prev, *next; };

   struct foo n1, n2; /* C90 "tentative definition" */

   struct foo circ_head = { &n2, &n1 };

   struct foo n1 = { &circ_head, &n2 };

   struct foo n2 = { &n2, &circ_head };
You can't do this purely declaratively in a block scope, because the tentative definition mechanism is lacking.

About macros used for include headers, those can be evil. A few years ago I had this:

   #include ALLOCA_H  /* config system decides header name */
Broke on Musl. Why? ALLOCA_H expanded to <alloca.h>. But unlike a hard-coded #include <alloca.h>, this <alloca.h> is just a token sequence that is itself scanned for more macro replacements: it consists of the tokens {<}{alloca}{.}{h}{>}. The <stdlib.h> on Musl defines an alloca macro (an object-like one, not function-like such as #define alloca __builtin_alloca), and that got replaced inside <alloca.h>, resulting in a garbage header name.


Compound literals are fun. You can use them to implement labelled and optional parameters to functions too:

    struct args { int a; char *b; };
    #define fn(...) (fn_((struct args){__VA_ARGS__}))

    void fn_ (struct args args) {
        printf ("a = %d, b = %s\n", args.a, args.b);
    }

    fn (0, "test");
    fn (1); // called with b == NULL
    fn (.b = "hello", .a = 2);
(As written this has a subtle catch that fn() passes undefined values, but you can get around that by adding an extra hidden struct field which is always zero).


I haven't heard of "tentative definitions" before. Couldn't you just replace it with a regular declaration i.e.

    extern foo n1, n2;
Is there any benefit of tentative definitions over this?


Yes; the "extern" is potentially confusing when the definition is located in the same file below. They usually inform the reader of the code to look in some other file for the definition, and usually appear in header files.


I'm not certain, but the `extern` variant probably doesn't reserve space at compile-time; it just says "the linker will know where to find these". So resolving those symbols might need to wait until link-time. The tentative definitions probably do reserve space (and hence an immediately-known address), and the later true definitions just supply the initial value to put in that space.


Oops I'm wrong, I meant compile time anonymous trees. It's very obviously possible by defining other variables.


Also variable assignment with identifier list:

   a = 0;
   x = (type_t) { .y = a++, .x = a++ }
Now, figure it out the order of the field's assignment.


The order of evaluation among initializers is unspecified, which makes it UB.

We don't have to use mixed-up designated initializers to run aground. Just simply:

  { int a = 0;
    int x[2] = { a++, a++ }; }
This was also new in C99 (not to mention the struct literal syntax); in C90, run-time values couldn't be used for initializing aggregate members, so the issue wouldn't arise.

Code like:

   { int a = 0;
     struct foo f = { a, a }; }
was supported in the GNU C dialect of C90 before C99 and of course is also a long-time C++ feature.

https://gcc.gnu.org/onlinedocs/gcc/Initializers.html#Initial...

The doc doesn't say anything about order. I can't remember what C++ has to say about this.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: