8

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.)

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • 1
    What are you trying to do? I can't think of many situations where that code is meaningful. It certainly isn't well-specified by the standard. So assuming this hack is actually necessary (which it probably isn't), you might have to just use the appropriate compiler flag to disable strict aliasing. – jalf Jun 05 '10 at 15:28
  • @jalf: It's a unique container. I mark empty positions with integral 0. However, since `T` can be anything, including bitwise 0, I need to mark at most one position as "not empty, even though it looks like empty". The comparison is the check for whether `x` should be marked so or not. –  Jun 05 '10 at 15:32
  • I'm unclear how you're solving this problem - how does a `reinterpret_cast` allow you to ignore the two different reasons for storing `0`? – Stephen Jun 05 '10 at 15:41
  • 1
    @doublep: That's still not entirely clear. Are you essentially trying to test whether `x` is represented in memory by all zeros? – Oliver Charlesworth Jun 05 '10 at 15:42
  • @Stephen: If two `T` are not equal with `==`, they cannot be bitwise equal, at least for any useful in a container definition of equality. So, there cannot be two bitwise-zero elements in a unique container (if equality is sane). –  Jun 05 '10 at 15:53

5 Answers5

1

Why not simply:

const Int zero = 0;
if (memcmp(&some_other_t, &zero, sizeof(zero)) == 0)
  /* some_other_t is 0 */

(you may want to try to add also the static qualifier to zero to see if it makes a difference performance-wise)

CAFxX
  • 28,060
  • 6
  • 41
  • 66
1

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.
  • 287,565
  • 48
  • 449
  • 722
  • 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. –  Jun 05 '10 at 16:44
  • "Unfortunately your solution has a huge issue: it's incorrect" — can you elaborate on this? –  Jun 05 '10 at 17:23
  • 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. Jun 05 '10 at 17:51
  • 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. –  Jun 05 '10 at 18:01
  • BTW, ironically, `boost::optional` generates strict-aliasing warnings as well. – Emile Cormier Jun 05 '10 at 18:23
1

Use a 33-bit computer. ;-P

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
1
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
  • 74,198
  • 17
  • 88
  • 77
  • 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. –  Jun 05 '10 at 22:01
  • Personally I'd make the usage cleaner (to me) by making `is_T_0` take a `const T&`, but you should check to make sure GCC still optimizes everything away in that case. – Chris Lutz Jun 05 '10 at 22:17
0

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
  • 28,391
  • 15
  • 94
  • 122
  • Upvoted because this seems to work fine. However I would sure like a better solution... –  Jun 05 '10 at 15:59
  • @doublep: Added another, more efficient solution to my answer. – Emile Cormier Jun 05 '10 at 16:49
  • 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. –  Jun 05 '10 at 17:19
  • @douplep: I don't use `memcmp()` at all in my updated answer. – Emile Cormier Jun 05 '10 at 17:48
  • 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. Jun 05 '10 at 17:53
  • @doublep: Added another addendum to my answer. – Emile Cormier Jun 05 '10 at 17:56
  • @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. –  Jun 05 '10 at 17:58
  • @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 Jun 05 '10 at 18:02
  • @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. –  Jun 05 '10 at 18:05
  • @doublep: Good point about my solution forcing `T` to have `operator==`. – Emile Cormier Jun 05 '10 at 18:10