Почему std::string хранит содержимое по одному и тому же адресу внутри цикла?

Мне нужно создать char[] динамически. Я хотел бы использовать std::string вместо работы с char[] напрямую, так как мне не нужно заботиться об управлении памятью с помощью std::string. Вот мой пример кода:

int main()
{
    std::vector<char*> char_vec;
    for(size_t i = 0; i < 10; i++)
    {
        std::string s;
        s = "test" + std::to_string(i);
        char_vec.push_back(const_cast<char*>(s.c_str()));
    }

    for(auto& ele : char_vec)
    {
        std::cout << ele << std::endl;
    }
}

Результат: , чего я не хочу.

В моем понимании строка s, созданная внутри цикла, должна быть совершенно новой на каждой итерации, но на самом деле все строки используют один и тот же адрес для хранения содержимого. Почему это происходит? Как мне изменить свой код для достижения моей цели?

Спасибо за помощь!


Обновление от 10 января 2023 г.:

Я попробовал следующий код на основе ответов:

int main()
{
    std::vector<char*> char_vec;
    std::vector<std::string> str_vec;
    for(size_t i = 0; i < 10; i++)
    {
        // str_vec.emplace_back("test" + std::to_string(i));
        str_vec.push_back("test" + std::to_string(i));
        char_vec.push_back(const_cast<char*>(str_vec.back().c_str()));
    }

    for(auto& ele : char_vec)
    {
        std::cout << ele << std::endl;
    }
}

но все еще не может вывести желаемые результаты. Могу я спросить, почему это не работает?

Каждый std::string, созданный в цикле, создается точно в одном и том же месте памяти. Обратите внимание, что сохраненные вами строки C исчезают после цикла for, когда s выходит за рамки. Поэтому программа имеет неопределенное поведение.

Ted Lyngmo 07.01.2023 21:44

На каждой итерации цикла создается временная переменная std::string. Затем вы берете указатель на данные, которыми управляет переменная, и помещаете его в вектор. Затем переменная уничтожается, как и управляемые ею данные. В конце у вас есть вектор, набитый болтающимися указателями. Попытка использовать эти указатели впоследствии приводит к неопределенному поведению.

Igor Tandetnik 07.01.2023 21:45

@TedLyngmo Потому что (вероятно) SSO

Paul Sanders 07.01.2023 21:45

@PaulSanders Да - и это, вероятно, спасло ОП от мира боли позже. Если бы были созданы более длинные строки, OP, вероятно, подумал бы, что все в порядке.

Ted Lyngmo 07.01.2023 21:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Сохранение указателей s.c_str() в векторе, когда s живет только до конца итерации цикла, приведет к неопределенному поведению, самое позднее, когда вы попытаетесь прочитать эти указатели.

Неважно, имеют ли указатели одинаковое значение. Как только объем s заканчивается, они становятся недействительными. Память, к которой они обращаются, может быть повторно использована компилятором/библиотекой для чего угодно. Вы не гарантируете, что вообще сможете прочесть из них что-то толковое. То, что ты отыгрался test9, было "удачей".

Вы должны использовать std::vector<std::string>, а затем просто char_vec.push_back(s); (или, если вы хотите оптимизировать char_vec.push_back(std::move(s)), но только потому, что вы не используете s после push_back).

Или еще лучше char_vec.push_back(std::move(s));

john 07.01.2023 21:49

@user17732522 user17732522 Я обновил код на основе вашего предложения, но все еще не могу получить желаемые результаты. Могу ли я спросить, почему?

Chuan Xu 10.01.2023 16:18

@john, могу я спросить, почему std::move() лучше?

Chuan Xu 10.01.2023 16:19

Потому что это экономит затраты на копирование строки. Поскольку вы больше не используете переменную s, память, на которую она ссылается, может быть просто «перемещена» в вектор без необходимости выделения новой памяти и копирования символов.

john 10.01.2023 16:49

@ChuanXu std::vector хранит свои элементы непрерывно. Если вы добавляете новый элемент и зарезервированный блок памяти становится слишком маленьким, то std::vector должен сделать новое (большее) выделение и скопировать/переместить в него старые элементы. Это делает недействительным любой указатель, который вы получили в std::strings. Если вам нужно полагаться на то, что указатели char* остаются действительными, пока вектор действителен, то вы должны либо сначала вызвать .reserve(10) для вектора и убедиться, что его размер никогда не превышает 10, либо вам нужно использовать другой контейнер, например. std::list.

user17732522 10.01.2023 20:24

@ChuanXu Или, лучше, сформируйте вектор char* только после того, как вы закончите изменение вектора std::string. Более того, старайтесь избегать формирования массива таких указателей char* в первую очередь. Обычно вам это не нужно в современном С++. Но если вам это нужно (например, потому что вам нужно вызвать C API), тогда охватите вектор char* как можно более узко.

user17732522 10.01.2023 20:26

@user17732522 user17732522 Круто, твое объяснение имеет смысл. Спасибо.

Chuan Xu 17.01.2023 19:54

Другие вопросы по теме