Boost Spirit Qi вылетает из-за нарушения памяти - PullRequest
0 голосов
/ 28 октября 2018

Но я не могу понять, почему ...?

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.Я не вижу ошибки.

Спасибо.

1 Ответ

0 голосов
/ 28 октября 2018

Проблема заключается в использовании auto выражений для захвата правил, которые выводят типы из выражений синтаксического анализатора.Этот тип является деревом прото-выражений, который захватывает любые отношения по ссылке, но это означает, что многие из промежуточных звеньев пропали после окончания включающего полного выражения (см. C ++: продолжительность жизни временных аргументов? ).

Это довольно хорошо известно, как вы можете видеть здесь:

Вот самое простое исправление:

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)

Gettin 'With The Times

Обратите внимание, что у Spirit X3 ( в духе повышения; Qi или 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";
}

Также печатает то же самое.

...