Всегда ли строки C заканчиваются нулем или это зависит от платформы?

13

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

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

Позвольте мне быть более конкретным, хотя для моего случая. Я внедряю систему, которая принимает и обрабатывает команды через последовательный порт. Могу ли я сохранить код обработки моей команды таким же, а затем ожидать, что все строковые объекты, созданные в ОСРВ (которая содержит команды), будут все завершены с нулевым значением? Или все будет иначе в зависимости от ОС?

Обновить

После того, как мне посоветовали взглянуть на этот вопрос, я решил, что он не совсем отвечает на мой вопрос. Сам вопрос заключается в том, следует ли всегда указывать длину строки, которая полностью отличается от того, что я спрашиваю, и хотя некоторые ответы содержат полезную информацию, они не совсем то, что я ищу. Ответы там, кажется, дают причины, почему или почему бы не завершить строку нулевым символом. Разница с тем, что я спрашиваю, заключается в том, могу ли я более или менее ожидать, что врожденные строки разных платформ завершат свои собственные строки с нулем, без необходимости выходить и пробовать каждую платформу, если это имеет смысл.

шпионить
источник
3
Я не использовал C долгое время, но я не могу вспомнить время, когда я столкнулся с реализацией, в которой не использовались строки, заканчивающиеся на NULL. Это часть стандартного C, если я правильно помню (как я уже говорил, это было давно ...)
MetalMikester
1
Я не специалист по C, но насколько я знаю, все строки в C являются массивами char с нулевым символом в конце. Вы можете создать свой собственный строковый тип, но вам придется самостоятельно реализовать все функции работы со строками.
Мачадо
1
@MetalMikester Вы думаете, что эту информацию можно найти в стандартной спецификации C?
Снуп
3
@ Snoopy Скорее всего, да. Но на самом деле, когда речь идет о строках в C, они представляют собой просто массив символов, заканчивающихся на NULL, и это все, если только вы не используете какую-то нестандартную библиотеку строк, но об этом мы все равно не говорим. Я сомневаюсь, что вы найдете платформу, которая не уважает это, особенно с одной из сильных сторон C, которая является мобильностью.
MetalMikester

Ответы:

42

Вещи, которые называются «C-строками», будут оканчиваться нулем на любой платформе. Вот как стандартные функции библиотеки C определяют конец строки.

В языке C ничто не мешает иметь массив символов, который не заканчивается на ноль. Однако вам придется использовать какой-то другой метод, чтобы избежать запуска строки.

Саймон Б
источник
4
просто добавить дальше; обычно у вас есть целое число где-то, чтобы отслеживать длину строки, а затем вы получаете настраиваемую структуру данных, чтобы сделать это правильно, что-то вроде класса QString в Qt
Рудольф Олах
8
Пример: я работаю с программой на C, которая использует по крайней мере пять различных форматов строк: массивы с нулевым символом в charконце, charмассивы с длиной, закодированной в первом байте (обычно называемые «строками Паскаля»), wchar_tоснованные на обоих версиях выше, и charмассивы, которые объединяют оба метода: длина, закодированная в первом байте, и нулевой символ, заканчивающий строку.
Mark
4
@ Марк Взаимодействие с большим количеством сторонних компонентов / приложений или устаревший код?
Дэн возится с помощью Firelight
2
@DanNeely, все вышеперечисленное. Строки Pascal для взаимодействия с классическими MacOS, строки C для внутреннего использования и Windows, широкие строки для добавления поддержки Unicode и строки-бастарды, потому что кто-то пытался быть умным и создать строку, которая могла бы одновременно взаимодействовать с MacOS и Windows.
Mark
1
@ Марк ... и, конечно, никто не хочет тратить деньги на погашение технического долга, потому что классический MacOS давно мертв, а цепочки ублюдков - двойные кластерные неудачи каждый раз, когда к ним нужно прикоснуться. Мои симпатии.
Дэн
22

Определение завершающего символа зависит от компилятора для литералов и реализации стандартной библиотеки для строк в целом. Это не определяется операционной системой.

