c++ - 在C++/CLI代码中从std::stringstream读取std::string时出现问题

标签 c++ c++-cli stringstream

我有一段代码可以像这样读取构建日期和月份。此处__DATE__Predefined Macros中所定义

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);


正常工作时,缓冲区通常是


  已读年= [2013]月= [3月]日期= [2013年3月9日]


但是在某些情况下


  读取年份= [0]月= [M]日期= [2013年3月9日]


要么


  阅读年份= [2013]月= [三月]日期= [2013年3月9日]


基本上,年份要么为0,要么月份有多余的空间。

该项目是使用Windows 7上的Microsoft Visual Studio 2010 SP1的x64 / CLR构建。

我为为什么偶尔发生这种情况而感到困惑。我不正确地使用stringstream吗?

最佳答案

最初,我很想删除问题,但我想我会分享我的发现,以防其他可怜的人遇到相同的问题。这个问题非常神秘,从未在应用程序的多次运行中发生,仅在测试期间发生,而在调试测试时从未发生。

这种无辜的功能

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中实现的。在深入探讨细节之前,让我解释一下字符串流在这里如何月份和年份读取。要弄清组成月份变量ss >> month的字符数,需要按空格分隔ss字符串缓冲区。通过使用当前的locale尤其是它的一个名为ctype的方面来完成此任务。 ctype构面具有名为ctype::is的功能,该功能可以判断字符是否为空格。在性能良好的C ++应用程序中,所有内容都可以按照标准运行。现在让我们假设由于某种原因ctype构面已损坏。
Viola,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中,您始终会看到myx
as 42. myglobal的生存期通常称为每个进程,因为它在问题的生存期内有效。 Foo ::〜Foo析构函数将仅在main返回之后运行。

输入C ++ / CLI和AppDomain

根据msdn的AppDomain为您提供执行应用程序的隔离环境。对于C ++ / CLI,它引入了我称为appdomain storage duration的对象的概念

 __declspec(appdomain)   Foo myglobal;


因此,如果您像上面那样更改myglobal的定义,则可能会使myglobal.x在不同的appdomains中成为不同的值,例如线程本地存储。因此,在程序的初始化/退出过程中会初始化/清除静态持续时间的常规C ++对象。我在这里非常松散地使用init / exit / cleaned,但是您明白了。在AppDomain的加载/卸载过程中,将初始化/清除appdomain存储的对象。

典型的托管程序仅使用默认的AppDomain,因此每个进程/每个应用程序域的存储几乎相同。

在C ++中,static initialization order fiasco是一个非常常见的错误,其中静态存储持续时间的对象在初始化期间引用了其他可能尚未初始化的静态存储持续时间的对象。

现在考虑当每个进程变量引用每个域变量时会发生什么。基本上,卸载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的调用者不负责返回的构面生命管理,因此如何清理这些构面。秘诀是代码的最后部分,其中注释排队等待销毁。 _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,表示所有创建的构面都在每个应用程序域级别上销毁。

另一方面,locale::facet *_Psave被声明为__PURE_APPDOMAIN_GLOBAL,这似乎最终会扩展为每个进程的含义。因此,清理完appdomain后,per-process _Psave可能指向删除的多面内存。这正是我的问题。 VS2010单元测试的发生方式是一个称为QTAgent的过程可以运行所有测试。这些测试似乎是通过相同的QTAgent进程在不同的应用程序域中以不同的运行方式进行的。最有可能隔离先前测试运行的副作用以影响后续测试。这对于完全托管的代码来说是一件好事,其中几乎所有静态存储都是线程/应用程序域级别的,但是对于C ++ / CLI,它们不正确地使用每个进程/每个应用程序域可能是个问题。我永远无法调试测试并找到问题的原因是因为UT基础结构似乎总是产生一个新的QTAgent调试过程,这意味着一个新的appdomain和一个没有这些问题的新过程。

关于c++ - 在C++/CLI代码中从std::stringstream读取std::string时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15314251/

相关文章:

c++ - 使用C++模板编程提取任意结构的字段类型

c++ - 如何获取异步读取时传输的字节数boost asio c++

.net - 如何为 C++/cli 项目指定默认命名空间?

c++ - 如何测试stringstream operator>>是否解析了错误的类型并跳过它

c++ - 通过函数传递字符串流值,C++

c++ - 如何保持服务器运行(cpprestsdk - 卡萨布兰卡)

c++ - 指向任意函数的 "general function signature"指针

.net - 在 C++/CLI 中实现 IEnumerable<T>

c# - C++ 到 C# 事件处理

c++ - 如何测试 stringstream operator>> 是否解析了错误的类型并跳过它