Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it legal to alias a struct and an array?

Pointer arithmetics between consecutive members of same type in a struct used to be a common practice while pointer arithmetics is only valid inside an array. In C++ it would be explicitely Undefined Behaviour because an array can only be created by a declaration or a new expression. But C language defines an array as a contiguously allocated nonempty set of objects with a particular member object type, called the element type. (n1570 draft for C11, 6.2.5 types §20). So provided we can make sure that that the members are consecutive (meaning no padding between them) it could be legal to see that as an array.

Here is a simplified example, that compiles without a warning and gives expected results at run time:

#include <stdio.h>
#include <stddef.h>
#include <assert.h>

struct quad {
    int x;
    int y;
    int z;
    int t;
};

int main() {
    // ensure members are consecutive (note 1)
    static_assert(offsetof(struct quad, t) == 3 * sizeof(int),
        "unexpected padding in quad struct");
    struct quad q;
    int *ix = &q.x;
    for(int i=0; i<4; i++) {
        ix[i] = i;
    }
    printf("Quad: %d %d %d %d\n", q.x, q.y, q.z, q.t);
    return 0;
}

It does not really make sense here, but I have already seen real world example where iterating among members of a struct allows simpler code with less risk of typo.

Question:

In the above example, is the static_assert enough to make legal the aliasing of the struct with an array?


(note 1) As a struct describes a sequentially allocated nonempty set of member objects, later members must have increasing addresses. Simply the compiler could include padding between them. So the offset of last member (here t) if 3 times sizeof(int) plus the total padding before it. If the offset is exactly 3 * sizeof(int) then there is no padding in struct


The question proposed as a duplicate contains both an accepted answer that let think that it would be UB, and a +1 answer that let think that it could be legal because I could ensure that no padding could exist

like image 823
Serge Ballesta Avatar asked Jan 08 '18 09:01

Serge Ballesta


2 Answers

No, it isn't legal to alias a struct and array like this, it violates strict aliasing. The work-around is to wrap the struct in a union, which contains both an array and the individual members:

union something {
  struct quad {
    int x;
    int y;
    int z;
    int t;
  };

  int array [4];
};

This dodges the strict aliasing violation, but you may still have padding bytes. Which you can detect with the static assert.

Another issue remains, and that is that you can't use pointer arithmetic on an int* pointing at the first member of the struct, for various obscure reasons outlined in the specified behavior of the additive operators - they require that the pointer points at an array type.

The best way to dodge all of this is to simply use the array member of the union above. This together with a static assert results in well-defined, rugged and portable code.

(In theory, you could also use a pointer to character type to iterate through the struct - unlike int* this would be allowed as per 6.3.2.3/7. But this is a more messy solution if you have no interest in the individual bytes.)

like image 78
Lundin Avatar answered Sep 22 '22 19:09

Lundin


The problem here is your definition of contiguously allocated: "we can make sure that that the members are consecutive (meaning no padding between them)".

Although that is a corollary of being contiguously allocated, it does not define the property.

Your structure members are separate variables with automatic storage duration, in a particular order with or without padding depending on how you are able to control your compiler, that's all. As such you can't use pointer arithmetic to reach one member given the address of another, and the behaviour on doing so is undefined.

like image 32
Bathsheba Avatar answered Sep 23 '22 19:09

Bathsheba