2009-12-31 3 views
31

Я делаю некоторые работы по техническому обслуживанию и натыкался что-то вроде следующего:Does "& s [0]" указывает на смежные символы в std :: string?

std::string s; 
s.resize(strLength); 
// strLength is a size_t with the length of a C string in it. 

memcpy(&s[0], str, strLength); 

Я знаю, используя & с [0] будет в безопасности, если это был станд :: вектор, но это безопасное использование std :: string?

+3

Использование & s [0] в порядке, memcpy(), возможно, меньше. Почему бы просто не выполнить назначение или использовать функцию assign() функции string? – 2009-12-31 20:26:02

+1

@Neil Butterworth, вот что я задаю себе, глядя на этот код ...;) – paxos1977

+0

По мере того, как вы получаете опыт программирования на C++, вы будете воздерживаться от использования 'memset' и' memcpy' и учиться рассуждения. Это одно, чтобы добавить к вашему опыту. –

ответ

34

Атрибут std :: string не гарантированно соприкасается со стандартом C++ 98/03, но C++ 11 заставляет его быть. На практике ни я, ни Herb Sutter не знают о реализации, которая не использует непрерывное хранилище.

Обратите внимание, что функция &s[0] всегда гарантируется работой по стандарту C++ 11, даже в случае с строкой длиной в 0 строк. Это не будет гарантировано, если вы сделали str.begin() или &*str.begin(), но &s[0] стандарт определяет operator[] как:

Возвращает: *(begin() + pos) если pos < size(), в противном случае ссылка на объект типа T со значением charT(); ссылочное значение не должно быть изменено

Продолжая, data() определяется как:

Возвращает: указателя p такое, что p + i == &operator[](i) для каждого i в [0,size()].

(обратите внимание на квадратные скобки на обоих концах диапазона)


Примечание: предварительная стандартизация C++ 0x не гарантирует &s[0] для работы со строками нулевой длины (на самом деле, это было явно неопределенное поведение), и более старая ревизия этого ответа объяснила это; это было исправлено в более поздних стандартных черновиках, поэтому ответ был соответствующим образом обновлен.

+0

