views:

165

answers:

5

I have a problem caused by breaking strict pointer aliasing rule. I have a type T that comes from a template and some integral type Int of the same size (as with sizeof). My code essentially does the following:

T x = some_other_t;
if (*reinterpret_cast <Int*> (&x) == 0)
  ...

Because T is some arbitary (other than the size restriction) type that could have a constructor, I cannot make a union of T and Int. (This is allowed only in C++0x only and isn't even supported by GCC yet).

Is there any way I could rewrite the above pseudocode to preserve functionality and avoid breaking strict aliasing rule? Note that this is a template, I cannot control T or value of some_other_t; the assignment and subsequent comparison do happen inside the templated code.

(For the record, the above code started breaking on GCC 4.5 if T contains any bit fields.)

+1  A: 

How about this:

Int zero = 0;
T x = some_other_t;
if (std::memcmp(&x, &zero, sizeof(zero)) == 0)

It might not be as efficient, but it should get rid of the warning.


ADDENDUM #1:

Since T is constrained to be the same size as Int, make yourself a dummy bitwise zero value of type T and compare directly against it (instead of casting and comparing agaist Int(0)).

If your program is single-threaded, you could have something like this:

template <typename T>
class Container
{
public:
    void foo(T val)
    {
        if (zero_ == val)
        {
            // Do something
        }
    }

private:
    struct Zero
    {
        Zero() {memset(&val, 0, sizeof(val));}
        bool operator==(const T& rhs) const {return val == rhs;}
        T val;
    };
    static Zero zero_;
};

If it is multi-threaded, you'll want to avoid using a static member zero_, and have each container instance hold it's own zero_ member:

template <typename T>
class MTContainer
{
public:
    MTContainer() {memset(zero_, 0, sizeof(zero_));}

    void foo(T val)
    {
        if (val == zero_)
        {
            // Do something
        }
    }

private:
    T zero_;
};

ADDENDUM #2:

Let me put the above addendum in another, simpler way:

// zero is a member variable and is inialized in the container's constructor
T zero;
std::memset(&zero, 0, sizeof(zero));

T x = some_other_t;
if (x == zero)
Emile Cormier
Upvoted because this seems to work fine. However I would sure like a better solution...
doublep
@doublep: Added another, more efficient solution to my answer.
Emile Cormier
Unfortunately, this is atrocious for performance. When `T` itself is `int`, I'm getting 2.5x times slowdown on GCC 4.5 -O2 (this is a composite test of "almost-real" use). Apparently, GCC cannot optimize `memcmp()` away.
doublep
@douplep: I don't use `memcmp()` at all in my updated answer.
Emile Cormier
This supposes that `T` is Default Constructible, which might not be the case. This also supposes that `0x00000000` is not a meaningful value of `T`.
Matthieu M.
@doublep: Added another addendum to my answer.
Emile Cormier
@Emile Cormier: As far as I see, your updated answer uses `T::operator==()`. This is a no-no, because you don't know what that does. For instance, `T` might be a wrapper class over some pointer and in `==` assert against being non-zero. Or dereference the pointer (being bitwise 0) and thus cause a segmentation fault.
doublep
@Matthieu: Isn't it a requirement that `T` be default constructible for STL containers? In one of douplep's comments, he says that he wants to compare `T` to bitwise zeroes. In my solution, I don't presume that `T` can be inialized with 0 (it could be a tuple), so I force `zero_`'s bits to 0x00000000 using `memset`.
Emile Cormier
@Emile Cormier: Yes, `T` is default-constructible. But you cannot invoke `T::operator==` on an unitialized object. For "common" types this will work, but generally speaking, this is undefined behavior. It is easy to create a type that will fail in such circumstances.
doublep
@doublep: Good point about my solution forcing `T` to have `operator==`.
Emile Cormier
@Emile: minor thing, but you have many missing `)`.
KennyTM
+1  A: 

Have you heard about boost::optional ?

I must admit I am unclear as to the real problem here... but boost::optional allow to store by value and yet know whether or not the actual memory has been initialized. I also allows in place construction and destruction, so could be a good fit I guess.

EDIT:

I think I finally grasped the problem: you want to be able to allocate a lot of objects, at various points in memory, and you'd like to know whether or not the memory at this point really holds an object or not.

Unfortunately your solution has a huge issue: it's incorrect. If ever T can somehow be represented by a null bit pattern, then you'll think it's unitialized memory.

You will have to resort yourself to add at least one bit of information. It's not much really, after all that's only 3% of growth (33 bits for 4 bytes).

You could for example use some mimick boost::optional but in an array fashion (to avoid the padding loss).

template <class T, size_t N>
class OptionalArray
{
public:


private:
  typedef unsigned char byte;

  byte mIndex[N/8+1];
  byte mData[sizeof(T)*N]; // note: alignment not considered
};

Then it's as simple as that:

template <class T, size_t N>
bool OptionalArray<T,N>::null(size_t const i) const
{
  return mIndex[i/8] & (1 << (i%8));
}

template <class T, size_t N>
T& OptionalArray<T,N>::operator[](size_t const i)
{
  assert(!this->null(i));
  return *reinterpret_cast<T*>(mData[sizeof(T)*i]);
}

note: For simplicity's sake I have not considered the issue of alignment. If you don't know about the subject, read about it before fiddling with memory :)

