0

ビルドの日付と月をそのように読み取るためのコードがあります。これは、定義済みマクロ__DATE__で定義されているとおりです

const char* BUILD_DATE = __DATE__;
std::stringstream ss(BUILD_DATE);
std::string month;
size_t year;
ss >> month;
ss >> year;
ss >> year;

char buffer[1024];
sprintf(buffer, "Read year=[%d] month=[%s] date=[%s]", year,month.c_str(),BUILD_DATE);

正常に動作する場合、バッファは通常

読み取り year=[2013] month=[Mar] date=[Mar 9 2013]

しかし、いくつかの実行では

読み取り年=[0] 月=[M] 日=[2013 年 3 月 9 日]

また

読み取り year=[2013] month=[Mar ] date=[Mar 9 2013]

基本的に、年は0であるか、月に余分なスペースがあります。

このプロジェクトは、Windows 7 ボックスで Microsoft Visual Studio 2010 SP1 を使用した x64/CLR ビルドです。

なぜこれが時々起こるのか、私は困惑しています。stringstream を間違って使用していますか?

4

2 に答える 2

5

最初は質問を削除したくなりましたが、他の貧しい魂が同じ問題に遭遇した場合に備えて、調査結果を共有すると思いました. この問題は非常に不思議で、アプリケーションの複数回の実行では発生せず、テスト中にのみ発生し、テストのデバッグ中には発生しませんでした。

無邪気なこの機能

const char* BUILD_DATE = __DATE__;
std::stringstream ss(BUILD_DATE);
std::string month;
size_t year;
ss >> month;
ss >> year;
ss >> year;

C++/CLI dll で実装されました。詳細に入る前に、stringstream がをどのように読み取るかを説明します。month変数 を構成する文字数を把握するss >> monthには、ss 文字列バッファーをスペースで区切る必要があります。現在のロケール、特にctypeと呼ばれるそのファセットを使用してそれを行う方法。ctype ファセットにはctype::isという関数があり、文字がスペースかどうかを判断できます。適切に動作する C++ アプリケーションでは、すべてが標準に従って機能します。ここで、何らかの理由でctype ファセットが破損したと仮定します。ビオラ、operator >>何がスペースで何がそうでないかを判断できず、正しく解析できません。これはまさに私の場合に起こっていたことであり、以下に詳細を示します。

残りの回答は、Visual Studio 2010 によって提供される std c++ ライブラリと、C++/CLI での動作方法にのみ適用されます。

そのようないくつかのコードを検討してください

struct Foo
{
    Foo()
    {
        x = 42;
    }

    ~Foo()
    {
        x = 45;
    }
    int x;
};

Foo myglobal;

void SimpleFunction()
{
    int myx = myglobal.x;
}

int main()
{
    SimpleFunction();

    return 0;
}

ここでmyglobalは、 main に入る前に初期化されることが保証された静的ストレージ期間を持つオブジェクトと呼ばれるもので、 SimpleFunction では常にmyx42として表示されます。myglobalの有効期間は、問題の有効期間中有効であるため、通常はプロセスごとと呼ばれるものです。Foo::~Fooデストラクタは、メインが戻った後にのみ実行されます。

C++/CLI と AppDomain に入る

msdn によるAppDomainは、アプリケーションが実行される分離された環境を提供します。C++/CLI の場合、 appdomain ストレージ期間と呼ぶオブジェクトの概念が導入されます。

 __declspec(appdomain)   Foo myglobal;

したがって、上記のように myglobal の定義を変更すると、myglobal.x が、スレッド ローカル ストレージのような別のアプリケーション ドメインで別の値になる可能性があります。したがって、静的期間の通常の C++ オブジェクトは、プログラムの初期化/終了時に初期化/消去されます。ここでは init/exit/cleaned を大まかに使用していますが、おわかりいただけたでしょうか。AppDomain ストレージのオブジェクトは、AppDomain のロード/アンロード中に初期化/消去されます。

典型的なマネージド プログラムはデフォルトの AppDomain のみを使用するため、プロセスごと/アプリドメインごとのストレージはほとんど同じです。

C++ では、初期化中に静的ストレージ期間のオブジェクトが、初期化されていない可能性がある静的ストレージ期間の他のオブジェクトを参照するという、静的初期化順序の大失敗は非常によくある間違いです。

ここで、プロセスごとの変数がドメインごとの変数を参照するとどうなるかを考えてみましょう。基本的に、AppDomain がアンロードされた後、プロセスごとの変数はジャンク メモリを参照します。元の問題とどう関係があるのか​​ 疑問に思っている人は、もう少し我慢してください.

Visual Studio の use_facet の実装

std::use_facetは、ロケールから対象のファセットを取得するために使用されます。ファセットoperator <<を取得するために使用さctype次のように定義されます。

template <class Facet> const Facet& use_facet ( const locale& loc );

