Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending a struct in C

Tags:

c

struct

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;   ((A*)b)->x = 10; } 

His explanation was that since struct A was the first member of struct B, so b->x would be the same as b->a.x and provides better readability.
This makes sense, but is this considered good practice? And will this work across platforms? Currently this runs fine on GCC.

like image 884
rubndsouza Avatar asked Mar 06 '14 08:03

rubndsouza


People also ask

Can we inherit structure in C?

No you cannot. C does not support the concept of inheritance.

How do you size a struct?

In 32 bit processor, it can access 4 bytes at a time which means word size is 4 bytes. Similarly in a 64 bit processor, it can access 8 bytes at a time which means word size is 8 bytes. Structure padding is used to save number of CPU cycles. Let's see what compiler is giving using the sizeof() operator.

How many bytes is a struct in C?

struct { unsigned int widthValidated; unsigned int heightValidated; } status; This structure requires 8 bytes of memory space but in actual, we are going to store either 0 or 1 in each of the variables. The C programming language offers a better way to utilize the memory space in such situations.

Can structure be inherited?

A struct cannot inherit from another kind of struct, whereas classes can build on other classes. You can change the type of an object at runtime using typecasting. Structs cannot have inheritance, so have only one type. If you point two variables at the same struct, they have their own independent copy of the data.


2 Answers

Yes, it will work cross-platform(a), but that doesn't necessarily make it a good idea.

As per the ISO C standard (all citations below are from C11), 6.7.2.1 Structure and union specifiers /15, there is not allowed to be padding before the first element of a structure

In addition, 6.2.7 Compatible type and composite type states that:

Two types have compatible type if their types are the same

and it is undisputed that the A and A-within-B types are identical.

This means that the memory accesses to the A fields will be the same in both A and B types, as would the more sensible b->a.x which is probably what you should be using if you have any concerns about maintainability in future.

And, though you would normally have to worry about strict type aliasing, I don't believe that applies here. It is illegal to alias pointers but the standard has specific exceptions.

6.5 Expressions /7 states some of those exceptions, with the footnote:

The intent of this list is to specify those circumstances in which an object may or may not be aliased.

The exceptions listed are:

  • a type compatible with the effective type of the object;
  • some other exceptions which need not concern us here; and
  • 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).

That, combined with the struct padding rules mentioned above, including the phrase:

A pointer to a structure object, suitably converted, points to its initial member

seems to indicate this example is specifically allowed for. The core point we have to remember here is that the type of the expression ((A*)b) is A*, not B*. That makes the variables compatible for the purposes of unrestricted aliasing.

That's my reading of the relevant portions of the standard, I've been wrong before (b), but I doubt it in this case.

So, if you have a genuine need for this, it will work okay but I'd be documenting any constraints in the code very close to the structures so as to not get bitten in future.


(a) In the general sense. Of course, the code snippet:

B *b; ((A*)b)->x = 10; 

will be undefined behaviour because b is not initialised to something sensible. But I'm going to assume this is just example code meant to illustrate your question. If anyone's concerned about it, think of it instead as:

B b, *pb = &b; ((A*)pb)->x = 10; 

(b) As my wife will tell you, frequently and with little prompting :-)

like image 196
paxdiablo Avatar answered Sep 18 '22 20:09

paxdiablo


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.

like image 26
unwind Avatar answered Sep 19 '22 20:09

unwind