Соглашение об NULувольнении восходит к предстандартному С, и через 30 с лишним лет я не могу сказать, что столкнулся со средой, которая делает что-то еще. Это поведение было кодифицировано в C89 и продолжает оставаться частью стандарта языка C (ссылка на черновик C99):

  • Раздел 6.4.5 устанавливает стадию для NULстрок, определяемых как окончание , требуя NULдобавления к строковым литералам.
  • Раздел 7.1.1 приводит это к функциям в стандартной библиотеке, определяя строку как «непрерывную последовательность символов, оканчивающихся первым нулевым символом и включающим его».

Нет причин, по которым кто-то не мог бы написать функции, которые обрабатывают строки, оканчивающиеся каким-либо другим символом, но в большинстве случаев также нет причин нарушать установленный стандарт, если только ваша цель не дать программистам соответствия. :-)

Blrfl
источник
2
Одной из причин было бы избегать необходимости искать конец одной и той же строки снова и снова.
Paŭlo Ebermann
@ PaŭloEbermann Правильно. За счет необходимости передавать два значения вместо одного. Что немного утомительно, если вы просто передаете строковый литерал, как в printf("string: \"%s\"\n", "my cool string"). Единственный способ обойти в этом случае четыре параметра (кроме завершающего байта) - это определить строку, похожую std::stringна C ++, которая имеет свои проблемы и ограничения.
cmaster - восстановить
1
Раздел 6.4.5 не требует, чтобы строковый литерал заканчивался нулевым символом. Он явно отмечает « строка Символьный литерал не должно быть строка (см 7.1.1), так как нулевой символ может быть встроен в него \ 0 последовательности для экранирования. »
bzeaman
1
@bzeaman Сноска гласит, что вы можете создать строковый литерал, который не соответствует определению строки в 7.1.1, но в предложении, ссылающемся на него, написано, что компиляторы соответствуют друг NULдругу - определяйте их независимо от того, что: «На этапе 7 перевода байт или код значение ноль добавляется к каждой многобайтовой последовательности символов, полученной из строкового литерала или литералов. " Библиотека функций с помощью определения стоп 7.1.1 в на первом NULони находят и не будут знать , или ухода , что дополнительные символы существуют за ее пределами.
Blrfl
Я стою исправлено. Я искал различные термины, такие как «ноль», но пропустил 6.4.5.5, упоминая «нулевое значение».
bzeaman
3

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

В языке C нет строкового типа данных, но есть строковые литералы .

Если вы поместите строковый литерал в вашу программу, он обычно завершается NUL (но смотрите специальный случай, обсуждаемый в комментариях ниже). То есть, если вы поместите "foobar"в место, где const char *ожидается значение, компилятор выдаст foobar⊘на const / кодовый сегмент / раздел вашей программы, а значением выражения будет указатель на адрес, где он хранил fсимвол. (Примечание: я использую для обозначения байта NUL.)

Единственный другой смысл, в котором язык C имеет строки, состоит в том, что он имеет некоторые стандартные библиотечные подпрограммы, которые работают с NUL-символами. Эти библиотечные процедуры не будут существовать в чистой металлической среде, если вы сами не перенесете их.

Они просто код - ничем не отличается от кода, который вы сами пишете. Если вы не сломаете их при портировании, они будут делать то, что всегда делают (например, останавливаются на NUL.)

