【问题标题】:Boost Spirit Qi crashes for memory violationBoost Spirit Qi 因内存违规而崩溃
【发布时间】:2018-10-28 16:10:26
【问题描述】:

但我不知道为什么...?

http://coliru.stacked-crooked.com/a/2912593bb421a35e

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace bsq = boost::spirit::qi;

int main()
{        
    std::uint16_t major, minor, build, revision;

    auto versionParser =
        bsq::uint_
        >> -('.' >> bsq::uint_)
        >> -('.' >> bsq::uint_)
        >> -('.' >> bsq::uint_);

    std::string version = "3.5.1";

    auto start = version.begin();
    if (!bsq::parse(start, version.end(), versionParser, major, minor, build, revision))
    {
        std::cout << "Error!\n";
    }

    std::cout << major << "-" << minor << "-" << build << "-" << revision << std::endl;

    return 0;
}

parse() 的调用会导致内存访问冲突。

我发誓我曾经做过这项工作,但是……也许我是在做白日梦。我已经在 Windows 上使用 Visual Studio 2017 以及在 Coliru 上使用 clang 进行了尝试。我看不到错误。

谢谢。

【问题讨论】:

    标签: c++ boost-spirit-qi


    【解决方案1】:

    问题是使用auto 表达式来捕获规则,它从解析器表达式中推断出类型。该类型是一个原型表达式树,它通过引用捕获任何关系,但这意味着许多中间体在封闭的full-expresion 结束后消失了(参见C++: Life span of temporary arguments?)。

    这是众所周知的,你可以在这里看到:

    这是最简单的解决方法:

    auto versionParser = bsq::copy(
        bsq::uint_
        >> -('.' >> bsq::uint_)
        >> -('.' >> bsq::uint_)
        >> -('.' >> bsq::uint_));
    

    如果您还修复了局部变量缺失的初始化,它可以正常工作:

    Live On Coliru

    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace bsq = boost::spirit::qi;
    
    int main()
    {    
        std::cout << "BOOST_VERSION: " << BOOST_VERSION << std::endl;
    
        std::uint16_t major = 0, minor = 0, build = 0, revision = 0;
    
        auto versionParser = bsq::copy(
            bsq::uint_
            >> -('.' >> bsq::uint_)
            >> -('.' >> bsq::uint_)
            >> -('.' >> bsq::uint_));
    
        std::string version = "3.5.1";
    
        auto start = version.begin();
        if (!bsq::parse(start, version.end(), versionParser, major, minor, build, revision))
        {
            std::cout << "Error!\n";
        }
    
        std::cout << major << "-" << minor << "-" << build << "-" << revision << std::endl;
    }
    

    打印

    BOOST_VERSION: 106600
    3-5-1-0
    

    附加说明

    1. 为了避免整个“属性统一”的情况,让我们让它解析器分配给所有元素,即使在输入文本中未指定:

          >> ('.' >> bsq::uint_ | bsq::attr(0))
          >> ('.' >> bsq::uint_ | bsq::attr(0))
          >> ('.' >> bsq::uint_ | bsq::attr(0))
      
    2. 要诊断尾随“垃圾”的错误(例如 "3.4bogus"),您可以添加一个检查是否已解析完整输入:

      auto versionParser = bsq::copy(
          bsq::uint_
          >> ('.' >> bsq::uint_ | bsq::attr(0))
          >> ('.' >> bsq::uint_ | bsq::attr(0))
          >> ('.' >> bsq::uint_ | bsq::attr(0))
          >> bsq::eoi);
      
    3. 因为版本在语义上是一个元组,为什么不这样表示呢?

      using Version = std::tuple<uint16_t, uint16_t, uint16_t, uint16_t>;
      Version parsed;
      
      if (!bsq::parse(version.begin(), version.end(), versionParser, parsed))
          std::cout << "Error!\n";
      

      这样你甚至可以说:

      using boost::fusion::operator<<;
      
      auto obsolete = parsed < Version(3, 4, 0, 0);
      std::cout << "Version " << parsed << " " << (obsolete? "too old" : "supported") << "\n";
      

    结合这些:

    Live On Coliru

    #include <boost/fusion/adapted/std_tuple.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace bsq = boost::spirit::qi;
    
    int main() {    
        auto versionParser = bsq::copy(
            bsq::uint_
            >> ('.' >> bsq::uint_ | bsq::attr(0))
            >> ('.' >> bsq::uint_ | bsq::attr(0))
            >> ('.' >> bsq::uint_ | bsq::attr(0))
            >> bsq::eoi);
    
        std::string version = "3.5.1";
    
        using Version = std::tuple<uint16_t, uint16_t, uint16_t, uint16_t>;
        Version parsed;
    
        if (!bsq::parse(version.begin(), version.end(), versionParser, parsed))
            std::cout << "Error!\n";
    
        using boost::fusion::operator<<;
    
        auto obsolete = parsed < Version(3, 4, 0, 0);
        std::cout << "Version " << parsed << " " << (obsolete? "too old" : "supported") << "\n";
    }
    

    打印

    Version (3 5 1 0) supported
    

    std::tuple 很烂?

    我同意。因此,等效地编写自己的结构:

    Live On Coliru

    struct Version {
        uint16_t major, minor, revision, build;
    
        auto key() const { return std::tie(major, minor, revision, build); }
        bool operator<(Version const& b) const { return key() < b.key(); }
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Version, major, minor, revision, build)
    

    与时俱进

    请注意,Spirit X3 (Getting into boost spirit; Qi or X3?) 没有您遇到的 auto 问题:

    Live On Coliru

    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/home/x3.hpp>
    
    #include <boost/fusion/include/io.hpp>
    #include <iostream>
    
    namespace bsx = boost::spirit::x3;
    
    struct Version {
        uint16_t major, minor, revision, build;
    
        auto key() const { return std::tie(major, minor, revision, build); }
        bool operator<(Version const& b) const { return key() < b.key(); }
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Version, major, minor, revision, build)
    
    int main() {    
        auto versionParser = bsx::uint_
            >> ('.' >> bsx::uint_ | bsx::attr(0))
            >> ('.' >> bsx::uint_ | bsx::attr(0))
            >> ('.' >> bsx::uint_ | bsx::attr(0))
            >> bsx::eoi;
    
        std::string version = "3.5.1";
    
        Version parsed;
    
        if (!parse(version.begin(), version.end(), versionParser, parsed))
            std::cout << "Error!\n";
    
        using boost::fusion::operator<<;
    
        auto obsolete = parsed < Version{3, 4, 0, 0};
        std::cout << "Version " << parsed << " " << (obsolete? "too old" : "supported") << "\n";
    }
    

    同样打印。

    【讨论】:

    • 添加了三个改进注释和两个带有实时示例的章节。
    • 谢谢你,@sehe,当我有精神问题时,我总是可以依靠你。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多