如何解决指针别名问题? [英] How to resolve pointer alias issues?

查看:91
本文介绍了如何解决指针别名问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

不小心使用模板会导致膨胀.避免这种膨胀的一种方法是拥有一个薄的类型安全模板,该模板包装了非类型安全的非模板代码.为此,包装程序需要为非模板代码提供某种方式以访问其不了解的内容.

例如,在数据结构中,包装器定义了节点结构.不安全的代码需要读写节点,但必须通过包装程序指定的某种接口间接进行读写.

实现此接口的一种方法是在结构(由不安全的代码定义)中填充诸如函数指针和包装程序确定的常量之类的详细信息.一种相关的常数是特定字段的偏移量(在某种结构内).不安全的代码可以使用该偏移量(和某些指针算法)直接访问该字段.

这正变得有问题-随着优化程序变得更加激进,这可能导致指针别名问题.如果节点可以转储库,则尤其如此.例如,可以从二叉树中提取节点并重新链接以形成链接列表.令人讨厌的另一个例子是在单元测试时发生.

我目前有一个沿这些行编写的容器库,它目前不会导致这些问题-但很快就会发生.它避免这些问题的原因是因为所有单元测试都应用于容器(而不是基础代码),并且因为节点从不逃避容器.也就是说,始终以相同的指针-算术方式访问节点,因此永远不会出现指针别名优化问题.

不幸的是,我很快将需要允许从容器中提取节点,并且可能还需要对基础的不安全代码进行单元测试.

我没有处理这个特定的库,而是从这里遇到相同问题的旧的二叉树库中提取了一个简单得多的摘录.在VC ++ 9中,它可以正常工作.使用MinGW GCC 4.4.0,调试版本可以运行,但是发行版本失败.问题是优化程序无法发现指针别名的内联和失败.

请明确-我不想在这里"WTF-GOTO !!!"管他呢.问题正在解决优化/指针问题.虽然,如果您能找到一种编写Tree_To_List的方法,该方法结构正确并且不使用隐藏/伪装的goto来实现它,那么我很感兴趣.

