Strict pointer aliasing: any solution for a specif

2019-06-16 15:47发布

问题:

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:

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



回答2:

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



回答3:

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)


回答4:

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)



回答5:

Use a 33-bit computer. ;-P



回答6:

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