На что обратить внимание при преобразовании std::string в char* для функции C?

Я прочитал много сообщений, в которых задавался вопрос о том, как преобразовать С++ std::string или const std::string& в char*, чтобы передать его функции C, и, похоже, в отношении этого есть несколько предостережений. Нужно остерегаться непрерывности строки и многих других вещей. Дело в том, что я никогда не понимал всех моментов, о которых нужно знать, и почему?

Мне интересно, может ли кто-нибудь суммировать предостережения и недостатки, связанные с преобразованием из std::string в char*, которое необходимо для перехода к функции C?

Это когда std::string является ссылкой const и когда это просто неконстантная ссылка, и когда функция C изменит char* и когда она не изменит его.


person Tony The Lion    schedule 12.04.2011    source источник
comment
Возможно, это поможет: programmedlessons.org/AssemblyTutorial/Chapter-20/ass20_2.html и en.wikipedia.org/wiki/C_string   -  person Cray    schedule 12.04.2011
comment
Это на самом деле очень хороший вопрос, и он вызвал довольно хорошие ответы. Должны ли мы сделать это часто задаваемыми вопросами?   -  person sbi    schedule 12.04.2011
comment
@sbi: Я думаю, что это, вероятно, хорошая идея, это не редкость, и я уверен, что я не первый и последний, кто будет удивляться или смущаться по этому поводу.   -  person Tony The Lion    schedule 12.04.2011


Ответы (5)


Во-первых, константная ссылка или значение ничего не меняет.

Затем вы должны рассмотреть, что ожидает функция. Есть разные вещи, которые функция может делать с char* или char const* --- например, исходные версии memcpy использовали эти типы, и возможно, что такой код все еще существует. Будем надеяться, что это редкость, и в дальнейшем я буду предполагать, что char* в функции C относятся к '\0' завершающим строкам.

Если функция C принимает char const*, вы можете передать ей результаты std::string::c_str(); если это занимает char*, это зависит. Если он принимает char* просто потому, что он датируется до const днями C, и фактически ничего не изменяет, std::string::c_str(), за которым следует const_cast, является подходящим. Однако, если функция C использует char* в качестве выходного параметра, все становится сложнее. Я лично предпочитаю объявлять буфер char[], передавать его, а затем преобразовывать результаты в std::string, но все известные реализации std::string используют непрерывный буфер, и следующая версия стандарта потребует его, поэтому сначала правильно определите размер std::string (используя std::string::resize() , затем можно использовать передачу &s[0] и последующее изменение размера строки до полученной длины (определяемой с помощью strlen(s.c_str()), если необходимо).

Наконец (но это также проблема для программ C, использующих char[]), вы должны учитывать любые проблемы жизненного цикла. Большинство функций, принимающих char* или char const*, просто используют указатель и забывают о нем, но если функция где-то сохраняет указатель для последующего использования, строковый объект должен существовать как минимум столько же времени, и его размер не должен изменяться в течение этого периода. (Опять же, в таких случаях я предпочитаю использовать char[].)

person James Kanze    schedule 12.04.2011
comment
+1 Спасибо! Для случая, когда функция C принимает char* и модифицирует его, я использовал хак &s[0] в прошлом, но никогда не делал окончательного изменения размера впоследствии. Недавно я столкнулся со странной проблемой, когда ostringstream пытался использовать результирующую (неизмененную) строку s, и он молча отключил stringstream. Использование обычного char[] для функций C теперь также является моим предпочтительным методом, он проще и безопаснее. - person Matthew; 12.07.2012

В принципе, важны три момента:

  • Согласно все еще действующему стандарту, std::string на самом деле не гарантирует использование непрерывного хранилища (насколько я знаю, это связано с изменениями). Но на самом деле все текущие реализации, вероятно, так или иначе используют непрерывное хранилище. По этой причине c_str()data()) могут фактически создать копию строки внутри...

  • Указатель, возвращаемый c_str()data()), действителен только до тех пор, пока не вызывается неконстантный метод исходной строки. Это делает его использование неприемлемым, когда функция C привязана к указателю (в отличие от использования его только во время фактического вызова функции).

  • Если есть какая-либо вероятность того, что строка будет изменена, отбрасывание константности из c_str() не является хорошей идеей. Вы должны создать буфер с копией строки и передать его в функцию C. Если вы создаете буфер, не забудьте добавить нулевое завершение.