此外,缺少基于模板的抽象层(模板c_Bin_Tree_Tool不能完成全部工作-c_Tool完成包装,但是以一种使用方式而不是以可重复使用的形式进行.这只是一个方面提取相关代码的效果.

此代码的作用是通过逐个插入节点来创建不平衡的二叉树,然后平衡该树.平衡的工作方式是将树转换为列表(已经存在),然后将列表转换回树.这棵树在天平之前和之后都被转储到stdio中.

bintree.h ...

inline void* Ptr_Add  (void* p1, std::ptrdiff_t p2)  {  return (void*) (((std::ptrdiff_t) p1) + p2);  }

struct c_Bin_Tree_Closure
{
  typedef int (*c_Node_Comp) (void* p_Node1, void* p_Node2);

  c_Node_Comp    m_Node_Comp;
  std::ptrdiff_t m_Left, m_Right;
};

class c_BT_Policy_Closure
{
  private:
    const c_Bin_Tree_Closure* m_Closure;

  protected:
    void** Left_Of  (void* p)  {  return ((void**) Ptr_Add (p, m_Closure->m_Left ));  }
    void** Right_Of (void* p)  {  return ((void**) Ptr_Add (p, m_Closure->m_Right));  }

    int Compare_Node (void* p_Node1, void* p_Node2) const
    {
      return m_Closure->m_Node_Comp (p_Node1, p_Node2);
    }

  public:
    c_BT_Policy_Closure ()
    {
      m_Closure = 0;
    }

    void Set_Closure (const c_Bin_Tree_Closure& p_Closure)
    {
      m_Closure = &p_Closure;
    }
};

template<class tc_Policy>
class c_Bin_Tree_Tool : public tc_Policy
{
  public:
    c_Bin_Tree_Tool ()
    {
    }

    void *List_To_Tree (size_t p_Size, void* &p_List);
    void  Tree_To_List (void* p_Root, void* &p_First, void* &p_Last, size_t &p_Size);
    void  Balance      (void* &p);
    void  Insert       (void* &p_Tree, void* p_Node);
};

template<class tc_Policy>
void* c_Bin_Tree_Tool<tc_Policy>::List_To_Tree (size_t p_Size, void* &p_List)
{
  if (p_Size == 0)  return 0;

  size_t l_Size = p_Size / 2;
  void*  l_Ptr  = List_To_Tree (l_Size, p_List);

  void* l_This = p_List;
  p_List = *tc_Policy::Right_Of (l_This);

  *tc_Policy::Left_Of (l_This) = l_Ptr;

  l_Size = p_Size - (l_Size + 1);
  *tc_Policy::Right_Of (l_This) = List_To_Tree (l_Size, p_List);

  return l_This;
}

template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Tree_To_List (void* p_Root, void* &p_First, void* &p_Last, size_t &p_Size)
{
  //  Use left links as prev links and right links as next links

  void*  l_Start    = 0;         //  first-item-in-list pointer
  void*  l_Prev     = 0;         //  previous node in list
  void** l_Prev_Ptr = &l_Start;  //  points to the next (ie right) pointer for the next node.
  void*  l_Pos      = p_Root;
  void*  l_Next;
  void*  l_Parent   = 0;
  size_t l_Count    = 0;

  p_Last = 0;

  TOP_OF_LOOP:
    l_Next = *tc_Policy::Left_Of (l_Pos);

    if (l_Next != 0)
    {
      *tc_Policy::Left_Of (l_Pos) = l_Parent;  //  So we can find our way back up the tree
      l_Parent = l_Pos;
      l_Pos    = l_Next;
      goto TOP_OF_LOOP;
    }

  AFTER_STEP_PARENT:
    l_Next = *tc_Policy::Right_Of (l_Pos);

    *tc_Policy::Left_Of (l_Pos) = l_Prev;

    p_Last      = l_Pos;
    l_Prev      = l_Pos;
    *l_Prev_Ptr = l_Pos;
    l_Prev_Ptr  = tc_Policy::Right_Of (l_Pos);
    l_Count++;

    if (l_Next != 0)
    {
      l_Pos = l_Next;
      goto TOP_OF_LOOP;
    }

    if (l_Parent != 0)
    {
      l_Pos    = l_Parent;
      l_Parent = *tc_Policy::Left_Of (l_Pos);
      goto AFTER_STEP_PARENT;
    }

  *l_Prev_Ptr = 0;
  p_First     = l_Start;
  p_Size      = l_Count;
}

template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Balance (void* &p)
{
  void   *l_First, *l_Last;
  size_t l_Count;

  Tree_To_List (p, l_First, l_Last, l_Count);
  p = List_To_Tree (l_Count, l_First);
}

template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Insert (void* &p_Tree, void* p_Node)
{
  void** l_Tree = &p_Tree;

  while (*l_Tree != 0)
  {
    int l_Compare = tc_Policy::Compare_Node (*l_Tree, p_Node);
    l_Tree = ((l_Compare < 0) ? tc_Policy::Right_Of (*l_Tree) : tc_Policy::Left_Of (*l_Tree));
  }

  *l_Tree = p_Node;
  *tc_Policy::Right_Of (p_Node) = 0;
  *tc_Policy::Left_Of  (p_Node) = 0;
};

test_bintree.cpp ...

#include <iostream>

#include "bintree.h"

struct c_Node
{
  c_Node *m_Left, *m_Right;
  int    m_Data;
};

c_Node g_Node0001 = { 0, 0,  1 };  c_Node g_Node0002 = { 0, 0,  2 };
c_Node g_Node0003 = { 0, 0,  3 };  c_Node g_Node0004 = { 0, 0,  4 };
c_Node g_Node0005 = { 0, 0,  5 };  c_Node g_Node0006 = { 0, 0,  6 };
c_Node g_Node0007 = { 0, 0,  7 };  c_Node g_Node0008 = { 0, 0,  8 };
c_Node g_Node0009 = { 0, 0,  9 };  c_Node g_Node0010 = { 0, 0, 10 };

int Node_Compare (void* p1, void* p2)
{
  return (((c_Node*) p1)->m_Data - ((c_Node*) p2)->m_Data);
}

c_Bin_Tree_Closure  g_Closure =
{
  (c_Bin_Tree_Closure::c_Node_Comp) Node_Compare,
  offsetof (c_Node, m_Left ),  offsetof (c_Node, m_Right)
};

class c_Tool : public c_Bin_Tree_Tool<c_BT_Policy_Closure>
{
  protected:
    typedef c_Bin_Tree_Tool<c_BT_Policy_Closure> c_Base;
  public:
    c_Tool ()  {  Set_Closure (g_Closure);  }
    void Insert  (c_Node* &p_Tree, c_Node* p_Node)  {  c_Base::Insert ((void*&) p_Tree, p_Node);  }
    void Balance (c_Node* &p)  {  c_Base::Balance ((void*&) p);  }
};

void BT_Dump (size_t p_Depth, c_Node* p_Node)
{
  if (p_Node != 0)
  {
    BT_Dump (p_Depth + 1, p_Node->m_Left);
    for (size_t i = 0; i < p_Depth; i++)  std::cout << " .";
    std::cout << " " << p_Node->m_Data << std::endl;
    BT_Dump (p_Depth + 1, p_Node->m_Right);
  }
}

int main (int argc, char* argv[])
{
  c_Tool  l_Tool;
  c_Node *l_Root = 0;

  l_Tool.Insert (l_Root, &g_Node0001);
  l_Tool.Insert (l_Root, &g_Node0002);
  l_Tool.Insert (l_Root, &g_Node0003);
  l_Tool.Insert (l_Root, &g_Node0004);
  l_Tool.Insert (l_Root, &g_Node0005);
  l_Tool.Insert (l_Root, &g_Node0006);
  l_Tool.Insert (l_Root, &g_Node0007);
  l_Tool.Insert (l_Root, &g_Node0008);
  l_Tool.Insert (l_Root, &g_Node0009);
  l_Tool.Insert (l_Root, &g_Node0010);

  BT_Dump (0, l_Root);  std::cout << "----------" << std::endl;

  l_Tool.Balance (l_Root);
  BT_Dump (0, l_Root);

  return 0;
}

预期结果是...

 1
 . 2
 . . 3
 . . . 4
 . . . . 5
 . . . . . 6
 . . . . . . 7
 . . . . . . . 8
 . . . . . . . . 9
 . . . . . . . . . 10
----------
 . . . 1
 . . 2
 . 3
 . . . 4
 . . 5
 6
 . . . 7
 . . 8
 . 9
 . . 10

实际发生的情况(MinGW GCC 4.4.0,经过优化的发行版)...

 1
 . 2
 . . 3
 . . . 4
 . . . . 5
 . . . . . 6
 . . . . . . 7
 . . . . . . . 8
 . . . . . . . . 9
 . . . . . . . . . 10
----------
 1

据我所知,平衡"操作正常运行,但是BT_Dump函数无法看到对m_Leftm_Right字段的所有更改.

编辑-是的-否则为什么我将节点1视为新的根.我想这就是当您过多地依赖几个月前进行的调查时会发生的事情.

编辑实际上,节点1作为根是一个问题,但是由于它是旧根-最好不要理会这个问题所在,并自己制定理论;-)

