Отсутствующие элементы при возврате std :: vector - PullRequest
4 голосов
/ 01 мая 2019

Я пишу функцию на C ++, которая теоретически должна принимать пользовательский ввод и разделять этот ввод на сегменты в соответствии с пробелами и возвращать эти сегменты как вектор.

В настоящее время я использую strtok () во входной строке, чтобы отделить слова от пробелов. Для каждого «слова» я помещаю его в буферный вектор. Перебирая каждое слово, я возвращаю вектор.

Итак, этот код у меня есть:

#include <iostream>
#include <string>
#include <cstring>
#include <vector>

std::vector<char*> tokenize(std::string input_, char const* delims=" \t\r\n\a")
{
    char* input = (char*)input_.c_str();
    std::vector<char*> tk_stream;
    char* tk = strtok(input, delims);

    while(tk != NULL) {
        tk_stream.push_back(tk);
        tk = strtok(NULL, delims);
    }

    return tk_stream;
}

int main(int argc, char** argv)
{
    while (true) {
        std::string input;
        std::getline(std::cin, input);

        if (input.empty()) {
            continue;
        }

        std::vector<char*> tks = tokenize(input);
        for (char* el : tks) {
            std::cout << el << std::endl;
        }
    }
    return 0;
}

Так что же должно произойти? хорошо, если у меня есть ввод "1 2 3 4", он должен напечатать каждое из этих чисел в отдельных строках. Это на самом деле работает с этим входом. Но когда длина входной строки больше, например, «1 2 3 4 5 6 7 8 9», вывод будет другим:

1 2 3 4 5 6 7 8 9




5 
6 
7 
8 
9

Не хватает первых 4 цифр! Это также происходит с любой строкой, длина которой больше этой, а число пропущенных чисел постоянно. Я также заметил, что это происходит с более длинными предложениями. Например, «Привет всем, это тест» дает:

hello everyone this is a test
0��

this
is
a
test

Я уже покопался в gdb и нашел кое-что интересное. С помощью ввода «1 2 3 4 5 6 7 8 9» я установил точку останова перед возвратом «tk_stream» и проверил ее значение:

(gdb) print tk_stream
$1 = std::vector of length 9, capacity 16 = {0x6176c0 "1", 0x6176c2 "2", 0x6176c4 "3", 0x6176c6 "4", 0x6176c8 "5", 0x6176ca "6", 0x6176cc "7", 0x6176ce "8", 0x6176d0 "9"}

Это кажется правильным. Но после того, как я перешагну несколько строк, когда это будет возвращено из функции, и проверю значение 'tks' (вектор, который должен содержать возвращаемое значение функции 'tokenize'); Я получаю это:

(gdb) print tks
$2 = std::vector of length 9, capacity 16 = {0x6176c0 "", 0x6176c2 "a", 0x6176c4 "", 0x6176c6 "", 0x6176c8 "5", 0x6176ca "6", 0x6176cc "7", 0x6176ce "8", 0x6176d0 "9"}

, в котором отсутствуют первые 4 записи с искаженной 2-й записью.

Так что что-то должно произойти в возвращении вектора 'tk_stream'.

В чем причина этого ненормального поведения? Как я могу это исправить, чтобы элементы вектора не были удалены?

Ответы [ 2 ]

3 голосов
/ 01 мая 2019

Вы не хотите использовать необработанные указатели, такие как char*, вместо этого используйте std::string.

Что-то вроде:

std::vector<std::string> tokenize(std::string input_, const std:string delims=" \t\r\n\a")
{
    std::string input = input_;
    std::vector<std::string> tk_stream;
    // ...
1 голос
/ 01 мая 2019

Вы передаете свою строку по значению в функцию токенизации. Затем вы вызываете c_str () для этого локального строкового объекта и сохраняете указатели в это пространство в своем векторе. Затем ваша функция возвращает, а вместе с ней и хранилище в локальном строковом объекте. Что теперь означает, что все указатели, которые вы сохранили в векторе, теперь являются висячими указателями. Отстаивать любую из них - это неопределенное поведение.

Похоже, что это работает для коротких строк (вероятно, длина строки <16 символов) из-за того, что называется Оптимизация коротких строк. Многие реализации std :: string имеют небольшой буфер (общий размер составляет 16 байт, но он не определяется стандартом) внутри самого объекта std :: string. Как только строка становится длиннее, std :: string будет динамически выделять буфер для хранения строки. Когда вы используете короткую строку, ваши висячие указатели указывают на ваш стек, и ваши данные там еще не были перезаписаны. Когда вы используете длинную строку, ваши указатели указывают на какое-то произвольное место в памяти, которое могло быть перезаписано чем-то другим. </p>

О, чтобы исправить, передайте свой std :: string по ссылке: const std::string & input_.

...