0

私は非常に驚くべきことに直面しています。(シナリオ: Win7 pro 64 ビット、VC2008 コンパイル 32 ビット コード)

メイン プログラムが std::map を使用するクラスをインスタンス化するとします。
これは std::map<std::string,Props> であり、クラス Props には 2 つのメンバーしかありません。

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() インプレースコンストラクターが呼び出されるよりもアロケーターが呼び出されます....そして、別の方法で動作します!!

この場合、コンストラクターは、_Node の先頭から 0x38 バイトの後に _Color メンバーが開始するように動作します... あたかも別のパディングがあるかのように。
この後、新しい値を挿入しようとすると、_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 を asf するときに、この構造体から使用されるメモリを調べます。
- _Left の場合は
4バイト - _Parent の場合は 4 バイト - _Right
の場合は 4 バイト
- _Myval の場合は 0x28 バイト (std::string の場合は 0x20、自分のクラスの場合は 8 です。確認したところ、alqays は 8 バイト
です) - _Color の場合は 1 バイト
- 1 _Isnil のバイト

ツリーの一番上 (つまり、一番左) に配置された新しい要素が作成されると、このコストラクタは、_Left、_Parent、_Right、_Myval を、初期化されていない 4 バイト分 (たとえば 0xcc で埋められている) よりも埋め、_Color と見なされるものを埋めます。および _Isnil 4 バイト先。
最もばかげたことは、std::map の他のメソッドがこれらの 4 バイトを「感じない」ことです。
これらは、デアサーションを引き起こす行です。

        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 は、すべてをより簡単にすると同時に複雑にすると言わざるを得ません。自分ですべてを行わなければならないので複雑ですが、したくない場合は何も起こらないので簡単です。

これは私に 1 つのことを教えてくれます: Visual Studio 2008 には何か問題があり、特定の条件でステージ上でジャンプする何かがあります...問題は、「これを引き起こす条件は何ですか?」です。

アイデアを待っています...

4

3 に答える 3

0

あなたの説明は確かに整列/パッキング問題の方向を示しています。

たぶん、にコンパイル時チェックを追加することができますsizeof(Props) == 8。Boostにはコンパイル時のチェックテンプレートがありますが、インターネットで見つけたり、自分で作成したりするのはそれほど難しくありません。

sizeofが常に同じであることが判明した場合は、別のチェック方法として、チェックされたイテレータを指定します。

その後、アイデアが足りなくなりました。

于 2011-02-15T19:07:50.803 に答える
0

コンパイラ オプションは/Zp4です。すべてのケースで正しいオプションを使用したと確信していますか?

于 2011-02-15T17:13:59.880 に答える
0

私は推測しているだけですが、あなたの問題は配置の問題に起因するものではないとすぐに推測できます。

VC++ では、このようなものを DLL に入れる場合、DLL で標準ライブラリを使用するには、コード (メイン プログラムと DLL の両方) をコンパイルする必要があります。これを行うと、メイン プログラムと DLL が共通のヒープを共有するため、メモリの割り当て/解放が可能になり、同期が維持されます。

標準ライブラリに静的にリンクすると、メイン プログラムと DLL は別々のヒープを持ちます。一方のコードが他方に割り当てられたアイテムを削除しようとすると (一例として)、主要な問題に遭遇します。最初の場所。

于 2011-02-15T17:04:54.947 に答える