3

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.

this
  • 5,229
  • 1
  • 22
  • 51
  • "structs A and B are guaranteed to have the same size" - I don't think that's guaranteed... (although I admit it's pretty likely in practice...) – Oliver Charlesworth Feb 08 '14 at 19:40
  • @OliCharlesworth I don't see when could they be different. Very specific circumstances or system? – this Feb 08 '14 at 19:45
  • Maybe a question out of scope. Do you have a hint how do you want to use this magic? Do you simply want to modify a part of float as being int? If yes, you can surely do it easier. I really do not see any interest in doing those struct conversions. – Marian Feb 08 '14 at 20:11
  • @ Marian *Do you have a hint how do you want to use this magic?* Yes. *Do you simply want to modify a part of float as being int?* I want to know how to properly access the structs( elaborated in the question). *I really do not see any interest in doing those struct conversions.* This is a sscce. That means it is only a minimal example of my whole codebase, which currently requires these conversions. – this Feb 08 '14 at 20:29

3 Answers3

6

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.

Community
  • 1
  • 1
  • Do you think I could get away with it, if I disable strict aliasing? – this Feb 08 '14 at 21:06
  • @self. Maybe. Or maybe not. Since in my interpretation, it's undefined behavior, I wouldn't recommend relying on it. –  Feb 08 '14 at 21:08
  • Can you just clarify please: *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* the structs mentioned here are A and B, right? – this Feb 08 '14 at 21:11
  • @self. Yes, they are, since you are trying to type-pun those. The structs `S1` and `S2` aren't really relevant, all what matters is that S1 and S2 are different types. –  Feb 08 '14 at 21:13
  • Can I get away with just one more question, why wouldn't casting the struct B pointer to struct A trough an union solve the problem. Not the members like I showed in the example but the pointer itself. Like this: http://pastebin.com/ERc0A4eB Don't tell me that would actually work. – this Feb 08 '14 at 21:21
  • @self. I didn't say that using a union wouldn't solve the problem. When you are using an union, the strict aliasing rule is not violated anymore, but the resulting pointer still may be a trap representation (the result is implementation-defined). However, almost every compiler and platform I know of would permit this and produce correct results, so if you really want to do this, then use a union. –  Feb 08 '14 at 21:31
  • The common initial sequence exception only applies to structs that are part of a union. Access through a pointer that was cast from a different, layout-compatible struct is still a violation. – tab Feb 08 '14 at 22:44
  • @H2CO3 Sorry to bother you. But I have another question. *sigh* i know... I search everywhere but couldn't find anything. If I pass pointer B through a void* function parameter and cast it to A inside the function is that still a aliasing violation? Do I have to cast it trough a union still? – this Feb 09 '14 at 00:52
  • @self.: Yes, casting to `void *` and passing it to a function and casting it to something else violates the aliasing rules. The intermediate steps do not matter. If an object is defined with one type and you access it with another type, you violate the rules unless one of the exceptions applies. – Eric Postpischil Feb 11 '14 at 00:21
  • @EricPostpischil Great, thank you for the response, there is almost no info on that online. – this Feb 11 '14 at 01:39
2

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

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.

Marian
  • 7,402
  • 2
  • 22
  • 34
  • Well, pointers b->p and a->p are different they are pointers to types S1 and S2 respectively. They are the same when casted, but that is the problem, in c sometimes you can't just do that. – this Feb 08 '14 at 20:36
  • @self. If you take a pointer `a` of type `A*` and `b` of type `B*` such that a==b, then the expression `a->p` gives the same value as `((B*)a)->p` and `b->p` and `((A*)b)->p`. Isn't it the core of your question? – Marian Feb 08 '14 at 20:41
  • Did you meant this: *a->p == ((B*)a)->p == b->p ==((A*)b)->p* ? Then if `a==b` , then pointer types of `a->p` and `b->p` are different. Because they interpret the data they point to in different way, therefore all the casting. – this Feb 08 '14 at 20:56
  • @self. I meant: if `(char*)a == (char*)b`, then `(char*) a->p` is equal to `(char*) ((struct B*)a)->p` which is equal to `(char*) b->p` which is equal to `(char*)((struct A*)b)->p` in this particular case, because `p` is the first field of structs A and B. – Marian Feb 08 '14 at 21:06