person Konrad Rudolph    schedule 12.04.2011
comment
c_str() должно быть постоянным временем, поэтому нельзя сделать копию. Но разрешено постоянно хранить отдельную копию, поэтому ваш второй пункт остается в силе. - person Sjoerd; 12.04.2011
comment
@Sjoerd, я не уверен; не могли бы вы указать мне на абзац, где это сказано в стандарте? §21.3.6 не имеет ограничений по сложности для функций. - person Konrad Rudolph; 12.04.2011
comment
@Sjoerd: я только что перепроверил стандарт и не могу найти требование времени выполнения для .c_str(), можете ли вы дать ссылку на то, где в стандарте .c_str() требуется операция с постоянным временем? - person David Rodríguez - dribeas; 12.04.2011
comment
Это упускает из виду очень важный аспект жизни IMO, который возвращает .c_str(). - person sbi; 12.04.2011

[Я бы добавил комментарий, но у меня недостаточно представителей для этого, поэтому извините за добавление (еще) еще одного ответа.]

Хотя стандарт current не гарантирует, что внутренний буфер std::string будет непрерывным, похоже, что практически все реализации используют непрерывные буферы. Кроме того, новый стандарт C++0x (который должен быть одобрен ISO) требует непрерывных внутренних буферов в std::string, и даже текущий стандарт C++03 требует возврата непрерывного буфера при вызове data() или &str[0] (хотя это не обязательно будет заканчиваться нулем). См. здесь для более подробной информации.

Это по-прежнему не делает запись в строку безопасной, поскольку стандарт не заставляет реализации фактически возвращать свой внутренний буфер при вызове data(), c_str() или оператора и им также не запрещено использовать оптимизации, такие как копирование при записи, что может еще больше усложнить ситуацию (похоже, что новый C++0x запретит копирование при записи). При этом, если вас не волнует максимальная переносимость, вы можете проверить свою целевую реализацию и посмотреть, что она на самом деле делает внутри. Насколько мне известно, Visual C++ 2008/2010 всегда возвращает реальный указатель внутреннего буфера и не выполняет копирование при записи (у него есть оптимизация малых строк, но это, вероятно, не проблема).

person Boaz Yaniv    schedule 12.04.2011
comment
Можете ли вы указать, почему &str[0] должно относиться к началу непрерывного буфера? - person MSalters; 12.04.2011
comment
На самом деле я впервые узнал об этом от Херба Саттера: ="nofollow noreferrer">herbsutter.com/2008/04/07/ Но, читая стандарт, я понимаю, что str[pos] эквивалентен (и должен быть) data()[pos], и там fore &str[0] эквивалентно &data()[0], которое, в свою очередь, эквивалентно data(). А сам data() (как и c_str()) должен указывать на непрерывный буфер. - person Boaz Yaniv; 12.04.2011
comment
Это странно - data() возвращает const char*, а operator[] возвращает неконстантное char&. - person MSalters; 12.04.2011
comment
@MSalters Вот что говорит стандарт C++03: const_reference operator[](size_type pos) const; reference operator[](size_type pos); 1 Возвращает: если pos ‹ size(), возвращает data()[pos]. В противном случае, если pos == size(), константная версия возвращает charT(). В противном случае поведение не определено. ----- Я предполагаю, что он не принимает во внимание константную правильность для неконстантной версии. - person Boaz Yaniv; 12.04.2011
comment
Ах да, несмежные сомнения были с интерфейсом итератора, а не с интерфейсом индекса LWG 530 - person MSalters; 12.04.2011
comment
Обновление: реализация оператора [](pos) в VC++ в основном просто возвращает internal_buffer[pos]. - person Boaz Yaniv; 12.04.2011

Когда функция C не изменяет строку после char*, вы можете использовать std::string::c_str() как для константных, так и для неконстантных экземпляров std::string. В идеале это будет const char*, но если это не так (из-за устаревшего API), вы можете на законных основаниях использовать const_cast. Но вы можете использовать указатель из c_str() только до тех пор, пока вы не изменяете строку!

Когда функция C изменяет строку после char*, ваш единственный безопасный и переносимый способ использования std::string — это самостоятельно скопировать ее во временный буфер (например, из c_str())! Убедитесь, что вы освободили временную память после этого — или используйте std::vector, который гарантированно имеет непрерывную память.

person ltjax    schedule 12.04.2011

  1. std:string может хранить ноль байтов. Это означает, что при передаче функции C она может быть преждевременно усечена, так как функции C остановятся на первом нулевом байте. Это может иметь последствия для безопасности, если вы попытаетесь использовать функцию C, например, для фильтрации или экранирования нежелательных символов.

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

  3. Никогда не используйте const_cast. goto доставляет меньше хлопот.

person Tometzky    schedule 12.04.2011
comment
@Tony Tometzky хотел подчеркнуть, что даже goto более безобидно, чем использование const_cast. Я не согласен. const_cast имеет свое место. Мне еще предстоит найти правильное использование goto (и я прочитал статью Кнута). - person Konrad Rudolph; 12.04.2011