There can be many advantages to such code, but unfortunately the C Standard was not written to facilitate it. Compilers have historically offered effective behavioral guarantees beyond what the Standard required that made it possible to write such code much more cleanly than is possible in Standard C, but compilers have lately started revoking such guarantees in the name of optimization.
Most notably, many C compilers have historically guaranteed (by design if not documentation) that if two structure types contain the same initial sequence, a pointer to either type may be used to access members of that common sequence, even if the types are unrelated, and further that for purposes of establishing a common initial sequence all pointers to structures are equivalent. Code which makes use of such behavior can be much cleaner and more type-safe than code which does not, but unfortunately even though the Standard requires that structures sharing a common initial sequence must be laid out the same way, it forbids code from actually using a pointer of one type to access the initial sequence of another.
Consequently, if you want to write object-oriented code in C, you'll have to decide (and should make this decision early on) to either jump through a lot of hoops to abide by C's pointer-type rules and be prepared to have modern compilers generate nonsensical code if one slips up, even if older compilers would have generated code which works as intended, or else document a requirement that the code will only be usable with compilers that are configured to support old-style pointer behavior (e.g. using an "-fno-strict-aliasing") Some people regard "-fno-strict-aliasing" as evil, but I would suggest that it is more helpful to think of "-fno-strict-aliasing" C as being a language which offers greater semantic power for some purposes than "standard" C, but at the expense of optimizations which might be important for some other purposes.
By means of example, on traditional compilers, historical compilers would interpret the following code:
struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };
void hey(struct pair *p, struct trio *t)
{
p->i1++;
t->i1^=1;
p->i1--;
t->i1^=1;
}
as performing the following steps in order: increment the first member of *p
, complement the lowest bit of the first member of *t
, then decrement the first member of *p
, and complement the lowest bit of the first member of *t
. Modern compilers will rearrange the sequence of operations in a fashion which code which will be more efficient if p
and t
identify different objects, but will change the behavior if they don't.
This example is of course deliberately contrived, and in practice code which uses a pointer of one type to access members that are part of the common initial sequence of another type will usually work, but unfortunately since there's no way of knowing when such code might fail it's not possible to safely use it at all except by disabling type-based aliasing analysis.
A somewhat less contrived example would occur if one wanted to write a function to do something like swap two pointers to arbitrary types. In the vast majority of "1990s C" compilers, that could be accomplished via:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
In Standard C, however, one would have to use:
#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
void **temp = malloc(sizeof (void*));
memcpy(temp, p1, sizeof (void*));
memcpy(p1, p2, sizeof (void*));
memcpy(p2, temp, sizeof (void*));
free(temp);
}
If *p2
is kept in allocated storage, and the temporary pointer isn't kept in allocated storage, the effective type of *p2
will become the type of the temporary pointer, and code which attempts to use *p2
as any type which didn't match the temporary-pointer type will invoke Undefined Behavior. It is to be sure extremely unlikely that a compiler would notice such a thing, but since modern compiler philosophy require that programmers avoid Undefined Behavior at all cost, I can't think of any other safe means of writing the above code without using allocated storage.
struct
, so that doesn't seem to be a viable option here. I'll give you encapsulation, though. Although, as I stated above, the issue of whether it's OO or not isn't really relevant to whether it's a good idea. – jpmc26 Jan 29 '16 at 17:51void*
structs of … – Jörg W Mittag Jan 29 '16 at 22:02