It explicitly and deliberately follows the first member rule, as it should :)
In C++, this is covered by 6.5/7, and allowed because it's a type compatible with the effective type of the object (in a standard layout class, a pointer to the a structure object points to the initial member)
If this is inconvenient, just casting directly to Base pointer is also legal.
> but what's the argument making the downcast back to Derived legal C?
The justification comes from this part of the C standard (C99 6.7.2.1 p13):
Within a structure object, the non-bit-field
members and the units in which bit-fields reside
have addresses that increase in the order in which
they are declared. 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.
There may be unnamed padding within a structure
object, but not at its beginning.
It follows that: Derived *d = GetDerived();
// This is legal: a pointer to Derived, suitably converted,
// points to its initial member "base":
Base *base = (Base*)d;
// This is also legal: a pointer to Derived.base, the initial
// member of Derived, suitably converted, points to Derived.
Derived *d2 = (Derived*)base; void copy_8_bytes(char *dst, const char *src) {
*(uint64_t*)dst = *(uint64_t*)src;
}
Only maybe excuses. And poor ones.Now I don't remember the article where I saw that, but technically given the current orientation of compiler writers there are some even more ridiculous situations. Like (a<<n) | (a>>(32-n)) having the obviously desired effect on all current architectures when you look at what would be an obvious direct translation (and quite efficient one already), and yet given the current orientation of compiler writers I would not like to see that code AT ALL unless it is proved that n is always strictly between 1 and 31. And now if they want to restore any kind of efficiency after all that madness, they would have to implement yet another case of convoluted peephole optim. Stupid. Give me my original intent of the langage back, because virtually everybody is using it like that consciously or not, and that will just not change.
How about the fact that there is no reason to write that if what you actually mean is memcpy(dst, src, 8)? Chunking yourself is a premature optimization that the compiler is in a better position to actually perform.
Derived* derived = (Derived*)base;
and then use both base and derived, is that not violating aliasing rules? typedef struct {
int x;
} Base;
typedef struct {
Base base;
int y;
} Derived;
int f(Base* b, Derived* d) {
b->x = 0;
d->base.x = 1;
return b->x;
}
Notice that if we are accessing the base members of "d", we are still accessing them through a struct of type "Base" (d->base.x). If we compile this with strict aliasing, you can see the output is allowing that the two might alias (while this isn't a proof, it's a strong indication that this is aliasing-correct). 0000000000000000 <f>:
0: c7 07 00 00 00 00 mov DWORD PTR [rdi],0x0
6: c7 06 01 00 00 00 mov DWORD PTR [rsi],0x1
c: 8b 07 mov eax,DWORD PTR [rdi]
e: c3 ret6.5.7. An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- 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)
This means Derived is allowed to alias Base.
You can't downcast to a derived type if the object isn't actually an instance of the derived type. That seems straightforward, no?
The reason your example is illegal is that you are casting to pointer-to-"struct derived", but the thing being pointed to is not actually a "struct derived."
The "physical subtyping" pattern works because the C standard says that a pointer to a struct, suitably converted, also points to its first member. So a pointer-to-Derived, converted to a pointer-to-Base, points at Derived's first member. But a pointer-to-Base doesn't point at a Derived unless that object actually is a Derived. So the downcast is only legal if the object actually is a Derived.
Let me try to think of a good way to update the post to capture this better...
You can work around this by clicking on the link for the post itself (ie. "3 minutes ago") which allows you to reply immediately.
But if the object isn't actually a Derived, you can't cast to pointer-to-Derived:
Derived derived;
Derived *pDerived = &derived;
// This is legal because it's equivalent to:
// Base *pb = &derived.base;
//
// ie. there actually is a Base object there that the
// pointer is pointing to.
Base *pBase = (Base*)pDerived;
// This is legal because pBase points to the initial member
// of a Derived. So, suitably converted, it points at the
// Derived.
//
// The key point is that there actually is a Derived object
// there that we are pointing at.
pDerived = (Derived*)pBase;
Base base;
// This is illegal, because this base object is not actually
// a part of a larger Derived object, it's just a Base.
// So we have a pDerived that doesn't actually point at a
// Derived object -- this is illegal.
pDerived = (Derived*)&base;
// Imagine if the above were actually legal -- this would
// reference unallocated memory!
pDerived->some_derived_member = 5;All the compilers I have used will cheerfully reference unallocated memory; I thought the behavior was undefined.
After spending too much of my life chasing these bugs, here the compiler will do exactly what you told it to, which probably means making your day miserable.
It also violates string aliasing because 6.5.7. only works for one way. See my other comment.
Derived can alias Base, but not vice-versa.
Not true. The standard says you can access any object's value via the char type, but not the reverse. You can't cast a character array to any type and dereference it.
> The author was talking about "chunking" non-char units.
Sure, but you can call my copy_8_bytes() function like so legally:
int64_t a, b = 0;
copy_8_bytes(&a, &b);
So what I said applies to non-char types.So making your own malloc backed by a static char array is undefined behavior?
The "effective type" (this is a term defined in the standard) of the char array elements would be "char", whereas the memory returned from malloc() is considered to be an object that initially has no effective type. I don't know of any way to take the char array and "erase" its effective type so that it can be used generically, like the value returned from malloc().
This is a problem!
We implement our own allocators all the time. If you can't even do such a basic thing legally, then the rules are obvious nonsense.
Non-allocated memory (stack char) cannot be used like that. I'm sure you know alloc(), and malloc() is fast for small sizes.
Of course, what do you take me for? :)
> I'm sure you know alloc()
Do you mean alloca? It has a lot of problems, and is generally prohibited at Google.
> and malloc() is fast for small sizes.
Not nearly fast enough for my purposes.
> Yes, alloca. (Don't use it.)
> Just make a fast allocator that uses heap instead of the stack. You only need to malloc once and it can be used for any type since like you pointed out, it's effective type can be changed.
There is also the case where you have some raw data from a file or network, that you want to re-interpret as a struct. That is always dangerous with endianness and struct padding, but it is a very common practice. You could always memcpy from a char array to a struct, but that can waste memory.
At least that's the rationale for why we don't use it at Google.
Yep, blowing the stack will do that.
One section claims "Physical Subtyping is Broken", where "physical subtyping" is defined as "the struct-based implementation of inheritance in C." I assume this means the typical pattern of:
The article claims physical subtyping is broken because casting between pointer types results in undefined behavior. The article gives this example: I agree this example is broken, but casting between pointer types in this way is totally unnecessary for C-based inheritance. You can do upcasts and downcasts that are totally legal: So I don't think the article has proved that "Physical Subtyping is Broken."The next section says that "Chunking Optimizations Are Broken," because code like this is illegal:
While this is true, such optimizations are generally unnecessary. For example, write this instead as: If you compile this on an architecture like x86 that truly allows unaligned reads, you'll see that modern compilers do the "chunking optimization" for you: It says next that "int8_t and uint8_t Are Not Necessarily Character Types." That is indeed a good point and probably not well-known. So I agree this is something people should keep in mind. But most of this article is warning against practices that are generally unnecessary and known to be bad C in 2016.It's true that a lot of legacy code-bases still break these rules. But many are cleaning up their act, fixing practices that were never correct but used to work. For example, here is an example of Python fixing its API to comply with strict aliasing, and this is from almost 10 years ago: https://www.python.org/dev/peps/pep-3123/