在代码中存在很多问题,因为它们是未定义的标准.我认为最大的问题是节点结构中的链接是c_Node *,但是由于不安全的代码对c_Node一无所知,因此它(通过指针算术)以void *的形式访问它们.

一种解决方法是不安全的代码通过getter和setter函数指针进行所有读写,避免所有指针运算,并确保对c_Node实例的所有访问都通过c_Node *指针完成.甚至更好-接口变成具有getter/setter方法等的类.在完整的二叉树库中,我有替代的策略类可以执行此操作,尽管老实说,当问题出现时,我的真正解决方法是将所有代码都放在根据我很少使用垃圾"文件夹的情况,并且无论如何应该使用boost intrusive列表.

但是,这仍然留下了另一个更复杂,使用更频繁的容器库,但它并没有消失.我想我将不得不做非常痛苦的重构才能摆脱offsetof和指针算术的东西,但是...

C ++规则到底是什么-何时才允许编译器未能发现可能的指针别名?可以重写上面的二进制树代码,使其仍然使用指针算法,仍然允许在库的内部和外部访问/修改节点,但是对于此优化问题是安全的吗?

解决方案

您是否关闭了警告?您应该有一些解引用类型的指针违反了严格的别名",因为那正是您在(void **)Ptr_Add(...

编译器可以自由地假设指向不同类型的指针没有别名(带有一些执行),并且将生成优化的代码,该代码将指针的目标缓存在寄存器中.为了避免这种情况,您必须使用联合在不同的指针类型之间进行转换.引用自 http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options :

尤其是,假定一种类型的对象永远不会与另一种类型的对象位于相同的地址,除非这些类型几乎相同.例如,一个无符号的int可以为一个int加上别名,但不能为void *或double的别名.字符类型可以是其他任何类型的别名.

请特别注意以下代码:

     union a_union {
        int i;
        double d;
      };

     int f() {
        union a_union t;
        t.d = 3.0;
        return t.i;
      }

从不同于最近写过的工会成员(称为类型伪造")的成员中读取数据的做法很常见.即使使用-fstrict-aliasing,只要通过联合类型访问内存,也可以进行类型修剪.因此,上面的代码将按预期工作.请参见结构联合枚举和位域实现.但是,此代码可能不会:

     int f() {
        union a_union t;
        int* ip;
        t.d = 3.0;
        ip = &t.i;
        return *ip;
      }

类似地,即使强制转换使用联合类型,例如,通过获取地址,转换结果指针并取消引用结果的访问也具有未定义的行为.

     int f() {
        double d = 3.0;
        return ((union a_union *) &d)->i;
      }

在-O2,-O3和-Os级别启用了-fstrict-aliasing选项.

在您的情况下,您可以使用类似的

union {
    void** ret_ptr;
    ptrdiff_t in_ptr;
}

但是ptr_add中的代码看起来太可怕了;-)

或仅通过"fno-strict-aliasing"禁用此特定的优化.更好地修复您的代码;-)

Careless use of templates can cause bloat. One way to avoid that bloat is to have a thin typesafe template that wraps non-typesafe non-template code. To do this, the wrapper needs to provide some way for the non-template code to access things it knows nothing about.

For example, in a data structure, the wrapper defines the node structs. The unsafe code needs to read and write to the nodes, but must do so indirectly, through some kind of interface which is specified by the wrapper.

One way to implement this interface is to fill in a struct (defined by the unsafe code) with details such as function-pointers and constants determined by the wrapper. And one relevant kind of constant is the offset (within some structure) of a particular field. The unsafe code can use that offset (and some pointer arithmetic) to access that field directly.

This is getting problematic, though - as optimisers get more aggressive, this can lead to pointer alias issues. This is particularly the case if nodes can escape the library. For example, nodes may be extracted from a binary tree and relinked to form a linked list. Another example, annoyingly, happens when unit testing.

I currently have a container library written along these lines, and it causes none of these problems at present - but it will shortly. The reason it avoids these problems is because all the unit testing is applied to containers (not the underlying code), and because the nodes never escape the containers. That is, nodes are always accessed the same pointer-arithmetic way, so the pointer alias optimization issue never arises.

Unfortunately, I'm shortly going to need to allow nodes to be extracted from the containers, and I'm probably going to need unit tests on the underlying unsafe code as well.

Rather than deal with this specific library, I have a much simpler extract from an old binary tree library here that suffers the same problems. In VC++9 it just works. Using MinGW GCC 4.4.0, a debug build works, but a release build fails. The problem is a mixture of inlining and failure of the optimizer to spot pointer aliasses.

Just to be clear - I don't want to here "WTF - GOTO!!!" or whatever. The issue is resolving the optimization/pointer problem. Though if you can find a way to write Tree_To_List that is properly structured and doesn't use hidden/disguised gotos to achieve it, I'm interested.

Also, a layer of template-based abstraction is missing (the template c_Bin_Tree_Tool doesn't do the whole job - c_Tool finishes the wrapping, but in a per-use way rather than in re-usable form. That's just a side-effect of extracting the relevant code.

What this code does is create an unbalanced binary tree by inserting nodes one-by-one, then balance that tree. The balancing works by converting the tree to a list (which in a way it already is), then converting the list back to a tree. The tree is dumped to stdio both before and after the balance.

bintree.h...

inline void* Ptr_Add  (void* p1, std::ptrdiff_t p2)  {  return (void*) (((std::ptrdiff_t) p1) + p2);  }

struct c_Bin_Tree_Closure
{
  typedef int (*c_Node_Comp) (void* p_Node1, void* p_Node2);

  c_Node_Comp    m_Node_Comp;
  std::ptrdiff_t m_Left, m_Right;
};

class c_BT_Policy_Closure
{
  private:
    const c_Bin_Tree_Closure* m_Closure;

  protected:
    void** Left_Of  (void* p)  {  return ((void**) Ptr_Add (p, m_Closure->m_Left ));  }
    void** Right_Of (void* p)  {  return ((void**) Ptr_Add (p, m_Closure->m_Right));  }

    int Compare_Node (void* p_Node1, void* p_Node2) const
    {
      return m_Closure->m_Node_Comp (p_Node1, p_Node2);
    }

  public:
    c_BT_Policy_Closure ()
    {
      m_Closure = 0;
    }

    void Set_Closure (const c_Bin_Tree_Closure& p_Closure)
    {
      m_Closure = &p_Closure;
    }
};

template<class tc_Policy>
class c_Bin_Tree_Tool : public tc_Policy
{
  public:
    c_Bin_Tree_Tool ()
    {
    }

    void *List_To_Tree (size_t p_Size, void* &p_List);
    void  Tree_To_List (void* p_Root, void* &p_First, void* &p_Last, size_t &p_Size);
    void  Balance      (void* &p);
    void  Insert       (void* &p_Tree, void* p_Node);
};

template<class tc_Policy>
void* c_Bin_Tree_Tool<tc_Policy>::List_To_Tree (size_t p_Size, void* &p_List)
{
  if (p_Size == 0)  return 0;

  size_t l_Size = p_Size / 2;
  void*  l_Ptr  = List_To_Tree (l_Size, p_List);

  void* l_This = p_List;
  p_List = *tc_Policy::Right_Of (l_This);

  *tc_Policy::Left_Of (l_This) = l_Ptr;

  l_Size = p_Size - (l_Size + 1);
  *tc_Policy::Right_Of (l_This) = List_To_Tree (l_Size, p_List);

  return l_This;
}

template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Tree_To_List (void* p_Root, void* &p_First, void* &p_Last, size_t &p_Size)
{
  //  Use left links as prev links and right links as next links

  void*  l_Start    = 0;         //  first-item-in-list pointer
  void*  l_Prev     = 0;         //  previous node in list
  void** l_Prev_Ptr = &l_Start;  //  points to the next (ie right) pointer for the next node.
  void*  l_Pos      = p_Root;
  void*  l_Next;
  void*  l_Parent   = 0;
  size_t l_Count    = 0;

  p_Last = 0;

  TOP_OF_LOOP:
    l_Next = *tc_Policy::Left_Of (l_Pos);

    if (l_Next != 0)
    {
      *tc_Policy::Left_Of (l_Pos) = l_Parent;  //  So we can find our way back up the tree
      l_Parent = l_Pos;
      l_Pos    = l_Next;
      goto TOP_OF_LOOP;
    }

  AFTER_STEP_PARENT:
    l_Next = *tc_Policy::Right_Of (l_Pos);

    *tc_Policy::Left_Of (l_Pos) = l_Prev;

    p_Last      = l_Pos;
    l_Prev      = l_Pos;
    *l_Prev_Ptr = l_Pos;
    l_Prev_Ptr  = tc_Policy::Right_Of (l_Pos);
    l_Count++;

    if (l_Next != 0)
    {
      l_Pos = l_Next;
      goto TOP_OF_LOOP;
    }

    if (l_Parent != 0)
    {
      l_Pos    = l_Parent;
      l_Parent = *tc_Policy::Left_Of (l_Pos);
      goto AFTER_STEP_PARENT;
    }

  *l_Prev_Ptr = 0;
  p_First     = l_Start;
  p_Size      = l_Count;
}

template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Balance (void* &p)
{
  void   *l_First, *l_Last;
  size_t l_Count;

  Tree_To_List (p, l_First, l_Last, l_Count);
  p = List_To_Tree (l_Count, l_First);
}

template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Insert (void* &p_Tree, void* p_Node)
{
  void** l_Tree = &p_Tree;

  while (*l_Tree != 0)
  {
    int l_Compare = tc_Policy::Compare_Node (*l_Tree, p_Node);
    l_Tree = ((l_Compare < 0) ? tc_Policy::Right_Of (*l_Tree) : tc_Policy::Left_Of (*l_Tree));
  }

  *l_Tree = p_Node;
  *tc_Policy::Right_Of (p_Node) = 0;
  *tc_Policy::Left_Of  (p_Node) = 0;
};

test_bintree.cpp...

#include <iostream>

#include "bintree.h"

struct c_Node
{
  c_Node *m_Left, *m_Right;
  int    m_Data;
};

c_Node g_Node0001 = { 0, 0,  1 };  c_Node g_Node0002 = { 0, 0,  2 };
c_Node g_Node0003 = { 0, 0,  3 };  c_Node g_Node0004 = { 0, 0,  4 };
c_Node g_Node0005 = { 0, 0,  5 };  c_Node g_Node0006 = { 0, 0,  6 };
c_Node g_Node0007 = { 0, 0,  7 };  c_Node g_Node0008 = { 0, 0,  8 };
c_Node g_Node0009 = { 0, 0,  9 };  c_Node g_Node0010 = { 0, 0, 10 };

int Node_Compare (void* p1, void* p2)
{
  return (((c_Node*) p1)->m_Data - ((c_Node*) p2)->m_Data);
}

c_Bin_Tree_Closure  g_Closure =
{
  (c_Bin_Tree_Closure::c_Node_Comp) Node_Compare,
  offsetof (c_Node, m_Left ),  offsetof (c_Node, m_Right)
};

class c_Tool : public c_Bin_Tree_Tool<c_BT_Policy_Closure>
{
  protected:
    typedef c_Bin_Tree_Tool<c_BT_Policy_Closure> c_Base;
  public:
    c_Tool ()  {  Set_Closure (g_Closure);  }
    void Insert  (c_Node* &p_Tree, c_Node* p_Node)  {  c_Base::Insert ((void*&) p_Tree, p_Node);  }
    void Balance (c_Node* &p)  {  c_Base::Balance ((void*&) p);  }
};

void BT_Dump (size_t p_Depth, c_Node* p_Node)
{
  if (p_Node != 0)
  {
    BT_Dump (p_Depth + 1, p_Node->m_Left);
    for (size_t i = 0; i < p_Depth; i++)  std::cout << " .";
    std::cout << " " << p_Node->m_Data << std::endl;
    BT_Dump (p_Depth + 1, p_Node->m_Right);
  }
}

int main (int argc, char* argv[])
{
  c_Tool  l_Tool;
  c_Node *l_Root = 0;

  l_Tool.Insert (l_Root, &g_Node0001);
  l_Tool.Insert (l_Root, &g_Node0002);
  l_Tool.Insert (l_Root, &g_Node0003);
  l_Tool.Insert (l_Root, &g_Node0004);
  l_Tool.Insert (l_Root, &g_Node0005);
  l_Tool.Insert (l_Root, &g_Node0006);
  l_Tool.Insert (l_Root, &g_Node0007);
  l_Tool.Insert (l_Root, &g_Node0008);
  l_Tool.Insert (l_Root, &g_Node0009);
  l_Tool.Insert (l_Root, &g_Node0010);

  BT_Dump (0, l_Root);  std::cout << "----------" << std::endl;

  l_Tool.Balance (l_Root);
  BT_Dump (0, l_Root);

  return 0;
}

The expected results are...

 1
 . 2
 . . 3
 . . . 4
 . . . . 5
 . . . . . 6
 . . . . . . 7
 . . . . . . . 8
 . . . . . . . . 9
 . . . . . . . . . 10
----------
 . . . 1
 . . 2
 . 3
 . . . 4
 . . 5
 6
 . . . 7
 . . 8
 . 9
 . . 10

What actually happens (MinGW GCC 4.4.0, optimized release build)...

 1
 . 2
 . . 3
 . . . 4
 . . . . 5
 . . . . . 6
 . . . . . . 7
 . . . . . . . 8
 . . . . . . . . 9
 . . . . . . . . . 10
----------
 1

As far as I can tell, the Balance operation runs correctly, but the BT_Dump function can't see all the changes to the m_Left and m_Right fields.

EDIT That's wrong - otherwise why am I seeing node 1 as the new root. I guess that's what happens when you rely too much on memory of an investigation that was done months ago.

EDIT Actually, node 1 as root is an issue, but since it was the old root - well, best to just ignore this what-the-problem-is stuff and work out your own theories ;-)

There are a number of issues, in terms of being standards-undefined, in the code. I think the biggest problem is that the links in the node struct are c_Node*, but since the unsafe code knows nothing about c_Node it accesses them (via pointer arithmetic) as void*.

One fix would be for the unsafe code to do all reads and writes through getter and setter function pointers, avoiding all the pointer arithmetic and ensuring that all access to c_Node instances is done through c_Node* pointers. Even better - the interface becomes a class with getter/setter methods etc. In the complete binary tree library, I have alternate policy classes that do this, though to be honest my real fix when the problem arose was to throw all the code in a "junk" folder on the basis that I rarely use it, and should probably be using boost intrusive lists anyway.

However, this still leaves the other much more complex and heavily used container library, which is not going away. I think I'm just going to have to do the very painful refactoring to get rid of the offsetof and pointer arithmetic stuff, but...

What exactly are the C++ rules - when precisely is the compiler allowed to fail to spot a possible pointer alias? And could the binary tree code above be rewritten so that it still uses pointer arithmetic, still allows the nodes to be accessed/modified both inside and outside the library, and yet is safe from this optimization issue?

解决方案

Did you switch off warnings? You should have got some "dereferencing type punned pointers violates strict aliasing", because thats exactly what you do at (void**) Ptr_Add(...

The compiler is free to assume that pointers to different types do not alias (with a few execpitions), and will produce optimized code which caches the targets of pointers in registers. To avoid that, you have to use unions to convert between different pointers types. Quoting from http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options:

In particular, an object of one type is assumed never to reside at the same address as an object of a different type, unless the types are almost the same. For example, an unsigned int can alias an int, but not a void* or a double. A character type may alias any other type.

Pay special attention to code like this:

     union a_union {
        int i;
        double d;
      };

     int f() {
        union a_union t;
        t.d = 3.0;
        return t.i;
      }

The practice of reading from a different union member than the one most recently written to (called "type-punning") is common. Even with -fstrict-aliasing, type-punning is allowed, provided the memory is accessed through the union type. So, the code above will work as expected. See Structures unions enumerations and bit-fields implementation. However, this code might not:

     int f() {
        union a_union t;
        int* ip;
        t.d = 3.0;
        ip = &t.i;
        return *ip;
      }

Similarly, access by taking the address, casting the resulting pointer and dereferencing the result has undefined behavior, even if the cast uses a union type, e.g.:

     int f() {
        double d = 3.0;
        return ((union a_union *) &d)->i;
      }

The -fstrict-aliasing option is enabled at levels -O2, -O3, -Os.

In your case you could use something like

union {
    void** ret_ptr;
    ptrdiff_t in_ptr;
}

but the code in ptr_add just looks horrible ;-)

Or just disable this specific optimization with "fno-strict-aliasing". Better fix your code though ;-)

这篇关于如何解决指针别名问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