Рассмотрим следующий код:
const char foo[] = "lorem ipsum"; // foo is an array of 12 characters
const auto length = strlen(foo); // length is 11
string bar(length, '\0'); // bar was constructed with string(11, '\0')
strncpy(data(bar), foo, length);
cout << data(bar) << endl;
Насколько я понимаю, string
всегда выделяются скрытым нулевым элементом. Если это так, то bar
действительно выделяет 12 символов, при этом 12th является скрытым '\0'
, и это совершенно безопасно ... Если я ошибаюсь, тогда cout
приведет к неопределенному поведению, потому что нет нулевого терминатора .
Может кто-нибудь подтвердить за меня? Это законно?
Было много вопросов о том, почему использовать strncpy
вместо простого конструктора string(const char*, const size_t)
. Мое намерение состояло в том, чтобы сделать код моей игрушки близким к моему фактическому коду, который содержит vsnprintf
. К сожалению, даже получив здесь отличные ответы, я обнаружил, что vsnprintf
не ведет себя так же, как strncpy
, и я задал следующий вопрос здесь: Почему vsnprintf не записывает такое же количество символов, как strncpy?
@TrebuchetMS Да, спасибо, я исправил этот комментарий.
У вас есть реальный пример использования этого? Если вы дадите std::string
c-образную струну, он будет делать то же самое, не царапая голову.
@NathanOliver Да, я использую vsnprintf
для заполнения string
. Просто казалось, что это только усложняет вопрос, который нужно задать, и не форсирует вопрос.
что такое string
и что такое data()
?
@Slava У меня есть злой using namespace std
до этого игрушечного примера. Итак, все это извлекается из стандартного пространства имен.
Тогда почему бы не std::string bar( foo, length );
вместо надоедливого strncpy()
?
@JonathanMee Есть ли причина, по которой вы не используете stringstream
, чтобы полностью этого избежать?
@Slava На самом деле, потому что я не так делаю в своем реальном коде. Но это сработало бы. В моем реальном коде я дважды вызываю vsnprintf
, один раз, чтобы получить размер, который я использую для выделения string
, затем я снова вызываю vsnprintf
, заполняя string
.
Почему бы не использовать с ним char buffer[arbitrarySize]
, затем vsnprintf
, а затем просто создать std::string
, используя этот буфер и размер? Вы действительно думаете, что вызов vsnprintf()
дважды более эффективен, чем создание массива символов в стеке?
@NathanOliver va_list
используется как способ, позволяющий интерфейсу C принимать информацию журнала. Так что не гарантируется, что на другой стороне интерфейса даже будет stringstream
.
@Slava arbitrarySize
может быть слишком маленьким, не так ли?
Вы имели ввиду cout << bar << endl
? В противном случае я не уверен в сути этой части вопроса.
Наверное. В любом случае я бы вернул std
в ваш пример и изменил его на snprintf( bar.data(), bar.size(), "format", data )
вместо strncpy()
, чтобы избежать путаницы.
@Slava Двойной вызов - довольно распространенный подход, позволяющий избежать случайного угадывания необходимого размера. Хотя компромиссы различаются.
@LightnessRacesinOrbit Это действительно вопрос о лежащем в основе string
. Я хочу знать, будет ли существовать 12-й «скрытый нулевой символ», даже если я построю его как string(11, '\0')
@LightnessRacesinOrbit, конечно, тогда вопрос должен отражать это вместо уродливого примера использования strncpy()
, который приводит к вопросу, почему бы не создать std::string
прямо из него? Использование snprintf()
в этом примере нисколько не усложнит его.
@Slava Я согласен с тем, что использование strncpy
здесь не является необходимым и не рекомендуется (и некоторое время назад я поддержал ваш комментарий по этому поводу). Хотя это не отменяет вопрос!
@LightnessRacesinOrbit Я не говорил, что это так (и я бы отклонил этот вопрос, если я так думаю), я просто предложил улучшения. Использование snprintf
или подобных ему понятно, использование strncpy
в этом случае стимулирует несвязанные дискуссии.
@Slava В самом деле! :)
Да, это безопасно согласно char * strncpy (char * назначение, const char * source, size_t num):
Copy characters from string
Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.
Проблема в том, что есть дополнительный символ, который нужно скопировать.
Не в этом вопрос, очевидно, что strncpy
не будет записывать за границу. Вопрос в том, выйдет ли cout
читать за пределы допуска. Исправьте ответ, чтобы задать вопрос, или удалите.
@Matthieu - Не обязательно. Если целью является std::string
, он сам знает ее длину.
@StoryTeller, однако, отсутствует \0
(при использовании с data(bar)
). Это действительно нормально при использовании самой строки.
@Matthieu - проблема только в том случае, если оператору нужен целевой буфер для хранения строки с завершающим нулем.
@StoryTeller действительно полностью согласен.
Здесь есть две разные вещи.
Во-первых, добавляет ли strncpy
дополнительный \0
в этом случае (11 элементов, не относящихся к \0
, которые должны быть скопированы в строку размером 11). Ответ - нет:
Copies at most count characters of the byte string pointed to by src (including the terminating null character) to character array pointed to by dest.
If count is reached before the entire string src was copied, the resulting character array is not null-terminated.
Так что звонок в порядке.
Затем data()
дает вам правильную строку с завершением \0
:
c_str() and data() perform the same function. (since C++11)
Таким образом, похоже, что для C++ 11 вы в безопасности. В документации не указано, выделяет ли строка дополнительный \0
или нет, но API ясно, что то, что вы делаете, прекрасно.
Чтобы расширить ваше утверждение, вы говорите, что string
не является выделен скрытым нулевым ограничителем?
Распределение должно работать таким образом, чтобы c_str()
/ data()
был O(1)
, учитывая, что при индексировании str[str.size()]
вы получаете '\0'
(начиная с C++ 11). На практике они всегда так работали.
Вы выделили 11-значный std::string
. Вы не пытаетесь читать или писать что-либо помимо этого, так что эта часть будет в безопасности.
Итак, настоящий вопрос заключается в том, не испортили ли вы внутреннюю структуру струны. Если вы не сделали ничего недопустимого, как это возможно? Если требуется, чтобы строка содержала внутри 12-байтовый буфер с нулевым заполнением в конце для выполнения своего контракта, это будет иметь место независимо от того, какие операции вы выполняли.
Это безопасно, если вы копируете символы [0, size())
в строку. За [basic.string] / 3
In all cases,
[data(), data() + size()]
is a valid range,data() + size()
points at an object with valuecharT()
(a “null terminator”), andsize() <= capacity()
istrue
.
Таким образом, string bar(length, '\0')
дает вам строку с size()
, равным 11, с неизменным нулевым ограничителем в конце (всего 12 символов фактического размера). Пока вы не перезаписываете этот нулевой терминатор и не пытаетесь прописать его, все в порядке.
Не уверен, что с ним все будет в порядке - std::string::length()
выдаст "неправильную" информацию
@Slava Как бы это было не так? Строка начинается с размера 11
, который не может измениться, если они не используют строковую функцию. Если они копируют только 5 символов, у них все еще остается строка размером 11, у нее просто есть 6 дополнительных нулей, завершающих ее.
Я заключил «неправильно» в двойные кавычки. Да, он покажет правильный размер буфера, но использование этого в качестве строки может привести к некрасивым проблемам, которые очень сложно обнаружить и исправить позже, когда эта строка будет использоваться.
@Slava std::string
может содержать нулевые символы, в отличие от C-строки.
strlen
считает только строки в стиле C. Действительно, размер, указанный в строке, является правильным.
@MarkRansom Я понимаю это, поэтому я не утверждаю, что он наверняка сломается, но есть довольно высокая вероятность, что в дальнейшем возникнут проблемы - поскольку большинство разработчиков ожидают, что length()
предоставит вам длину строки, а не размер буфера. Поэтому я бы избегал такого кода, если не хочу проблем, и использовал бы std::vector<char>
, если мне нужен буфер.
@Slava Я определенно вижу потенциал для проблем, но я думаю, что вы его преувеличиваете. length()
действительно дает вам истинный размер строки, это сбивает с толку strlen()
. Вы столкнетесь с проблемой только тогда, когда смешаете код, который работает с string
и C-строками, и только тогда, если у вас есть встроенный нулевой символ, что не относится к приведенному примеру.
@MarkRansom "Вы столкнетесь с проблемой только тогда, когда смешаете код, который работает со строкой и C-строкой", правильно говоря, что вы, вероятно, будете в порядке с человеком, который спрашивает, можно ли использовать strncpy()
, поскольку маловероятно, что вы будете иметь дело с C-струны. Да, я преувеличиваю.
@Slava Я слышу сарказм в твоем ответе. Я надеюсь, что причина для копирования на string
в первую очередь состоит в том, чтобы работать над ним в этой форме с этого момента, и с этого момента встроенный нуль в основном безвреден. Опять же, как показывает вопрос.
@MarkRansom Я пытаюсь сказать, что если кто-то пытается преобразовать strncpy()
в std::string
, то есть довольно высокая вероятность, что эта строка будет преобразована / обработана как C-строка позже. Поэтому я бы не стал использовать std::string
в качестве буфера, если не хочу тратить много времени на отладку, которую трудно выявить. Но это всего лишь мое скромное мнение, поэтому я заключил слово «неправильно» в двойные кавычки.
@Slava «поскольку большинство разработчиков ожидают, что length () даст вам длину строки, а не размер буфера». Но это именно то, что он делает. Строка - это последовательность байтов. Ближе к концу есть несколько нулевых байтов. Вам следует избавиться от привычки думать, что «строка» эквивалентна «c-строке». (Между тем, буфер мог бы быть несколько больше благодаря .reserve()
и т. д.)
@Slava "тогда очень высока вероятность того, что эта строка будет преобразована / обработана как C-строка позже" обрабатывали может быть проблемой, но преобразованный (с использованием strlen и strcpy of the data ()) должен быть безопасным.
@LightnessRacesinOrbit "Вам следует избавиться от привычки думать, что" строка "эквивалентна" c-строке "." Проблема не в том, чтобы избавиться от этой привычки, а в том, чтобы заставить всех разработчиков делать это. У меня нет такой возможности, поэтому безопаснее использовать std::vector<char>
, когда мне нужен буфер, и std::string
, когда мне нужна строка, которую можно рассматривать как C-строку.
@Slava Если разработчики вокруг вас относятся к std::string
как к C-строке, тогда они не разработчики на C++ .... (хотя я соглашусь, что vector<char>
- или vector<byte>
! - часто лучше по ряду причин)
Как же этого не могло быть? Предполагая, конечно, что вы не копируете больше байтов, чем имеется доступное буферное пространство.