10

We just upgraded our compiler to gcc 4.6 and now we get some of these warnings. At the moment our codebase is not in a state to be compiled with c++0x and anyway, we don't want to run this in prod (at least not yet) - so I needed a fix to remove this warning.

The warnings occur typically because of something like this:

struct SomeDataPage
{
  // members
  char vData[SOME_SIZE];
};

later, this is used in the following way

SomeDataPage page;
new(page.vData) SomeType(); // non-trivial constructor

To read, update and return for example, the following cast used to happen

reinterpret_cast<SomeType*>(page.vData)->some_member();

This was okay with 4.4; in 4.6 the above generates:

warning: type punned pointer will break strict-aliasing rules

Now a clean way to remove this error is to use a union, however like I said, we can't use c++0x (and hence unrestricted unions), so I've employed the horrible hack below - now the warning has gone away, but am I likely to invoke nasal daemons?

static_cast<SomeType*>(reinterpret_cast<void*>(page.vData))->some_member();

This appears to work okay (see simple example here: http://www.ideone.com/9p3MS) and generates no warnings, is this okay(not in the stylistic sense) to use this till c++0x?

NOTE: I don't want to use -fno-strict-aliasing generally...

EDIT: It seems I was mistaken, the same warning is there on 4.4, I guess we only picked this up recently with the change (it was always unlikely to be a compiler issue), the question still stands though.

EDIT: further investigation yielded some interesting information, it seems that doing the cast and calling the member function in one line is what is causing the warning, if the code is split into two lines as follows

SomeType* ptr = reinterpret_cast<SomeType*>(page.vData);
ptr->some_method();

this actually does not generate a warning. As a result, my simple example on ideone is flawed and more importantly my hack above does not fix the warning, the only way to fix it is to split the function call from the cast - then the cast can be left as a reinterpret_cast.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Nim
  • 33,299
  • 2
  • 62
  • 101
  • 1
    I think that after you do `new(page.vData) SomeType()`, the actual type of the object located at the same place as `page.vData` is `SomeType`, since that's the last thing stored there, and hence the type pun is legal. The warning triggered by the cast presumably doesn't know for sure that you've done that placement new, though. If I'm right that your original code is in fact OK, then assuming no bugs in GCC the horrible hack should be OK too (since of course GCC's definition of `reinterpret_cast` makes the resulting pointer the same). Not sure, though. – Steve Jessop Sep 20 '11 at 10:45
  • @Steve, seems like I was wrong, the warning is there in 4.4 as well - just not picked up. Question about the hack still stands though. – Nim Sep 20 '11 at 10:54
  • "_this actually does not generate a warning. As a result,_" with which options? – curiousguy Dec 13 '11 at 15:19
  • @curiousguy, `-Wall` if I remember correctly.. – Nim Dec 13 '11 at 15:27
  • @Nim You might want to try again with some optimisation. – curiousguy Dec 13 '11 at 15:35
  • @curiousguy, `-O3`, I haven't seen the warnings... – Nim Dec 13 '11 at 15:40
  • @Nim It isn't guaranteed, but in general more optimisation means more code analysis means more warnings. – curiousguy Dec 13 '11 at 15:44

4 Answers4

2
SomeDataPage page;
new(page.vData) SomeType(); // non-trivial constructor
reinterpret_cast<SomeType*>(page.vData)->some_member();

This was okay with 4.4; in 4.6 the above generates:

warning: type punned pointer will break strict-aliasing rules

You can try:

SomeDataPage page;
SomeType *data = new(page.vData) SomeType(); // non-trivial constructor
data->some_member();
curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • Thanks for the answer, I think I updated the question, the only way I can avoid the warning was to split the cast (unfortunately the `new` operation happens at initialization time, and the call to `some_member()` happens during normal running) - so there is now a `reinterpret_cast` as I added to the edit. This is the only way I could get it to remove the warning. I should have added this as an answer - but never got around to it... – Nim Dec 13 '11 at 15:25
  • 1
    @Nim The problem here is that by decomposing the operations, you are not fixing the underlying problem (if there is really an underlying problem), you are just making it harder for the compiler to warn you. – curiousguy Dec 13 '11 at 15:42
  • AFAIK there is no underlying problem, I'm using placement new to construct the object; IMO, it's a spurious warning that needed to be silenced in this specific case - without the generally turn this type of warning off switch. – Nim Dec 13 '11 at 15:45
  • @Nim My interpretation is also that this is a spurious warning; the issue I have with the strict aliasing rules is that the rules are not clear enough for everyone, in particular compiler writers. – curiousguy Dec 13 '11 at 15:58
1

Why not use:

SomeType *item = new (page.vData) SomeType();

and then:

item->some_member ();

I don't think a union is the best way, it may also be problematic. From the gcc docs:

`-fstrict-aliasing'
 Allows the compiler to assume the strictest aliasing rules
 applicable to the language being compiled.  For C (and C++), this
 activates optimizations based on the type of expressions.  In
 particular, an object of one type is assumed never to reside at
 the same address as an object of a different type, unless the
 types are almost the same.  For example, an `unsigned int' can
 alias an `int', but not a `void*' or a `double'.  A character type
 may alias any other type.

 Pay special attention to code like this:
      union a_union {
        int i;
        double d;
      };

      int f() {
        a_union t;
        t.d = 3.0;
        return t.i;
      }
 The practice of reading from a different union member than the one
 most recently written to (called "type-punning") is common.  Even
 with `-fstrict-aliasing', type-punning is allowed, provided the
 memory is accessed through the union type.  So, the code above
 will work as expected.  However, this code might not:
      int f() {
        a_union t;
        int* ip;
        t.d = 3.0;
        ip = &t.i;
        return *ip;
      }

How this relates to your problem is tricky to determine. I guess the compiler is not seeing the data in SomeType as the same as the data in vData.

Skizz
  • 69,698
  • 10
  • 71
  • 108
  • @Nim: Is there any reason why not? Perhaps have a dummy constructor that doesn't overwrite the `vData` and just in-place-new when you want to use it. – Skizz Sep 20 '11 at 10:53
  • there are some complications which I have not mentioned, the `SomeType` is infact a variant, and I want to make use of the fact that assigning to the same type held by the variant is cheaper than constructing the default(then reconstructing the new type). – Nim Sep 20 '11 at 10:58
0

I'd be more concerned about SOME_SIZE not being big enough, frankly. However, it is legal to alias any type with a char*. So simply doing reinterpret_cast<T*>(&page.vData[0]) should be just fine.

Also, I'd question this kind of design. Unless you're implementing boost::variant or something similar, there's not much reason to use it.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 2
    It is legal to alias any type with `char*`, but it is not necessarily legal to alias `char*` with any type. The type-pun rules are not symmetric with respect to the actual type of the object, and the type used to access it. – Steve Jessop Sep 20 '11 at 11:18
  • While the type aliasing rules are not in general symmetric, they are symmetric in relation to `char` -- you can alias any type with `char` and can alias `char` with any type. Since this code is aliasing with `char`, the compiler warning is incorrect. – Chris Dodd Sep 20 '11 at 17:04
  • @Chris: check 3.10/15. It does not say that an object whose dynamic type is `char` can be accessed through an lvalue of any type. Various cases of `SomeType` are OK, but the only UDTs that qualify are an aggregate or union with a `char`, `signed char`, `unsigned char` member, or cv-qualified version of one of those. `SomeType` might well not be such an aggregate or union. There is no symmetry. So this alias is only legal since the dynamic type of the object is `SomeType`, thanks to the placement new. If there's somewhere else in the standard that permits any other aliasing, I've missed it. – Steve Jessop Sep 20 '11 at 18:05
  • @SteveJessop: I consider this a bug in the standard then. There somehow has to be a way to have a blob of untyped memory of a specific number of bytes. If there isn't, than an approved way of doing this needs to be created. If nothing else, it makes it practically impossible to implement an allocator in C++. – Omnifarious Sep 21 '11 at 20:04
  • @Omnifarous: there is an awkwardness there - placement `new` gives you a means to convert raw memory (`char`) into your object of type `SomeType` without ever having to refer to it as any other lvalue, since the implementation takes care of that. Clearly that's not how C resolves the issue, though, and POD types have some rules about lifetime and the fact that you can end it by "reusing" their memory. That "reuse" might interact with the aliasing rules in some way, I can't remember. If any of that implies a symmetry not stated in 3.10/15, then my apologies. – Steve Jessop Sep 21 '11 at 22:07
  • @SteveJessop This is a confused, sad, frightening state of affairs: there are some different interpretations of the aliasing rules, not just by some random C++ programmer, also by compiler implementers (at least GNU people). And one interpretation, the strongest one, means that **you cannot have a custom allocator in C/C++, end of story.** The second less strict interpretation means that **you cannot have a custom allocator in C** but placement-new saves you. Please note that the ISO C standard seems to have quite different aliasing semantics (and IMO the C committee went crazy WRT aliasing). – curiousguy Dec 13 '11 at 15:08
0
struct SomeDataPage
{
  // members
  char vData[SOME_SIZE];
};

This is problematic for aliasing/alignment reasons. For one, the alignment of this struct isn't necessarily the same as the type you're trying to pun inside it. You could try using GCC attributes to enforce a certain alignment:

struct SomeDataPage { char vData[SOME_SIZE] __attribute__((aligned(16))); };

Where alignment of 16 should be adequate for anything I've come across. Then again, the compiler still won't like your code, but it won't break if the alignment is good. Alternatively, you could use the new C++0x alignof/alignas.

template <class T>
struct DataPage {
   alignof(T) char vData[sizeof(T)];
};
Maister
  • 4,978
  • 1
  • 31
  • 34
  • Unfortunately I cannot use a template here, as the we use a mechanism to extend `SomeType` in such a way that backward compatibility is maintained with older releases - as a result, all releases must have sufficient storage to account for potential changes to `SomeType`... – Nim Sep 23 '11 at 09:29