严格的指针别名:对于一个特定问题有任何解决方案吗?

8

我遇到了一个由于违反严格指针别名规则而引起的问题。我有一个类型T,它来自模板和一些与sizeof相同大小的整数类型Int。我的代码实际上执行以下操作:

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

由于 T 是一个除了大小限制以外的任意类型,它可能有一个构造函数,因此我无法将 TInt 合并为一个联合体。这只在 C++0x 中允许,在当前版本的 GCC 中也还不支持。
有没有一种方法可以重新编写上面的伪代码来保留功能并避免违反严格别名规则?请注意,这是一个模板,我无法控制 Tsome_other_t 的值;赋值和后续比较确实发生在模板化的代码中。
(仅供参考,如果 T 包含任何位域,则上述代码从 GCC 4.5 开始出现错误。)

1
你想做什么?我想不出很多情况下那段代码有意义。它肯定没有被标准很好地规定。所以假设这个黑客实际上是必要的(这可能并不是),你可能需要使用适当的编译器标志来禁用严格别名。 - jalf
@jalf:这是一个独特的容器。我用整数0标记空位置。然而,由于“T”可以是任何东西,包括按位0,因此我需要将最多一个位置标记为“非空,即使它看起来为空”。比较是检查是否应该标记“x”的检查。 - user319799
我不清楚你如何解决这个问题 - reinterpret_cast 如何使你忽略存储 0 的两种不同原因? - Stephen
1
@doublep:这还不是很清楚。你的本质目的是要测试x是否在内存中被所有零表示吗? - Oliver Charlesworth
@Stephen:如果两个 T== 不相等,那么它们就不可能按位相等,至少对于任何有用的容器相等定义来说是这样。因此,在唯一的容器中(如果相等性是合理的话),不能有两个按位零元素。 - user319799
5个回答

1

你听说过 boost::optional 吗?

我必须承认,我对这个真正的问题不太清楚... 但是 boost::optional 允许按值存储,并且可以知道实际内存是否已经初始化。它还允许就地构造和销毁,所以我想它可能非常适合。

编辑

我想我终于理解了问题:你想要能够在内存的各个点上分配许多对象,并且希望知道该点上的内存是否真的持有对象。

不幸的是,你的解决方案有一个很大的问题:它是不正确的。如果 T 可以以某种方式用 null 位模式表示,那么你会认为它是未初始化的内存。

你必须至少添加一位信息来解决这个问题。其实并不是很多,毕竟只增加了 3% 的空间(4 字节为 33 位)。

例如,你可以使用一些类似于数组的方式来模拟 boost::optional(以避免填充损失)。

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
};

那么,就是这么简单:

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]);
}

注意:为了简单起见,我没有考虑对齐问题。 如果您不了解这个主题,请在调整内存之前先阅读相关资料 :)


使用当前设置,每个元素我正在使用4个字节 - 即不超过元素大小。这是关于效率的问题:当您有成千上万个元素时,这很重要。 - user319799
很遗憾,您的解决方案存在一个严重问题:它是不正确的。您能详细说明一下吗? - user319799
阅读下一句话:假设 T 是一个 Int,我将其赋值为 0,那么你的测试将会说:“未初始化”,但实际上它已经被初始化(为 0)。你基本上是在使用“魔法值”,但没有任何保证你的“魔法值”超出了有意义的值域。这就是为什么你需要至少再多一个比特来存储信息。 - Matthieu M.
如果不必要就不要做出假设来回答问题,我的确已经考虑到了这种情况。尽管这与原始问题无关,但它甚至在问题的评论中提到过。 - user319799
顺带一提,具有讽刺意味的是,boost::optional 也会产生严格别名警告。 - Emile Cormier

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

在我的系统上,GCC 对 is_T_0()memcpy() 两者都进行了优化,导致在 myfunc() 中只有几条汇编指令。

根据另一个问题的回答,这是正确的方法。 - user319799
个人而言,我会使is_T_0的使用更加简洁(对我来说),即通过使其接受const T&。 但您应该检查确保GCC在这种情况下仍然能够优化所有内容。 - Chris Lutz

1

使用33位计算机。;-P


1
为什么不简单地这样做:
const Int zero = 0;
if (memcmp(&some_other_t, &zero, sizeof(zero)) == 0)
  /* some_other_t is 0 */

(您可能想尝试将 static 限定符添加到 zero 中,以查看它是否在性能方面有所不同)


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

可能不是最高效的,但它应该可以消除警告。


附录 #1:

由于 T 受限于与 Int 相同的大小,因此请创建一个类型为 T 的虚拟位零值,并直接与其进行比较(而不是进行转换并与 Int(0) 进行比较)。

如果您的程序是单线程的,可以像这样:

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_;
};

如果是多线程的话,您将希望避免使用静态成员zero_,并让每个容器实例持有它自己的zero_成员:

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

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

private:
    T zero_;
};

补充 #2:

让我用另一种更简单的方式表达上面的补充:

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

@doublep:在我的答案中添加了另一种更高效的解决方案。 - Emile Cormier
很不幸,这对性能来说太糟糕了。当T本身是int时,在GCC 4.5 -O2下我得到了2.5倍的减速(这是一个“几乎真实”的使用测试组合)。显然,GCC无法优化掉memcmp() - user319799
@douplep:在我更新的答案中,我根本不使用memcmp()函数。 - Emile Cormier
这假定T是具有默认构造函数的,但这并不一定成立。同时也假设0x00000000不是T的一个有意义的值。 - Matthieu M.
@doublep:在我的回答中又添加了一个补充。 - Emile Cormier
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接