Inheritance fake in C: Will alignment break my neck?

I have a C structure that is used in various C and C ++ (via extern "C" ).

 #ifdef __cplusplus extern "C" { #endif typedef struct AA; struct A { /*some members*/ }; #ifdef __cplusplus } #endif 

Allocation, initialization, and release are performed by individual member functions under my control, but I do not control access to elements, as they are accessible throughout the place.

The problem is that I cannot change the definition of struct in the header, which is heavily used throughout the system, but I still want to extend the type and add some elements. Since this should compile as C ++ and C, I cannot just create a derived type struct B : public A So I wanted to add this type to the cpp file:

 #ifdef __cplusplus extern "C" { #endif typedef struct BB; struct B { A parent; // <---- public inheritance /*some members*/ }; #ifdef __cplusplus } #endif 

Now I can change all the functions in the cpp file, but I still have to give and receive A* outside the compilation module, since no one knows what B .

So, I am wondering if there is a reasonable and well-defined way to do this. Can I just translate my B* to A*s and vice versa or do I have to explicitly convert them:

 A* static_cast_A(B* b) { return &(b->parent); } B* static_cast_B(A* a) { B* const b = 0; unsigned const ptrdiff = (unsigned)((void*)(&(b->parent))); return (B*)(((void*)a)-ptrdiff); } 

Are there any other problems with this, or should I do it differently?

+4
source share
2 answers

Alignment requirements should not cause any problems, and this is indeed the default method for faking inheritance in C.

However, your static_cast_B() not portable (in particular, void * arithmetic and conversion to unsigned in case of sizeof (void *) > sizeof (unsigned) - the latter should work, but it is something like a smell of code).

I suggest using the following code instead:

 #include <stddef.h> B* static_cast_B(A* a) { return (B*)((char*)a - offsetof(B, parent)); } 
+2
source

Until you enter a virtual table (by adding a virtual method or destructor) into struct B and parent , the member of this structure is the first element, then it is safe to simply case B to A

Dropping A into B also safe, but only if the original pointer actually points to B , and not to something else, for example, only to structure A

If parent not the first member of struct B , you should use the container_of macro, for example, as shown in the Linux kernel. It works with member pointer offsets (i.e. you can find a good explanation here ).

In addition, by default, both structures will have the same alignment. I'm not sure how the compiler puts A in B if you configure one of the structure alignments. I think it depends on the compiler, but it is easy to understand.

If B has a virtual table, you must know the compiler internals in order to correctly convert pointers. For example, GCC on x86_64 adds 8 bytes of offset, so you must offset it manually when converting. But this will not work in the case of virtual inheritance (virtual base classes) to decide that you need to resort to using RTTI, etc. But this is beyond the scope of your question, and I would recommend that you do not go this way.

Hope this helps.

+4
source

Source: https://habr.com/ru/post/1434422/


All Articles