Re: [boost] gcc-4.4.0 and boost::optional is giving (spurious?) strict-aliasing warnings

On Thu, May 7, 2009 at 3:37 AM, Kim Barrett <kab.conundrums@verizon.net> wrote:
At 7:17 PM -0300 5/5/09, Brad Spencer wrote:
In boost/aligned_storage.hpp, the class template boost::aligned_storage defines its address member function thusly:
void* address() { return this; } const void* address() const { return this; }
I'm not 100% sure, but this could be very wrong as, IIRC, there is no guarantee that for non PODS the address of the first element (i.e. aligned_storage.data_) is the same as 'this'. but why doesn't aligned storage implements address() as: void * address { return this.data_.data_.buf; } BTW, I see that boost optional actually uses its own implementation of aligned storage, whose address member function returns a pointer to a char buffer, so it shouldn't be a problem.
There is code in boost/optional/optional.hpp (and probably the other libs you mention) which looks like:
internal_type* get_object() { return static_cast<internal_type*>(m_storage.address()); }
What this leads, after some expansion, to something roughly equivalent to
static_cast<internal_type*>( static_cast<void*>( <a pointer to an instance of aligned_storage> ))
which I believe is not strict aliasing safe. It is effectively a cast from aligned_storage* to internal_type*, with a trip through void* along the way. That trip through void* does not make the conversion satisfy the strict aliasing rules.
IIRC aliasing rules talk about dynamic types of objects: roughly, you can access a memory location only with a type which is the same as the *dynamic type* of that location or as a char array. Boost optional does an explicit placement new of the stored type on the buffer which changes the dynamic type of the buffer so it should be safe from a standard point of view. Before reusing the buffer It invokes the destructor of the type, which ends the object lifetime. That said, gcc has been known to miscompile placement new from time to time (but it should be more or less fixed on recent versions: it pretty much acts as a compiler optimization barrier). In the end, IMHO the warning is spurious and could be safely ignored (modulo compiler bugs).
[Note that it is not the cast itself that violates the strict aliasing rules, it is the later dereference of the pointer resulting from the cast.]
[Note that I'm pretty sure that switching from static_cast to reinterpret_cast doesn't help.]
One of the escape hatches provided by the strict aliasing rules involves ensuring that there is a union type in scope at the point of the conversion which contains both of the involved types. [The "visible union" rule of 6.5.2.3#5.]
I do not think this rule actually help you: you can't still read from a field different from the last written to (except for some exceptions like structures with common initial sequences.) Most compilers give additional guarantees, though.
Another escape hatch offered by the strict aliasing rules involves memcpy and memmove. [6.5#6] I think that the following implementation of aligned_storage::address will satisfy the strict aliasing rules:
void* address() { void* data = data_.data_.buf; return memmove(data, data, sizeof(*data)); }
And I think gcc will optimize away the memmove, at least at sufficient optimization levels. But note that I have not tested this at all.
[Hm, using a size of 0 or 1 or some similar small value might be sufficient to quiet the compiler; I'm not certain whether it is sufficient to satisfy the letter of the C standard.]
I do not see how this helps, first of all sizeof(*data) is illegal (sizeof(void) doesn't make any sense, gcc defines it as 1 as an extension), then how copying one byte will fix aliasing problems?
The idea is that memmove (and memcpy, but that requres non-overlapping source and destination, which we don't have here) is defined by the aliasing rules to strip away the type of the destination, in a way that a simple cast does not.
I do not think it really strips away the type of the destination: in fact it is often unsed to bit cast from integers to float and back: if a memcpy(&some_float, &some_integer, sizeof(int32_t)) really did change the type of some_float, it would break a lot of code (you could no longer threat some_float as a float). What is guarateed is that some_float will contain the same bit pattern of some_integer, while still maintaining the float type. It assumes that the bit pattern of the source is a valid pattern for the destination type, otherwise it is UB.
[And yes, I agree that this is rather grotesque.]
Aliasing rules really are a mess. -- gpd

Hi All, Sorry but I just couldn't find time to answer this before.
On Thu, May 7, 2009 at 3:37 AM, Kim Barrett <kab.conundrums@verizon.net> wrote:
At 7:17 PM -0300 5/5/09, Brad Spencer wrote: In boost/aligned_storage.hpp, the class template boost::aligned_storage defines its address member function thusly:
void* address() { return this; } const void* address() const { return this; }
I'm not 100% sure, but this could be very wrong as, IIRC, there is no guarantee that for non PODS the address of the first element (i.e. aligned_storage.data_) is the same as 'this'.
but why doesn't aligned storage implements address() as:
void * address { return this.data_.data_.buf; }
BTW, I see that boost optional actually uses its own implementation of aligned storage,
Indeed.. whose code is not like the one above.
whose address member function returns a pointer to a char buffer, so it shouldn't be a problem.
Precisely... a char* is explcitely allowed to alias any object, so, for Boost.Optional at least, the warning is clearly spurious.
There is code in boost/optional/optional.hpp (and probably the other libs you mention) which looks like:
internal_type* get_object() { return static_cast<internal_type*>(m_storage.address()); }
What this leads, after some expansion, to something roughly equivalent to
static_cast<internal_type*>( static_cast<void*>( <a pointer to an instance of aligned_storage> ))
Not in the case of optional. It is:
static_cast<internal_type*>( static_cast<void*>( <a char*> )) which should not raise any strict aliasing warnings
which I believe is not strict aliasing safe. It is effectively a cast from aligned_storage* to internal_type*, with a trip through void* along the way. That trip through void* does not make the conversion satisfy the strict aliasing rules.
IIRC aliasing rules talk about dynamic types of objects: roughly, you can access a memory location only with a type which is the same as the *dynamic type* of that location or as a char array.
Boost optional does an explicit placement new of the stored type on the buffer
Via a char*, so the placement new is merely taking the address of a suitably aligned memory block.
which changes the dynamic type of the buffer
Just for the record, no it doesn't. Best -- Fernando Cacciola SciSoft Consulting, Founder http://www.scisoft-consulting.com

On Fri, May 15, 2009 at 9:11 PM, Fernando Cacciola <fernando.cacciola@gmail.com> wrote: [ the following text was by me] z>>
IIRC aliasing rules talk about dynamic types of objects: roughly, you can access a memory location only with a type which is the same as the *dynamic type* of that location or as a char array.
Boost optional does an explicit placement new of the stored type on the buffer
Via a char*, so the placement new is merely taking the address of a suitably aligned memory b'lock.
which changes the dynamic type of the buffer
Just for the record, no it doesn't.
Wait, how it doesn't? It uses placement new. The lifetime of an object, and thus its type begins at the new expression and ends when deleted. Initially the buffer of an optional<T> certainly hasn't type T (in fact, being an union, the type would be undetermined until the first store to a member of the union), but when placement new T is used and the object is constructed, the buffer certainly has type T. As the type is a property of the object, when the object is deleted, the buffer certainly no longer has that type. So I think you can say that, in optional at least, placement new changes the type of the buffer, from undetermined to T. Am I missing something? -- gpd

On 2009-05-15 21:11, Fernando Cacciola wrote:
Hi All,
Sorry but I just couldn't find time to answer this before.
On Thu, May 7, 2009 at 3:37 AM, Kim Barrett <kab.conundrums@verizon.net> wrote:
At 7:17 PM -0300 5/5/09, Brad Spencer wrote: In boost/aligned_storage.hpp, the class template boost::aligned_storage defines its address member function thusly:
void* address() { return this; } const void* address() const { return this; }
I'm not 100% sure, but this could be very wrong as, IIRC, there is no guarantee that for non PODS the address of the first element (i.e. aligned_storage.data_) is the same as 'this'.
but why doesn't aligned storage implements address() as:
void * address { return this.data_.data_.buf; }
BTW, I see that boost optional actually uses its own implementation of aligned storage,
Indeed.. whose code is not like the one above.
whose address member function returns a pointer to a char buffer, so it shouldn't be a problem.
Precisely... a char* is explcitely allowed to alias any object, so, for Boost.Optional at least, the warning is clearly spurious.
Has this issue been reported to gcc, then? I searched their bugzilla and didn't find anything relevant. I'm also curious whether someone has found a workaround for this. I'd like to patch optional.hpp, so I can keep building with -Werror. Regards, Thomas Sondergaard
participants (3)
-
Fernando Cacciola
-
Giovanni Piero Deretta
-
Thomas Sondergaard