【发布时间】:2026-02-08 05:50:01
【问题描述】:
我正面临着一件非常了不起的事情。 (场景:Win7 pro 64位,VC2008编译32位代码)
假设一个主程序实例化一个使用 std::map 的类。
这是一个 std::map<:string> 类 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 字节) .
现在...我要为地图提供的元素是一对 <: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
当一个新元素放置在树的顶部(我的意思是最左边)被创建时,这个构造函数填充 _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 中存在一些问题,在某些特定条件下会跳到舞台上……问题是“导致这种情况的条件是什么?”。
等待一个想法...
【问题讨论】:
-
我们使用的许多结构,其中数据包是相关的,被序列化到磁盘并从中读取。还有另一个原因……过去的兼容性和平台可移植性。即使在 Linux、SunOs、MacOS、Aix 上也可以编译相同的代码,并且可以使用相同的文件。现在...我不得不说我还没有尝试过 Linux...也许这是我要做的下一步。
-
MSVC 是否没有与 gcc 的
__attribute__((packed))等效的东西(可能是#pragma pack?),这样您就不必依赖编译器标志了吗? [也:s/memory-alignment/memory-layout/] -
@user611775 MSVC 使用
#pragma pack(push, 4)....#pragma pack(pop)。 -
@Sjored。查看 MSVC 包含文件,我看到了 #pragma pack(push,...) 和 #pragma pack(pop) 的用法。这让我认为 std 库中发生的一切都应该以相同的方式表现,尤其是在对齐方面......但最荒谬的是 _Node cotuctor 使用对齐,而 std::map 的其他方法使用另一个!跨度>
标签: c++ stdmap memory-alignment