Extending a struct in C

前端 未结 11 894
你的背包
你的背包 2020-12-23 15:54

I recently came across a colleague\'s code that looked like this:

typedef struct A {
  int x;
}A;

typedef struct B {
  A a;
  int d;
}B;

void fn(){
  B *b;         


        
相关标签:
11条回答
  • 2020-12-23 16:26

    I'll go out on a limb and oppose @paxdiablo on this one: I think it's a fine idea, and it's very common in large, production-quality code.

    It's basically the most obvious and nice way to implement inheritance-based object oriented data structures in C. Starting the declaration of struct B with an instance of struct A means "B is a sub-class of A". The fact that the first structure member is guaranteed to be 0 bytes from the start of the structure is what makes it work safely, and it's borderline beautiful in my opinion.

    It's widely used and deployed in code based on the GObject library, such as the GTK+ user interface toolkit and the GNOME desktop environment.

    Of course, it requires you to "know what you're doing", but that is generally always the case when implementing complicated type relationships in C. :)

    In the case of GObject and GTK+, there's plenty of support infrastructure and documentation to help with this: it's quite hard to forget about it. It might mean that creating a new class isn't something you do just as quickly as in C++, but that's perhaps to be expected since there's no native support in C for classes.

    0 讨论(0)
  • 2020-12-23 16:27

    Anything that circumvents type checking should generally be avoided. This hack rely on the order of the declarations and neither the cast nor this order can be enforced by the compiler.

    It should work cross-platform, but I don't think it is a good practice.

    If you really have deeply nested structures (you might have to wonder why, however), then you should use a temporary local variable to access the fields:

    A deep_a = e->d.c.b.a;
    deep_a.x = 10;
    deep_a.y = deep_a.x + 72;
    e->d.c.b.a = deep_a;
    

    Or, if you don't want to copy a along:

    A* deep_a = &(e->d.c.b.a);
    deep_a->x = 10;
    deep_a->y = deep_a->x + 72;
    

    This shows from where a comes and it doesn't require a cast.

    Java and C# also regularly expose constructs like "c.b.a", I don't see what the problem is. If what you want to simulate is object-oriented behaviour, then you should consider using an object-oriented language (like C++), since "extending structs" in the way you propose doesn't provide encapsulation nor runtime polymorphism (although one may argue that ((A*)b) is akin to a "dynamic cast").

    0 讨论(0)
  • 2020-12-23 16:31

    That's a horrible idea. As soon as someone comes along and inserts another field at the front of struct B your program blows up. And what is so wrong with b.a.x?

    0 讨论(0)
  • 2020-12-23 16:31

    I am sorry to disagree with all the other answers here, but this system is not compliant to standard C. It is not acceptable to have two pointers with different types which point to the same location at the same time, this is called aliasing and is not allowed by the strict aliasing rules in C99 and many other standards. A less ugly was of doing this would be to use in-line getter functions which then do not have to look neat in that way. Or perhaps this is the job for a union? Specifically allowed to hold one of several types, however there are a myriad of other drawbacks there too.

    In short, this kind of dirty casting to create polymorphism is not allowed by most C standards, just because it seems to work on your compiler does not mean it is acceptable. See here for an explanation of why it is not allowed, and why compilers at high optimization levels can break code which does not follow these rules http://en.wikipedia.org/wiki/Aliasing_%28computing%29#Conflicts_with_optimization

    0 讨论(0)
  • 2020-12-23 16:38

    This is perfectly legal, and, in my opinion, pretty elegant. For an example of this in production code, see the GObject docs:

    Thanks to these simple conditions, it is possible to detect the type of every object instance by doing:

    B *b;
    b->parent.parent.g_class->g_type
    

    or, more quickly:

    B *b;
    ((GTypeInstance*)b)->g_class->g_type
    

    Personally, I think that unions are ugly and tend to lead towards huge switch statements, which is a big part of what you've worked to avoid by writing OO code. I write a significant amount of code myself in this style --- typically, the first member of the struct contains function pointers that can be made to work like a vtable for the type in question.

    0 讨论(0)
  • 2020-12-23 16:41

    I can see how this works but I would not call this good practice. This is depending on how the bytes of each data structure is placed in memory. Any time you are casting one complicated data structure to another (ie. structs), it's not a very good idea, especially when the two structures are not the same size.

    0 讨论(0)
提交回复
热议问题