【问题标题】:Strict pointer aliasing: any solution for a specific problem?严格的指针别名:针对特定问题的任何解决方案?
【发布时间】:2010-06-05 15:23:43
【问题描述】:

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

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

因为T 是一些可以有构造函数的任意(大小限制除外)类型,所以我不能将TInt 合并。 (这仅在 C++0x 中允许,甚至 GCC 还不支持)。

有什么方法可以重写上面的伪代码来保留功能并避免违反严格的别名规则?请注意,这是一个模板,我无法控制Tsome_other_t 的值;赋值和后续比较确实发生在模板代码中。

(作为记录,如果T 包含任何位字段,上述代码在 GCC 4.5 上开始中断。)

【问题讨论】:

  • 你想做什么?我想不出该代码有意义的许多情况。它当然没有被标准很好地指定。因此,假设这个 hack 确实是必要的(它可能不是),你可能只需要使用适当的编译器标志来禁用严格的别名。
  • @jalf:这是一个独特的容器。我用整数 0 标记空位置。但是,由于T 可以是任何值,包括按位 0,我最多需要将一个位置标记为“非空,即使它看起来像空”。比较是检查是否应该标记x
  • 我不清楚你是如何解决这个问题的 - reinterpret_cast 如何让你忽略存储 0 的两个不同原因?
  • @doublep:这还不完全清楚。您实际上是在尝试测试 x 在内存中是否由全零表示?
  • @Stephen:如果两个T== 不相等,则它们不能按位相等,至少对于容器相等定义中的任何有用。因此,唯一容器中不能有两个按位为零的元素(如果相等是合理的)。

标签: c++ generics reinterpret-cast strict-aliasing type-punning


【解决方案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() 中只有几条汇编指令。

【讨论】:

【解决方案2】:

你听说过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 个字节——即不超过元素大小。这关乎效率:当您拥有数千个元素时,这很重要。
  • “很遗憾,你的解决方案有一个大问题:它不正确”——你能详细说明一下吗?
  • 阅读下一句:说TInt,我给它赋值0,然后你的测试会说:“初始化”,而它被初始化(为0)。您基本上使用的是“魔术值”,但不能保证您的“魔术值”超出有意义的值的范围。这就是为什么您至少需要多一位来存储信息。
  • 不做假设怎么样?尤其是在不需要回答问题的时候?我当然有这个案子。它甚至在问题的 cmets 中的某个地方,尽管这与原始问题不太相关。
  • 顺便说一句,具有讽刺意味的是,boost::optional 也会生成严格别名警告。
【解决方案3】:

使用 33 位计算机。 ;-P

【讨论】:

    【解决方案4】:

    为什么不简单:

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

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

    【讨论】:

      【解决方案5】:

      这个怎么样:

      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:为我的答案添加了另一个更有效的解决方案。
      • 不幸的是,这对性能来说是非常糟糕的。当T 本身是int 时,我在 GCC 4.5 -O2 上的速度降低了 2.5 倍(这是“几乎真实”使用的综合测试)。显然,GCC 无法优化memcmp()
      • @douplep:我在更新的答案中根本不使用memcmp()
      • 这假定T 是默认可构造的,但可能并非如此。这也假设0x00000000 不是T 的有意义值。
      猜你喜欢
      • 1970-01-01
      • 2010-09-20
      • 2021-09-06
      • 2014-07-13
      • 2021-05-10
      • 2020-12-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多