Соломон Медленный
источник
2
Re: «Если вы поместите строковый литерал в вашу программу, он всегда будет заканчиваться NUL»: Вы уверены в этом? Я вполне уверен, что (например) char foo[4] = "abcd";является допустимым способом создания ненулевого массива из четырех символов.
Руах
2
@ruakh, упс! это случай, который я не рассматривал. Я думал о строковом литерале, который появляется в месте, где ожидается char const * выражение . Я забыл, что инициализаторы C иногда могут подчиняться другим правилам.
Соломон Слоу
@ruakh Строковый литерал завершается NUL. Массив не.
Джеймсдлин
2
@ruakh у тебя есть char[4]. Это не строка, но она была инициализирована с одного
Caleth
2
@Caleth, «инициализируется из одного» - это не то, что должно происходить во время выполнения. Если мы добавим ключевое слово staticк примеру Ruakh, тогда компилятор может излучать не завершенный нуль «абвг» к инициализированному сегменту данных таким образом , чтобы переменный инициализируются загрузчиком программы. Итак, Руах был прав: есть, по крайней мере, один случай, когда появление строкового литерала в программе не требует, чтобы компилятор выдавал строку с NUL-символами в конце. (ps, я фактически скомпилировал пример с gcc 5.4.0, и компилятор не
Solomon Slow
2

Как уже упоминалось, нулевое завершение строк является соглашением Стандартной библиотеки Си. Вы можете работать со строками так, как пожелаете, если не собираетесь использовать стандартную библиотеку.

Это справедливо для любой операционной системы с компилятором 'C', а также вы можете писать программы на 'C', которые не работают под реальной операционной системой, как вы упоминаете в своем вопросе. Примером может служить контроллер для струйного принтера, который я разработал однажды. Во встроенных системах накладные расходы памяти операционной системы могут не требоваться.

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

Итак, что важнее в вашем приложении: размер и эффективность кода или совместимость с ОС или библиотекой? Еще одним соображением может быть ремонтопригодность. Чем дальше вы отклоняетесь от соглашения, тем труднее будет поддерживать кого-то еще.

Хью Бунту
источник
1

Другие обращались к вопросу о том, что в C строки - это в основном то, что вы из них делаете. Но в вашем вопросе, похоже, есть некоторая путаница с самим терминатором, и, с одной стороны, это может быть тем, о чем беспокоится кто-то из вас.

Строки C заканчиваются нулем. То есть они заканчиваются нулевым символом NUL. Они не заканчиваются нулевым указателем NULL, который представляет собой совершенно другой тип значения с совершенно другой целью.

NULгарантированно имеет целочисленное значение ноль. Внутри строки он также будет иметь размер основного типа символов, который обычно равен 1.

NULLвовсе не обязательно иметь целочисленный тип. NULLпредназначен для использования в контексте указателя, и обычно ожидается, что он будет иметь тип указателя, который не должен преобразовываться в символ или целое число, если ваш компилятор хорош. Несмотря на то, что определение NULLвключает в себя глиф 0, оно не обязательно будет иметь это значение [1], и если ваш компилятор не реализует константу как односимвольный #define(многие этого не делают, потому что на NULL самом деле не должно быть значимым в указатель контекста), поэтому расширенный код, как гарантируют, не будет фактически включать нулевое значение (даже при том, что это вводит в заблуждение нулевой глиф).

Если NULLнапечатано, он также вряд ли будет иметь размер 1 (или другой размер символа). Это может вызвать дополнительные проблемы, хотя фактические символьные константы по большей части не имеют размера символов.

Теперь большинство людей увидят это и подумают: «Нулевой указатель - это что-то иное, чем ноль-бит? Что за вздор?», Но подобные предположения безопасны только на распространенных платформах, таких как x86. Поскольку вы явно упомянули интерес к нацеливанию на другие платформы, вам необходимо принять во внимание эту проблему, поскольку вы явно отделили свой код от предположений о природе отношений между указателями и целыми числами.

Следовательно, хотя строки C заканчиваются нулем, они не заканчиваются NULL, а NUL(обычно пишутся '\0'). Код, который явно используется NULLв качестве ограничителя строки, будет работать на платформах с простой структурой адресов и даже будет компилироваться со многими компиляторами, но это абсолютно не правильно.


[1] фактическое значение нулевого указателя вставляется компилятором, когда он читает 0 токен в контексте, где он будет преобразован в тип указателя. Это не преобразование из целочисленного значения 0, и оно не гарантированно сохраняется, если используется что-либо, кроме самого токена 0, например, динамическое значение из переменной; преобразование также необратимо, и нулевой указатель не должен давать значение 0 при преобразовании в целое число.

Leushenko
источник
Отличный момент. Я отправил правку, чтобы прояснить ситуацию.
Монти Хардер
NULmsgstr " гарантированно иметь целочисленное значение ноль." -> C не определяет NUL. Вместо этого C определяет, что строки имеют окончательный нулевой символ , байт со всеми битами, установленными в 0.
chux - Восстановить Монику
1

Я использовал строку в C, это означает, что символы с нулевым окончанием называется Strings.

Это не будет иметь никаких проблем при использовании в baremetal или в любых операционных системах, таких как Windows, Linux, RTOS: (FreeRTO, OSE).

Во встроенном мире нулевое окончание на самом деле помогает больше символизировать символ в виде строки.

Я использовал такие строки в Си во многих критических системах безопасности.

Вам может быть интересно, что такое строка на самом деле в C?

Строки в стиле C, которые являются массивами, также есть строковые литералы, такие как «this». В действительности оба этих типа строк представляют собой просто наборы символов, сидящих в памяти рядом друг с другом.

Всякий раз, когда вы пишете строку, заключенную в двойные кавычки, C автоматически создает для нас массив символов, содержащий эту строку, оканчивающуюся символом \ 0.

Например, вы можете объявить и определить массив символов и инициализировать его строковой константой:

char string[] = "Hello cruel world!";

Простой ответ: вам не нужно беспокоиться об использовании символов с нулевым завершением, эта работа не зависит от какой-либо платформы.

висячий указатель
источник
Спасибо, не знал, что при объявлении с двойными кавычками, NULавтоматически добавляется.
Снуп
1

Как уже говорили другие, нулевое завершение в значительной степени универсально для стандартного C. Но (как уже отмечали другие) не 100%. Для (другого) примера операционная система VMS обычно использовала так называемые «строковые дескрипторы» http://h41379.www4.hpe.com/commercial/c/docs/5492p012.html, доступ к которым осуществляется в C с помощью #include <descrip.h. >

На уровне приложения может использоваться нулевое завершение или нет, однако разработчик считает это целесообразным. Но для низкоуровневого VMS абсолютно необходимы дескрипторы, которые вообще не используют нулевое завершение (подробности см. Выше). Это в основном так, что все языки (C, ассемблер и т. Д.), Которые напрямую используют внутренние компоненты VMS, могут иметь общий интерфейс с ними.

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

Джон Форкош
источник
Сегодня 0 прекращение на самом деле довольно необычно. В C ++ std :: string нет, в Java String нет, в Objective-C NSString нет, в Swift String нет - в результате каждая языковая библиотека поддерживает строки с кодами NUL внутри строки (что невозможно с C Строки по понятным причинам).
gnasher729
@ gnasher729 Я изменил «... в значительной степени универсальный» на «в значительной степени универсальный для стандартного C», который, я надеюсь, устраняет любую двусмысленность и остается верным сегодня (и именно это я имел в виду, в соответствии с предметом и вопросом OP).
Джон Форкош
0

По моему опыту встраиваемых систем, критичных для безопасности и систем реального времени, нередки случаи, когда используются соглашения о строках C и PASCAL, т. Е. В качестве первого символа указывается длина строки (которая ограничивает длину 255), а для завершения строка с как минимум одним 0x00, ( NUL), который уменьшает полезный размер до 254.

Одна из причин этого заключается в том, чтобы знать, сколько данных вы ожидаете после получения первого байта, а другая заключается в том, что в таких системах, где это возможно, избегают динамических размеров буфера - выделение фиксированного размера буфера 256 быстрее и безопаснее (нет нужно проверить, mallocне удалось ли ). Другое заключается в том, что другие системы, с которыми вы общаетесь, могут быть не написаны в ANSI-C.

В любой встроенной работе важно создать и поддерживать документ управления интерфейсом (IDC), который определяет все ваши коммуникационные структуры, включая строковые форматы, порядковый номер, целочисленные размеры и т. Д., Как можно скорее (в идеале до запуска ), и это должна быть ваша и все команды, священная книга при написании системы - если кто-то хочет представить новую структуру или формат, она должна быть сначала задокументирована там, и каждый, кто может быть затронут, проинформирован, возможно, с возможностью наложить вето на изменение ,

Стив Барнс
источник