Почему я получаю ошибки seg от использования итератора istream? - PullRequest
1 голос
/ 01 февраля 2020
void parse_and_run_command(const std::string &command) {
    std::istringstream iss(command);
    std::istream_iterator<char*> begin(iss), end;
    std::vector<char*> tokens(begin, end); //place the arguments in a vector
    tokens.push_back(NULL); 

Согласно GDB, ошибка возникает после выполнения второй строки с помощью istream_iterator. Раньше я не использовал segfault, когда я использовал строковые векторы.

Ответы [ 3 ]

1 голос
/ 01 февраля 2020

Сначала вам нужно разделить команду std::string на список токенов типа std::vector<std::string>. Затем вы можете использовать std::transform, чтобы заполнить новый список токенов типа std::vector<char const*>.

. Вот пример кода:

void parse_and_run_command(std::string const& command) {
    std::istringstream iss(command);
    std::vector<std::string> results(std::istream_iterator<std::string>{iss},
                                     std::istream_iterator<std::string>());

    // debugging
    for (auto const& token : results) {
        std::cout << token << " ";
    }

    std::cout << std::endl;

    std::vector<const char*> pointer_results;
    pointer_results.resize(results.size(), nullptr);
    std::transform(
        std::begin(results), std::end(results),
        std::begin(pointer_results),
        [&results](std::string const& str) {
            return str.c_str();
        }
    );

    // debugging
    for (auto const& token : pointer_results) {
        std::cout << token << " ";
    }

    std::cout << std::endl;

    // execv expects NULL as last element
    pointer_results.push_back(nullptr);

    char **cmd = const_cast<char**>(pointer_results.data());
    execv(cmd[0], &cmd[0]);
}

Обратите внимание на последнюю часть функции: execv ожидает, что последний элемент будет nullptr.

1 голос
/ 01 февраля 2020

Сначала вам нужно создать std::vector из std::string, который будет владеть строковыми данными, затем вы можете преобразовать это std::vector в std::vector указателей, обратите внимание, что указатели будут действительны только на протяжении жизни std::string std::vector:

#include <string>
#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <algorithm>

void parse_and_run_command(const std::string &command) {
    std::istringstream iss(command);
    std::istream_iterator<std::string> begin(iss), end;
    std::vector<std::string> tokens(begin, end);
    std::vector<char*> ctokens;
    std::transform(tokens.begin(), tokens.end(), std::back_inserter(ctokens), [](std::string& s) { return s.data(); });
    ctokens.push_back(nullptr);
    for (char* s : ctokens) {
        if (s) {
            std::cout << s << "\n";
        }
        else {
            std::cout << "nullptr\n";
        }
    }
}

int main() {
    parse_and_run_command("test test2 test3");
}
0 голосов
/ 01 февраля 2020

Хм, очень интересно. Звучит как простая задача, но есть несколько предостережений.

Прежде всего, мы должны учесть, что есть как минимум 2 различных реализации execv.

Одна под Posix / Linux, см. здесь и версию windows: см. здесь и здесь .

Обратите внимание на различные сигнатуры функций:

Linux / POSIX:   int execv(const char *path, char *const argv[]);
Windows:         intptr_t _execv(const char *cmdname, const char *const *argv);

В этом случае я нахожу версию WIndows немного чище, потому что параметр argv имеет тип const char *const *. В любом случае, основная проблема заключается в том, что мы должны вызывать устаревший код.

Хорошо, давайте посмотрим.

Для функции execv требуется массив символов-указателей с NULL-символами в конце с аргументом для вызов функции. Это нам нужно создать.

Начнем с std::string, содержащего команду. Это должно быть разделено на части. Есть несколько способов, и я добавил разные примеры.

Самый простой способ - это поместить std::string в std::istringstream и затем использовать std::istream_iterator, чтобы разбить его на части. Это типичная короткая последовательность:

// Put this into istringstream 
std::istringstream iss(command);
// Split
std::vector parts(std::istream_iterator<std::string>(iss), {});

Мы используем конструктор диапазона для std::vector. И мы можем определить std::vector без аргумента шаблона. Компилятор может вывести аргумент из заданных параметров функции. Эта функция называется CTAD («дедукция аргумента шаблона класса»).

Кроме того, вы можете видеть, что я не использую итератор end () явно.

Этот итератор будет создается из пустого инициализатора по умолчанию, заключенного в скобки, с правильным типом, потому что он будет выведен таким же, как тип первого аргумента, поскольку конструктор std :: vector требует этого.

Мы можем избежать использование std::istringstream и прямое преобразование строки в токены с помощью std::sregex_token_iterator. Очень прост в использовании. В результате получается одна строка для разделения исходной строки команды:

// Split
std::vector<std::string> parts(std::sregex_token_iterator(command.begin(), command.end(), re, -1), {});

Все это сводится к 6 строкам кода, включая определение переменной и вызов функции execv:

Пожалуйста, смотрите:

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <iterator>
#include <memory>
#include <algorithm>
#include <regex>

const std::regex re{ " " };

// Define Dummy function for _execv  (Windows style, eveything const)
// Note: Type of argv decays to " const char* const* "
int _execv(const char* path, const char* const argv[]) {
    std::cout << "\n\nPath: " << path << "\n\nArguments:\n\n";
    while (*argv != 0) std::cout << *argv++ << "\n";
    return 0;
}

// Define Dummy function for _execv  (Posix style)
// Note: Type of argv decays to " char* const* "
int execv(const char* path, char* const argv[]) {
    std::cout << "\n\nPath: " << path << "\n\nArguments:\n\n";
    while (*argv != 0) std::cout << *argv++ << "\n";
    return 0;
}


int main() {
    {
        // ----------------------------------------------------------------------
        // Solution 1
        // Initial example
        char path[] = "path";
        const char* const argv[] = { "arg1", "arg2", "arg3", 0 };
        _execv(path, argv);
    }


    {
        // ----------------------------------------------------------------------
        // Solution 2
        // Now, string, with command convert to a handmade argv array
        std::string command{ "path arg1 arg2 arg3" };

        // Put this into istringstream 
        std::istringstream iss(command);

        // Split into substrings
        std::vector parts(std::istream_iterator<std::string>(iss), {});

        // create "argv" List. argv is of type " const char* "
        std::unique_ptr<const char*[]> argv = std::make_unique<const char*[]>(parts.size());

        // Fill argv array
        size_t i = 1U;
        for (; i < parts.size(); ++i) {
            argv[i - 1] = parts[i].c_str();
        }
        argv[i - 1] = static_cast<char*>(0);

        // Call execv
        // Windows
        _execv(parts[0].c_str(), argv.get());

        // Linux / Posix
        execv(parts[0].c_str(), const_cast<char* const*>(argv.get()));
    }

    {
        // ----------------------------------------------------------------------
        // Solution 3
        // Transform string vector to vector of char*
        std::string command{ "path arg1 arg2 arg3" };

        // Put this into istringstream 
        std::istringstream iss(command);
        // Split
        std::vector parts(std::istream_iterator<std::string>(iss), {});

        // Fill argv
        std::vector<const char*> argv{};
        std::transform(parts.begin(), parts.end(), std::back_inserter(argv), [](const std::string& s) { return s.c_str(); });
        argv.push_back(static_cast<const char*>(0));

        // Call execv
        // Windows
        _execv(argv[0], &argv[1]);

        // Linux / Posix
        execv(argv[0], const_cast<char* const*>(&argv[1]));
    }
    {
        // ----------------------------------------------------------------------
        // Solution 4
        // Transform string vector to vector of char*. Get rid of istringstream
        std::string command{ "path arg1 arg2 arg3" };

        // Split
        std::vector<std::string> parts(std::sregex_token_iterator(command.begin(), command.end(), re, -1), {});

        // Fill argv
        std::vector<const char*> argv{};
        std::transform(parts.begin(), parts.end(), std::back_inserter(argv), [](const std::string& s) { return s.c_str(); });
        argv.push_back(static_cast<const char*>(0));

        // Call execv
        // Windows
        _execv(argv[0], &argv[1]);

        // Linux / Posix
        execv(argv[0], const_cast<char* const*>(&argv[1]));
    }

    return 0;
}

...