0

我正面临着一件非常了不起的事情。(场景: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 中存在一些问题,在某些特定条件下会跳到舞台上……问题是“导致这种情况的条件是什么?”。

等待一个想法...

4

3 回答 3

0

编译器选项是/Zp4。您确定在所有情况下都使用了正确的选项吗?

于 2011-02-15T17:13:59.880 回答
0

我只是猜测,但我的直接猜测是您的问题不是源于对齐问题。

使用 VC++,如果你把这样的东西放在 DLL 中,你必须编译代码(主程序和 DLL)才能在 DLL 中使用标准库。当你这样做时,主程序和 DLL 共享一个公共堆,因此它们可以分配/释放内存并且事情保持同步。

如果静态链接到标准库,则主程序和 DLL 将具有单独的堆。当/如果其中的代码尝试删除在另一个中分配的项目(例如),您将遇到重大问题,因为它会尝试将其返回到它不是来自的堆第一名。

于 2011-02-15T17:04:54.947 回答
0

您的描述肯定指向对齐/包装问题的方向。

也许您可以在sizeof(Props) == 8. Boost 有一个编译时检查模板,但在互联网上找到一个或自己动手并不难。

如果 sizeof 总是相同,让我指出检查的迭代器以检查替代的东西。

在那之后,我的想法已经不多了。

于 2011-02-15T19:07:50.803 回答