Разделение std::string на два const char*, в результате чего второй const char* перезаписывает первый

Я беру строку ввода, разделенную пробелом, и пытаюсь прочитать данные в две целочисленные переменные.

например: "0 1" должно дать child1 == 0, child2 == 1.

Код, который я использую, выглядит следующим образом:

int separator = input.find(' ');
const char* child1_str = input.substr(0, separator).c_str(); // Everything is as expected here.
const char* child2_str = input.substr(
    separator+1,  //Start with the next char after the separator
    input.length()-(separator+1) // And work to the end of the input string.
    ).c_str();     // But now child1_str is showing the same location in memory as child2_str!
int child1 = atoi(child1_str);
int child2 = atoi(child2_str);      // and thus are both of these getting assigned the integer '1'.
// do work

То, что происходит, меня бесконечно озадачивает. Я отслеживаю последовательность с помощью отладчика Eclipse (gdb). Когда функция запускается, показано, что child1_str и child2_str имеют разные ячейки памяти (как и должно быть). После разделения строки на separator и получения первого значения child1_str содержит '0', как и ожидалось.

Однако следующая строка, которая присваивает значение child2_str, не только присваивает правильное значение child2_str, но и перезаписывает child1_str. Я даже не имею в виду, что значение символа перезаписывается, я имею в виду, что отладчик показывает, что child1_str и child2_str занимают одно и то же место в памяти.

Что что?

1) Да, я буду рад выслушать другие предложения по преобразованию строки в int - так я научился делать это давным-давно, и у меня никогда не было с этим проблем, поэтому никогда не нужно изменить, однако:

2) Даже если есть лучший способ выполнить преобразование, я все равно хотел бы знать, что здесь происходит! Это мой главный вопрос. Поэтому, даже если вы придумаете лучший алгоритм, выбранный ответ будет тем, который поможет мне понять, почему мой алгоритм не работает.

3) Да, я знаю, что std::string — это C++, а const char* — стандарт C. atoi требует строки c. Я помечаю это как C++, потому что входные данные абсолютно точно будут поступать в виде std::string из фреймворка, который я использую.


person Thomas Thorogood    schedule 03.07.2012    source источник


Ответы (4)


Во-первых, превосходные решения.

В C++11 вы можете использовать новомодную функцию std::stoi:

int child1 = std::stoi(input.substr(0, separator));

В противном случае вы можете использовать boost::lexical_cast:

int child1 = boost::lexical_cast<int>(input.substr(0, separator));

Теперь объяснение.

input.substr(0, separator) создает временный std::string объект, который умирает на точке с запятой. Вызов c_str() для этого временного объекта дает вам указатель, действительный только до тех пор, пока существует временный объект. Это означает, что в следующей строке указатель уже недействителен. Разыменование этого указателя имеет неопределенное поведение. Затем происходят странные вещи, как это часто бывает с неопределенным поведением.

person R. Martinho Fernandes    schedule 03.07.2012
comment
Спасибо за ответ с двойным заголовком. Очень полезно. - person Thomas Thorogood; 03.07.2012

Значение, возвращаемое c_str(), недействительно после уничтожения строки. Итак, когда вы запускаете эту строку:

const char* child1_str = input.substr(0, separator).c_str();

Функция substr возвращает временную строку. После запуска строки эта временная строка уничтожается, а указатель child1_str становится недействительным. Доступ к этому указателю приводит к неопределенному поведению.

Что вам нужно сделать, так это присвоить результат substr локальной переменной std::string. Затем вы можете вызвать c_str() для этой переменной, и результат будет действительным до тех пор, пока переменная не будет уничтожена (в конце блока).

person interjay    schedule 03.07.2012
comment
Теперь это кажется таким очевидным. Спасибо. - person Thomas Thorogood; 03.07.2012

Другие уже указали на проблему с вашим текущим кодом. Вот как я бы сделал преобразование:

std::istringstream buffer(input);

buffer >> child1 >> child2;

Гораздо проще и понятнее, не говоря уже о значительно большей гибкости (например, он будет продолжать работать, даже если ввод содержит табуляцию или два пробела между числами).

person Jerry Coffin    schedule 03.07.2012
comment
Этот метод мне нравится гораздо больше, это точно! - person Thomas Thorogood; 03.07.2012

input.substr возвращает временный std::string. Поскольку вы его нигде не сохраняете, он уничтожается. Все, что произойдет потом, зависит исключительно от вашей удачи.

Я рекомендую использовать istringstream.

person Shahbaz    schedule 03.07.2012