Я не следовал стандарту за последние несколько месяцев, но это было мое впечатление, что это все еще было в проекте 0x, а потому на самом деле еще не требуется (или будет, если библиотека выбирает только реализованный '03). –

+3

Sutter говорит в комментарии к этому сообщению: «В настоящее время ISO C++ требует & str [0], чтобы кашлять указатель на непрерывные строковые данные (но не обязательно заканчиваться нулями!)», Что фактически сделало бы использование OP правильным. Однако я не могу найти ничего, что говорит о том, что в стандарте (по крайней мере, это не в 21.3.4 lib.string.access). –

+0

Я думаю, что это может быть правильно; дефект std 530 говорит, что оператор [] является непрерывным, но интерфейс итератора не гарантируется, и цитирует 23.4.4. Я выкапываю свой стандарт, чтобы проверить. –

6

Технически, нет, поскольку std::string не требуется хранить его содержимое смежно в памяти.

Однако почти во всех реализациях (каждая реализация которых я знаю) содержимое хранится смежно, и это «работает».

+0

Можете ли вы определить некоторые реализации, где это не сработает? –

+2

Нет. Но вы можете сделать такую ​​реализацию, если хотите. –

+0

@Neil: У вас есть ссылка/ссылка на этот TC? –

2

Читатели должны обратить внимание на то, что этот вопрос был задан в 2009 году, когда стандартом C++ 03 была текущая публикация. Этот ответ основан на этой версии Стандарта, в которой std::string s равно , а не, гарантированно использующий непрерывное хранилище. Поскольку этот вопрос не задавался в контексте конкретной платформы (например, gcc), я не делаю никаких предположений о платформе OP - в частности, о погоде или нет, она использует условное хранилище для string.

Юридический? Может быть, может и нет. Безопасно? Наверное, но, возможно, нет. Хороший код? Ну, давай не будем туда ...

Почему бы не просто сделать:

std::string s = str; 

... или:

std::string s(str); 

... или:

std::string s; 
std::copy(&str[0], &str[strLen], std::back_inserter(s)); 

... или:

std::string s; 
s.assign(str, strLen); 

?

+0

или s.assign (str, strLen); – 2009-12-31 20:34:11

+0

good, updated w/assign –

+1

'std :: string s (str, strLen);' (самая короткая форма идентична, в случае встроенных нулей или отсутствия нулевого завершения, к исходному поведению из вопроса.) – 2009-12-31 21:44:51

0

Это, как правило, не безопасно, независимо от того, сохраняется ли внутренняя последовательность строк в памяти непрерывно или нет. Может быть много других деталей реализации, связанных с тем, как контролируемая последовательность хранится объектом std::string, помимо непрерывности.

Настоящая практическая проблема с этим может быть следующей. Управляемая последовательность std::string не требуется хранить в виде строки с нулевым завершением. Однако на практике многие (большинство?) Реализаций выбирают, чтобы увеличить внутренний буфер на 1 и сохранить последовательность как строку с нулевым завершением, так как она упрощает реализацию метода c_str(): просто верните указатель на внутренний буфер, и вы сделанный.

Код, указанный в вашем вопросе, не прикладывает никаких усилий для нулевого завершения копирования данных во внутренний буфер. Вполне возможно, что он просто не знает, требуется ли нулевое завершение в этой реализации std::string. Вполне возможно, что он полагается на заполнение внутренним буфером нулей после вызова resize, поэтому дополнительный символ, выделенный для нулевого терминатора реализацией, удобно предварительно установлен на ноль. Все это детализация реализации, что означает, что этот метод зависит от некоторых довольно хрупких предположений.

Другими словами, в некоторых реализациях вам, вероятно, придется использовать strcpy, а не memcpy, чтобы принудительно вводить данные в управляемую последовательность. Хотя в некоторых других реализациях вам нужно будет использовать memcpy, а не strcpy.

+1

После вызова 'resize' вы можете быть абсолютно уверены, что внутренняя строка является или не имеет нулевой последовательности, как требует реализация. После вызова 'resize' после всех вы должны иметь допустимую строку из n символов (с нулевыми символами, если необходимо). - Тем не менее, это показывает отсутствие понимания для класса 'std :: string': memcpy используется либо из-за незнания, либо как ошибочная попытка для производительности (из-за вызова' resize' код заканчивает назначение значений в буфер дважды). – UncleBens

+0

@UncleBens: Я не понимаю твое первое предложение. В любом случае, да, стандарт языка гарантирует, что увеличивающий размер 'resize' вызов накладывает строку на нули. Тем не менее, стандарт гарантирует заполнение только до требуемого размера ('strLength' в этом случае), но в стандарте для этого дополнительного символа нет гарантии, если реализация выделяет его. – AnT

0

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

std::string s(str) ; 

или присвоения существующего объекта станд :: строки, просто:

s = str ; 

, а затем пусть std :: string сама определяет, как достичь результата. Если вы собираетесь прибегать к такой чепухе, то вы также можете не использовать std :: string и придерживаться, поскольку вы повторно вводите все опасности, связанные с строками C.

+0

На самом деле я не могу быть уверен, что назначенная строка имеет нулевое завершение. Таким образом, лучшее, что я мог сделать, вероятно, будет s.assign (ptr, ptrLength); который по-прежнему является улучшением, я думаю. – paxos1977

+0

Используйте форму конструктора: 'std :: string s (str, strLen);' – GManNickG

6

Безопасно использовать. Я думаю, что большинство ответов были правильными один раз, но стандарт изменился. Цитируя стандарта C++ 11, basic_string общих требований [string.require], 21.4.1.5, говорят:

полукокса подобных объектов в объекте basic_string должен храниться смежно.То есть, для любого basic_string объекта с, личность & * (s.begin() + п) == & * s.begin() + п должно выполняться для всех значений п такие, что 0 = < < н ы .размер().

Немного до этого в нем говорится, что все итераторы являются итераторами произвольного доступа. Оба бита поддерживают использование вашего вопроса. (Кроме того, Строуструп, по-видимому, использует его в своей новейшей книге;))

Это маловероятно, что это изменение было выполнено на C++ 11. Я, кажется, помню, что та же гарантия была добавлена ​​тогда для вектора, который также получил очень полезный указатель () с этой версией.

Надеюсь, что это поможет.

+2

Вопрос был pre-C++ 11 (он помечен как таковой). Вы правы, C++ 11 сделал это официально безопасным для этого. – paxos1977

Смежные вопросы