Facetへの参照を返すことに注意してください。VCによる実装方法は

    const _Facet& __CRTDECL use_facet(const locale& _Loc)

    {   // get facet reference from locale
    _BEGIN_LOCK(_LOCK_LOCALE)   // the thread lock, make get atomic
        const locale::facet *_Psave =
            _Facetptr<_Facet>::_Psave;  // static pointer to lazy facet

        size_t _Id = _Facet::id;
        const locale::facet *_Pf = _Loc._Getfacet(_Id);

        if (_Pf != 0)
            ;   // got facet from locale
        else if (_Psave != 0)
            _Pf = _Psave;   // lazy facet already allocated
        else if (_Facet::_Getcat(&_Psave, &_Loc) == (size_t)(-1))

 #if _HAS_EXCEPTIONS

            _THROW_NCEE(bad_cast, _EMPTY_ARGUMENT); // lazy disallowed

 #else /* _HAS_EXCEPTIONS */
            abort();    // lazy disallowed
 #endif /* _HAS_EXCEPTIONS */

        else
            {   // queue up lazy facet for destruction
            _Pf = _Psave;
            _Facetptr<_Facet>::_Psave = _Psave;

            locale::facet *_Pfmod = (_Facet *)_Psave;
            _Pfmod->_Incref();
            _Pfmod->_Register();
            }

        return ((const _Facet&)(*_Pf)); // should be dynamic_cast
    _END_LOCK()
    }

ここで何が起こっているかというと、ロケールに関心のあるファセットを尋ねて、それを格納します。

   template<class _Facet>
    struct _Facetptr
    {   // store pointer to lazy facet for use_facet
    __PURE_APPDOMAIN_GLOBAL static const locale::facet *_Psave;
    };

同じファセットを取得するための後続の呼び出しが高速になるように、ローカル キャッシュ_Psaveを使用します。use_facet の呼び出し元は、返されたファセットの寿命管理に責任を負わないため、これらのファセットはどのようにクリーンアップされますか。シークレットは、コメントqueue up lazy facet for destroyを含むコードの最後の部分です。は_Pfmod->_Register()最終的にこれを呼び出します

__PURE_APPDOMAIN_GLOBAL static _Fac_node *_Fac_head = 0;

static void __CLRCALL_OR_CDECL _Fac_tidy()
{   // destroy lazy facets
    _BEGIN_LOCK(_LOCK_LOCALE)   // prevent double delete
        for (; std::_Fac_head != 0; )
        {   // destroy a lazy facet node
            std::_Fac_node *nodeptr = std::_Fac_head;
            std::_Fac_head = nodeptr->_Next;
            _DELETE_CRT(nodeptr);
        }
        _END_LOCK()
}



struct _Fac_tidy_reg_t { ~_Fac_tidy_reg_t() { ::_Fac_tidy(); } };
_AGLOBAL const _Fac_tidy_reg_t _Fac_tidy_reg;


void __CLRCALL_OR_CDECL locale::facet::_Facet_Register(locale::facet *_This)
{   // queue up lazy facet for destruction
    _Fac_head = _NEW_CRT _Fac_node(_Fac_head, _This);
}

かなり賢いですね。すべての新しいファセットをリンク リストに追加し、静的オブジェクト デストラクタを使用してそれらをすべて消去します。わずかな問題があることを除いて。_Fac_tidy_reg_AGLOBAL としてマークされ、作成されたすべてのファセットがアプリドメインごとのレベルで破棄されることを意味します。

一方、最終的にper-processを意味するように拡張されるようにlocale::facet *_Psave宣言されています。そのため、appdomain がクリーンアップされた後、削除されたファセット メモリを指す可能性があります。これはまさに私の問題でした。VS2010 単体テストが行​​われる方法は、 QTAgentと呼ばれるプロセスがすべてのテストを実行することです。これらのテストは、同じQTAgentによる異なる実行で異なる appdomain で行われているようです__PURE_APPDOMAIN_GLOBALper-process _Psave処理する。以前のテスト実行の副作用を分離して、後続のテストに影響を与える可能性が最も高くなります。これは、ほぼすべての静的ストレージがスレッド/アプリドメイン レベルのいずれかである完全に管理されたコードでは問題ありませんが、プロセスごと/アプリドメインごとを誤って使用する C++/CLI では問題になる可能性があります。テストをデバッグできず、問題を見つけることができなかった理由は、UT インフラストラクチャがデバッグ用の新しいQTAgentプロセスを常に生成しているように見えるためです。これは、これらの問題のない新しい appdomain と新しいプロセスを意味します。

于 2013-03-11T00:40:07.010 に答える
0

実際の日付文字列を確認するには、これを試すことをお勧めします。

cout << "Raw date: " << ss.str() << "\n";

または、デバッガーでステップ実行し、ss作成後に変数を確認します。

于 2013-03-09T18:46:43.510 に答える