Question

Assuming code is compiled with c11 and strict aliasing enabled.

I am not searching for a different approach, I would like to focus on this specific problem and if it works or why not.

(If I unintentionally made some unrelated error let me know and I will fix it)

c11 standard says:

6.2.5.28 All pointers to structure types shall have the same representation and alignment requirements as each other.

6.7.2.1.6 a structure is a type consisting of a sequence of members, whose storage is allocated in an ordered sequence

This means the pointer size and alignment of pointers in struct A and B are the same.

#include <stdio.h>
#include <stdlib.h>

struct S1
{
    int i ;
} ;

struct S2
{
    float f ;
} ;

struct A
{
    struct S1* p ;
} ;


struct B
{
    struct S2* p ;
} ;


int main( void )
{

Structs A and B have pointers to structs S1 and S2, and structs A and B are guaranteed to have the same size and alignment.

We have a struct B whose member pointer is a struct S2 pointer, but is pointing to some struct S1, which achieved with a void* cast.

struct S1 s1 = { 0 } ;

struct B* b = malloc( sizeof( *b ) ) ;
b->p = ( void* ) &s1 ;

That is ok, we can store the pointer, as long as we don't actually use the pointer. But we want to. We could cast the pointer to struct S1.

( ( struct S1* )(b->p) )->i = 123 ;    //redundant brackets for emphasis

printf("%d\n" , s1.i ) ;

And use it correctly.

So far I don't see any problems, as the pointer was casted to the correct type.

But can we cast the whole struct B to struct A instead? They are the same regarding size and alignment, though the standard might complain(?), could compilers produce undefined behavior?

( ( struct A* )b)->p->i = 666 ;

printf("%d\n" , s1.i ) ;

I know the solution is to use an union( or use a void and just cast correctly any time), as the standard allows to use the member not last used to store a value.

6.5.2.3.3( 95 ) If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called "type punning"). This might be a trap representation.

But, I would like to avoid this:

struct C
{
    union
    {
        struct S1* p1 ;
        struct S2* p2 ;
    } ;
} ;

struct C* c = malloc( sizeof( *c ) ) ;

c->p2 = ( void* )&s1 ;

c->p1->i = 444 ;

printf("%d\n" , s1.i ) ;

return 0 ;
}

Above code without text.

Was it helpful?

Solution

What you described until this point:

But can we cast the whole struct B to struct A instead?

is all correct, but the answer to this question is unfortunately no. It is only permitted to access a struct through a pointer to incompatible type if the two structs contain a "common initial sequence", i. e. if their first few members have the same type. Since your structs don't (namely, the first members are of different types), it is not legal to access an object of type S1 through a pointer to S2 and vice versa. In particular, doing so violates the strict aliasing rule.

From C99, 6.5.7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:76)

— a type compatible with the effective type of the object,

— a qualified version of a type compatible with the effective type of the object,

— a type that is the signed or unsigned type corresponding to the effective type of the object,

— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,

— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

— a character type.

OTHER TIPS

In the expression ((struct A *) b)->p->i, the access to p violates C 2011 6.5 7, which says “An object shall have its stored value accessed only by an lvalue expression that has one of the follow types: a type compatible with the effective type of the object,…”. b->p is a pointer to struct S2, but ((struct A *) b)->p is an lvalue expression with type pointer to struct S1. Although the representations of these pointers may be identical, they are not compatible types.

I think that in this particular case your example will work and is conform to the standard. ANSI standard says:

A pointer to a structure object, suitably
cast, points to its initial member (or if that member is a bit-field,
then to the unit in which it resides), and vice versa.  There may
therefore be unnamed holes within a structure object, but not at its
beginning, as necessary to achieve the appropriate alignment.

The pointer p in your example is always the first (and unique) field of the structure. In my understanding of the previous paragraph a pointer to a struct A is the same as a pointer to A::p (excuse me for the C++ notation) which is the same as a pointer to B::p which is the same as pointer to B. Explicit casting does not change the value of the pointer, so your example shall be conform to the standard.

Useless to say that it is not very beautiful and your boss will probably not appreciate this style of programming.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top