简体   繁体   中英

Cast struct pointer to another struct

This code snippet prints the value 5 . I don't understand why.

#include <stdio.h>

struct A
{
    int x;
};

struct B
{
    struct A a;
    int y;
};

void printA(struct A *a)
{
    printf("A obj: %d\n", a->x);
}

int main()
{
    struct B b = {
        {
            5
        },
        10
    };

    struct A *a = (struct A*)&b;
    printA(a);

    printf("Done.\n");

    return 0;
}

When I create b , a pointer to it would point to the data { {5}, 10 } .

When I cast &b to struct A* , I'm assuring the compiler that this struct A* points to a struct of a single data element of data type int . Instead, I'm providing it a pointer to a struct of two data elements of data types struct A , int .

Even if the second variable is ignored (since struct A has only one data member) I am still providing it a struct whose member is of data type struct A , not int .

Thus, when I pass in a to printA , the line a->x is performed, essentially asking to access the first data element of a . The first data element of a is of data type struct A , which is a type mismatch due to the %d expecting a digit, not a struct A .

What exactly is happening here?

When I create b , a pointer to it would point to the data { {5}, 10 } .

Yes, in the sense of that being the text of a type-appropriate and value-correct C initializer. That text itself should not be taken literally as the value of the structure.

When I cast &b to struct A* , I'm assuring the compiler that this struct A* points to a struct of a single data element of data type int.

No, not exactly. You are converting the value of the expression &b to type struct A * . Whether the resulting pointer actually points to a struct A is a separate question.

Instead, I'm providing it a pointer to a struct of two data elements of data types struct A , int .

No, not "instead". Given that struct B 's first member is a struct A , and that C forbids padding before the first member of a structure, a pointer to a struct B also points to a struct A -- the B's first member -- in a general sense. As @EricPostpischi observed in comments, the C standard explicitly specifies the outcome in your particular case: given struct B b , converting a pointer to b to type struct A * yields a pointer to b 's first member., a struct A .

Even if the second variable is ignored (since struct A has only one data member) I am still providing it a struct whose member is of data type struct A , not int .

The first sizeof(struct A) bytes of the representation of a struct B form the representation of its first member, a struct A . That the latter is a member of the former has no physical manifestation other than their overlap in memory.

Even if the language did not explicitly specify it, given your declaration of variable b as a struct B , there would be no practical reason to expect that the expression (struct A*)&b == &b.a would evaluate to false, and there can be no question that the right-hand pointer can be used to access a struct A .

Thus, when I pass in a to printA , the line a->x is performed, essentially asking to access the first data element of a .

Yes, and this is where an assertion enters that a really does point to a struct A . Which it does in your case, as already discussed.

The first data element of a is of data type struct A ,

No. *a is by definition a struct A . Specifically, it is the struct A whose representation overlaps the beginning of the representation of b . If there were not such a struct A then the behavior would be undefined, but that's not an issue here. Like every struct A , it has a member, designated by x , that is an int .

which is a type mismatch due to the %d expecting a digit, not a struct A .

You mean expecting an int . And that's what it gets. That's what the expression a->x reads, supposing the behavior is defined at all, because that is the type of that expression. Under different circumstances the behavior might indeed not be defined, but under no circumstance does that expression ever provide a struct A .

What exactly is happening here?

What seems to be happening is that you are imagining different, higher-level semantics than C actually provides. In particular, you seem to have a mental model of structures as lists of distinguishable member objects, and that's leading you to form incorrect expectations.

Perhaps you are more familiar with a weakly typed language such as Perl, or a dynamically typed language such as Python, but C works differently. You cannot look at a C object and usefully ask "what is your type"? Instead, you look at each and every object through the lens of the static type of the expression used to access it.

The language-lawyer explanation of why the code is fine:

  • Any pointer in C may be converted to any other pointer type. (C17 6.3.2 §7).
  • If it is safe to dereference the pointed-at object after conversion depends on: 1) if the types are compatible and thereby correctly aligned, and 2) if the respective pointer types used are allowed to alias.
  • As a special case, a pointer to a struct type is equivalent to a pointer to its first member. The relevant part of C17 6.7.2 §15 says:

    A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa.

  • This means that (struct A*)&b is fine. &b is suitably converted to the correct type.

  • There is no violation of "strict aliasing", since we fulfil C17 6.5 §7:

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

    • a type compatible with the effective type of the object, ...
    • an aggregate or union type that includes one of the aforementioned types among its members

    The effective type of the initial member being struct A . The lvalue access that happens inside the print function is fine. struct B is also an aggregate type that includes struct A among its members, so strict aliasing violations are impossible, regardless of the initial member rule cited at the top.

There is a special rule in the C standard for this case. C 2011 6.7.2.1 15 says:

A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM