C++ 如何解决指针别名问题?
不小心使用模板会导致膨胀。避免这种膨胀的一种方法是使用一个精简的类型安全模板来包装非类型安全的非模板代码。要做到这一点,包装器需要为非模板代码提供某种方式来访问它一无所知的内容 例如,在数据结构中,包装器定义节点结构。不安全的代码需要读写节点,但必须通过包装器指定的某种接口间接执行 实现此接口的一种方法是用包装器确定的函数指针和常量等细节填充结构(由不安全代码定义)。一个相关的常数是特定场的偏移量(在某些结构中)。不安全代码可以使用该偏移量(和一些指针算法)直接访问该字段 不过,这会带来问题——随着乐观主义者变得更加激进,这可能会导致指针别名问题。如果节点可以逃逸库,则情况尤其如此。例如,可以从二叉树中提取节点并重新链接以形成链接列表。令人烦恼的是,另一个例子发生在单元测试时 我目前有一个容器库,它是按照这些思路编写的,目前不会导致这些问题,但很快就会出现。它避免这些问题的原因是,所有的单元测试都应用于容器(而不是底层代码),并且节点永远不会脱离容器。也就是说,节点总是以相同的指针算术方式访问,因此指针别名优化问题永远不会出现 不幸的是,我很快就需要允许从容器中提取节点,并且可能还需要对底层不安全代码进行单元测试 我没有处理这个特定的库,而是从一个遭受相同问题的旧二叉树库中提取了一个更简单的内容。在VC++9中,它只是工作。使用MingWGCC4.4.0,调试构建可以工作,但发布构建失败。问题是内联和优化器无法发现指针别名 我只是想说清楚,我不想在这里说“WTF-GOTO!!!”之类的话。问题在于解决优化/指针问题。虽然如果你能找到一种写C++ 如何解决指针别名问题?,c++,pointers,pointer-arithmetic,C++,Pointers,Pointer Arithmetic,不小心使用模板会导致膨胀。避免这种膨胀的一种方法是使用一个精简的类型安全模板来包装非类型安全的非模板代码。要做到这一点,包装器需要为非模板代码提供某种方式来访问它一无所知的内容 例如,在数据结构中,包装器定义节点结构。不安全的代码需要读写节点,但必须通过包装器指定的某种接口间接执行 实现此接口的一种方法是用包装器确定的函数指针和常量等细节填充结构(由不安全代码定义)。一个相关的常数是特定场的偏移量(在某些结构中)。不安全代码可以使用该偏移量(和一些指针算法)直接访问该字段 不过,这会带来问题——
Tree\u to\u List
的方法,这种方法结构合理,不使用隐藏/伪装的goto来实现它,我很感兴趣
此外,还缺少一层基于模板的抽象(模板c_Bin_Tree_工具并不完成全部工作-c_工具完成包装,而是以每次使用的方式完成,而不是以可重用的形式完成。这只是提取相关代码的副作用
这段代码所做的是通过逐个插入节点来创建一个不平衡的二叉树,然后平衡该树。平衡的工作方式是将树转换为一个列表(已经是这样),然后将列表转换回一个树。在平衡之前和之后,该树都会转储到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;
};
#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
实际发生了什么(MingWGCC4.4.0,优化版本构建)
据我所知,平衡操作运行正常,但BT_Dump函数无法看到m_Left
和m_Right
字段的所有更改
编辑这是错误的-否则,为什么我会将节点1视为新的根。我想,如果您过于依赖几个月前完成的调查的内存,就会发生这种情况
编辑事实上,节点1作为根是一个问题,但因为它是旧根-好吧,最好忽略这个问题所在,然后制定自己的理论;-)
准则中存在许多未定义标准的问题。我认为最大的问题是node结构中的链接是c_node*,但由于不安全代码对c_node一无所知,所以它(通过指针算法)以void*的形式访问它们
一种修复方法是让不安全代码通过getter和setter函数指针执行所有读写操作,避免所有指针算法,并确保所有对c_节点实例的访问都是通过c_节点*指针完成的。更好的是,接口变成了一个包含getter/setter方法等的类。在完整的二叉树库中,我有其他策略类可以做到这一点,但老实说,当问题出现时,我真正的解决办法是将所有代码都扔到一个“垃圾”文件夹中,因为我很少使用它,而且无论如何都应该使用boost入侵列表
然而,这仍然留下了另一个更复杂、使用更频繁的容器库,它不会消失。我想我将不得不进行非常痛苦的重构,以摆脱偏移量和指针算术的东西,但是
<什么是C++规则——当编译器精确地不可能发现一个可能的指针别名时?上面的二叉树代码是否可以重写,以便它仍然使用指针算法,仍然允许在库内外访问/修改节点,并且不会出现这种优化问题?不小心使用模板会导致膨胀。但你完全没有抓住重点
- 模板使用时会导致膨胀 不小心,不小心李>
- 运行时错误的数量 模板所避免的是大量的
- 模板化代码的速度非常快 大于非模板代码
- 除非在嵌入式系统上运行,否则可执行文件的大小绝对微不足道
- STL提供了一个地图容器(它是一个二进制搜索树)供您使用
还值得注意的是,代码在Visual Studio 2010上的工作方式与预期一致。是否关闭了警告?你应该这么做
1
. 2
. . 3
. . . 4
. . . . 5
. . . . . 6
. . . . . . 7
. . . . . . . 8
. . . . . . . . 9
. . . . . . . . . 10
----------
1
union a_union {
int i;
double d;
};
int f() {
union a_union t;
t.d = 3.0;
return t.i;
}
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;
}
union {
void** ret_ptr;
ptrdiff_t in_ptr;
}