Hacker News new | ask | show | jobs
by joejev 2245 days ago
Interesting idea, but this implementation has UB:

    typedef void* var;

    struct Header {
      var type;
    };

    // ...

    #define alloc_stack(T) header_init( \
      (char[sizeof(struct Header) + sizeof(struct T)]){0}, T)

    var header_init(var head, var type) {
      struct Header* self = head;
      self->type = type;
      return ((char*)self) + sizeof(struct Header);
    }
The section "struct Header* self = head" is UB. The alignement requirement of the local char array is 1 but the alignment requirement of struct Header is that of void* which is probably 8.
2 comments

That's just what I was wondering, are "magic" libraries like this still safe to use considering modern compiler's UB shenanigans?
Not only that but you have a pointer to a parameter returned back and used outside its scope ...
It's not a pointer to a parameter, it is just the parameter itself.

var is a typedef for void* and no & appears in the function.

> no & appears in the function.

It's an array, so you don't need & to take its address, it decays into a pointer without &.

Imagine:

    char buf[sizeof(struct Header) + sizeof(struct T)];
    char *p = buf;
Then take away the names so that you are effectively passing p as a parameter... Then returning p.

As in, an anonymous temporary being given to the function, and the function returns its address back.

It's assuming that this temporary parameter buffer will exist after it is used and the function has returned. I'm not sure what the standard says for that but it is crazy sketchy. [Edit: Googling around, it seems like maybe this is illegal in C99 but possibly legal in C11? Or that C11 changed the rules for this. Does not seem like a great thing to rely upon.]

There is no issue here (except the one highlighted by joejev).

Many standard library functions return a pointer which they got as a parameter (or another pointer offset from it, as is the case here). The compound literal is no more "temporary" than a variable that was introduced right before the function call. Lifetimes of compound literals are specified in section 6.5.2.5 of both C99 and C11.

  func((int[256]){ 0 });
  // is mostly equivalent to 
  int __a__[256] = { 0 };
  func(__a__);
> Many standard library functions return a pointer which they got as a parameter

Obviously. But this does not extend the lifetime of the buffer they are passed. Namely you can't use this as a technique to extend the life of automatic storage falling out of scope.

> Lifetimes of compound literals are specified in section 6.5.2.5 of both C99 and C11.

This is what I was missing. So it is valid by the standard. Which is good if you have looked it up. It remains not obvious when reading source without a copy of the standard on hand, or prior knowledge of that section. Passing an expression of that sort and keeping a pointer to it visually looks like the intent is to retain a pointer of more limited scope. Intuitively it would make just as much sense if the lifetime were shorter. If you are seeking clarity of intent this is not a great thing to rely upon.