我正面临着一件非常了不起的事情。(场景:Win7 pro 64位,VC2008编译32位代码)
假设一个主程序实例化一个使用 std::map 的类。
这是一个 std::map<std::string,Props> 类 Props 只有两个成员:
class Props {
public:
/**
* value of property
* @var boost::any
*/
boost::any _a ;
/**
* expire time
* @var time_t
*/
time_t _tExpiry ;
/* Somethign more I'm not writing down here... */
}
现在...我构建了一个 DLL,为它自己的业务使用相同的类。
DLL 实例化该类并提供 std::map。
嗯...当主程序提供地图时一切正常,而 DLL 在第一个项目插入后崩溃。
更多(非常有趣)的东西。
如果我深入到由主程序完成的插入,我将单个 _Node 的构造函数放入 std::map
_Node 显示如下(c:\Programmi\Microsoft Visual Studio 9.0\VC\include\xtree)
struct _Node
{ // tree node
_Node(_Nodeptr _Larg, _Nodeptr _Parg, _Nodeptr _Rarg,
const value_type& _Val, char _Carg)
: _Left(_Larg), _Parent(_Parg), _Right(_Rarg),
_Myval(_Val), _Color(_Carg), _Isnil(false)
{ // construct a node with value
}
_Nodeptr _Left; // left subtree, or smallest element if head
_Nodeptr _Parent; // parent, or root of tree if head
_Nodeptr _Right; // right subtree, or largest element if head
value_type _Myval; // the stored value, unused if head
char _Color; // _Red or _Black, _Black if head
char _Isnil; // true only if head (also nil) node
};
很好……所以我们的 _Node 结构有 _Left(4 字节)、_Parent(4 字节)、_Right(4 字节)、_Myval(地图的键和值的大小)、_Color(1 字节)和 _Isnil(1 字节)。
现在...我要为地图提供的元素是一对 <std::string, Props>。
根据我的调试器,std::string 需要 0x20 个字节,而 Props 只需要 8 个。
现在,当我询问调试器单个节点的大小时,我可以看到它是 0x38。所以...0x4 + 0x4 + 0x4 + 0x20 + 0x8 + 0x1 + 0x1 = 0x36 + 填充 = 0x38。
这意味着 _Color 成员在 _Node 开始后的 0x34 字节处开始。
(好吧...我宁愿指定我所有的项目都使用 /Zp4 所以所有结构都是 4 字节打包)。
让我们继续...当我遵循 DLL 行为时,我可以看到一些非常惊人的东西。
std::map 的 _Buynode() 方法调用分配器为我要插入的新元素设置新的大小。调用分配器而不是调用 _Node() 就地构造函数....并且它以另一种方式起作用!!
在这种情况下,构造函数表现为 _Color 成员在 _Node 开头的 0x38 字节之后开始......好像有不同的填充一样。
在此之后,在以下尝试插入新值时,该过程失败,因为 _Color 和 _Isnil 的值......错误(0xcc,因为该部分内存未初始化)。
我确定我在所有项目中都将 /Zp4 设置为解决方案,所以......
怎么了?
我“感觉”对齐有问题,但我不能说什么......
提前致谢!
好的...我要添加更多内容。
这是来自 c:\Programmi\Microsoft Visual Studio 9.0\VC\include\xtree 的 _Node 结构
struct _Node
{ // tree node
_Node(_Nodeptr _Larg, _Nodeptr _Parg, _Nodeptr _Rarg,
const value_type& _Val, char _Carg)
: _Left(_Larg), _Parent(_Parg), _Right(_Rarg),
_Myval(_Val), _Color(_Carg), _Isnil(false)
{ // construct a node with value
}
_Nodeptr _Left; // left subtree, or smallest element if head
_Nodeptr _Parent; // parent, or root of tree if head
_Nodeptr _Right; // right subtree, or largest element if head
value_type _Myval; // the stored value, unused if head
char _Color; // _Red or _Black, _Black if head
char _Isnil; // true only if head (also nil) node
};
_Tree_nod(const key_compare& _Parg,
allocator_type _Al)
: _Traits(_Parg, _Al), _Alnod(_Al)
{ // construct traits from _Parg and allocator from _Al
}
typename allocator_type::template rebind<_Node>::other
_Alnod; // allocator object for nodes
};
如您所见,它的构造函数非常简单。
当我按照我之前所说的那样查找 std::map 时,查看从这个结构中使用的内存。
- _Left
4 个字节 - _Parent 4 个字节 - _Right
4 个字节
- _Myval 0x28 个字节(std::string 为 0x20,我自己的类为 8 个。我检查过,它总是 8 个字节)
- _Color 1 个字节
- 1 _Isnil 的字节
当一个新元素放置在树的顶部(我的意思是最左边的)被创建时,这个 costructor 填充 _Left、_Parent、_Right、_Myval,而不是 lat 4 个未初始化的字节(比如填充 0xcc)并填充它认为应该是 _Color和 _Isnil 提前 4 个字节。
最荒谬的是 std::map 的其他方法不会“感觉”这 4 个字节。
这些是导致 de 断言的行。
if (_Isnil(_Ptr))
{
_Ptr = _Right(_Ptr); // end() ==> rightmost
if (_Isnil(_Ptr))
#if _HAS_ITERATOR_DEBUGGING
{
_DEBUG_ERROR("map/set iterator not decrementable");
_SCL_SECURE_OUT_OF_RANGE;
}
#elif _SECURE_SCL
{
_SCL_SECURE_OUT_OF_RANGE;
}
#else
return; // begin() shouldn't be incremented, don't move
#endif
}
发生这种情况是因为对 _IsNil(_Ptr) 的测试发现 _Isnil 为 0xcc,并给出了错误。发布版本可能不疯,这不是一件快乐的事。
任何想法?
又一步!
我把整个解决方案(2个大项目,18个小项目,大约250000个C/C++代码行),在Linux下编译(gcc4.1.2)。
一切都很好。完全没有问题,std::map 可以正常工作。
我不得不说 Linux Makefile 同时让一切变得更加简单和复杂。复杂是因为你必须自己做所有事情,简单是因为如果你不想做就什么都不会发生。
这告诉我一件事:Visual Studio 2008 中存在一些问题,在某些特定条件下会跳到舞台上……问题是“导致这种情况的条件是什么?”。
等待一个想法...