【问题标题】:Why am I getting seg faults from using the istream iterator?为什么使用 istream 迭代器会出现段错误?
【发布时间】:2020-05-17 18:30:35
【问题描述】:
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 执行第二行之后。当我使用字符串向量时,它没有更早的段错误。

【问题讨论】:

  • 因为istream_iterator&lt;char *&gt; 将尝试从(在这种情况下)字符串command 读取指针的,这会导致问题(未定义的行为)为char *被视为char 的数组,即使(如您的情况)它不是。如果要从command 中提取字符串,请使用std::istream_iterator&lt;std::string&gt;std::vector&lt;std::string&gt; 而不是std::istream_iterator&lt;char *&gt;std::vector&lt;char *&gt;
  • @Peter 感谢您的回复。我正在用 C++ 实现一个简单的 shell,并且需要一种将“命令”字符串解析为 char const* 数组以供 execv 的方法。如果我不能将 istream 用于 char*,那么将输入命令分解为一系列标记可能是一种可接受的方式
  • 您可以使用@Peter 的解决方案创建strings 的vector,然后通过调用@ 中每个stringc_str 成员将其转换为const char*[] 987654338@.
  • 跟进@DavidNagy 的评论,可以使用使用所有字符串的c_str() 成员的循环构造从std::vector&lt;std::string&gt; 初始化的std::vector&lt;const char *&gt;。有必要确保vector&lt;string&gt; 继续存在并且其中的字符串在调用execv() 之前保持不变 - 否则传递给execv() 的指针数组将悬空(指向不再存在的东西) .给定一个向量v,可以获得指向元素的指针&amp;v[0],因为向量连续存储其元素。

标签: c++ segmentation-fault istream istream-iterator


【解决方案1】:

嗯,很有趣。听起来很容易,但有几个注意事项。

首先,我们需要考虑execv至少有两种不同的实现方式。

Posix / Linux 下的一个,参见here 和一个windows 版本:参见herehere

请注意不同的函数签名:

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 结尾的 char 指针数组以及函数调用的参数。这是我们需要创建的。

我们从包含命令的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()”-iterator。

这个迭代器将从带有正确类型的空大括号括起来的默认初始值设定项构造,因为由于 std::vector 构造函数需要,它将被推断为与第一个参数的类型相同。

我们可以避免使用std::istringstream,直接使用std::sregex_token_iterator将字符串转换成token。使用非常简单。结果是用于拆分原始命令字符串的单行:

// 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;
}

【讨论】:

  • 使用std::sregex_token_iterator 可能会过度使用单个字符分隔符来分割字符串。您可能应该注意到
【解决方案2】:

首先,您需要将std::string 命令拆分为std::vector&lt;std::string&gt; 类型的令牌列表。然后,您可能需要使用std::transform 来填充std::vector&lt;char const*&gt; 类型的新令牌列表。

这是一个示例代码:

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

【讨论】:

    【解决方案3】:

    您首先需要创建一个std::stringstd::vector 将拥有字符串数据,然后您可以将std::vector 转换为指针的std::vector,请注意,指针仅在生命周期内有效std::stringstd::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");
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-19
      • 1970-01-01
      • 1970-01-01
      • 2011-04-23
      • 2013-01-29
      • 2013-02-11
      相关资源
      最近更新 更多