Matthieu M.
With the current setup I'm using 4 bytes per element — i.e. not more than the element size. It's about efficiency: this matters when you have many thousands of elements.
doublep
"Unfortunately your solution has a huge issue: it's incorrect" — can you elaborate on this?
doublep
Read the next sentence: say `T` is an `Int` and I assign 0 to it, then your test will say: "unitialized" while it is initialized (to 0). Your are basically using a "Magic Value" but without any guarantee that your "Magic Value" is out of the domain of meaningful values. That is why you need at least one more bit to store information.
Matthieu M.
@Matthieu: How about not making assumptions especially when not needed to answer the question? I certainly have this case covered. It is even somewhere in comments to the questions, though that's not very relevant to the original problem.
doublep
BTW, ironically, `boost::optional` generates strict-aliasing warnings as well.
Emile Cormier
A: 

Use a 33-bit computer. ;-P

Emile Cormier
A: 

It feels like a hack, but apparently I found a solution: using volatile for Int casting. Essentially, what I am doing now is:

T x = some_other_t;
if (*reinterpret_cast <volatile Int*> (&x) == 0)
  ...

The problem with bitfield T is now gone. Still, I don't feel quite happy about this as volatile is not well-defined in C++ AFAIK...

doublep
You might be on to something. I hope the language lawyers will see this and comment.
Emile Cormier
I'm not an expert on these matters, but my understanding of the `volatile` keyword is that its only effect is to ensure the compiler will not make any optimisations that would lead to incorrect behaviour if the variable refers to e.g. an I/O register, i.e. something that can be modified externally. What you have above is not one of these situations, so you probably can't rely on `volatile` to guarantee correct behaviour. In other words, it's not a workaround for the strict-aliasing rules.
Oli Charlesworth
@Oli Charlesworth: From specification: [ Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. — end note ]I'd say this case fits "might be changed by means undetectable by an implementation" definition, though it is of course debatable.
doublep
@doublep: I am not a language lawyer, so the semantics of that spec point are indeed up for debate. However, if all it took to solve strict-aliasing problems was use of `volatile`, there would no longer be questions about strict aliasing on Stack Overflow! That there are still countless such question implies that this is not the solution!
Oli Charlesworth
@Oli Charlesworth: You have a point; I asked this [as a separate question](http://stackoverflow.com/questions/2981827/strict-pointer-aliasing-is-access-through-a-volatile-pointer-reference-a-solut).
doublep
+1  A: 
static inline int is_T_0(const T *ob)
{
        int p;
        memcpy(&p, ob, sizeof(int));
        return p == 0;
}

void myfunc(void)
{
    T x = some_other_t;
    if (is_T_0(&x))
        ...

On my system, GCC optimizes away both is_T_0() and memcpy(), resulting in just a few assembly instructions in myfunc().

Daniel Stutzbach
According to [the other question](http://stackoverflow.com/questions/2981827/strict-pointer-aliasing-is-access-through-a-volatile-pointer-reference-a-solut), this is the way to go.
doublep
Chris Lutz