В программировании , строка с нулевым завершением представляет собой строку символов, хранящуюся в виде массива , содержащего символы и завершающуюся нулевым символом (символом с внутренним значением ноль, называемым в этой статье «NUL», не то же самое, что глиф ноль). Альтернативные названия — C string , что относится к языку программирования C и ASCIIZ [1] (хотя C может использовать кодировки, отличные от ASCII ).
Длина строки находится путем поиска (первого) NUL. Это может быть медленно, так как занимает O( n ) ( линейное время ) относительно длины строки. Это также означает, что строка не может содержать NUL (в памяти есть NUL, но он находится после последнего символа, а не в строке).
Строки с нулевым завершением были созданы директивой .ASCIZ
языков ассемблера PDP-11 и директивой языка макроассемблера MACRO-10 для PDP-10 . Они появились еще до разработки языка программирования C, но часто использовались и другие формы строк.ASCIZ
В то время, когда разрабатывался язык C (и языки, от которых он произошел), память была крайне ограничена, поэтому использование только одного байта служебных данных для хранения длины строки было привлекательным. Единственная популярная альтернатива в то время, обычно называемая «строкой Паскаля» (более современный термин — « префикс длины »), использовала ведущий байт для хранения длины строки. Это позволяло строке содержать NUL и делало нахождение длины требующим только одного доступа к памяти ( время O(1) (константа) ), но ограничивало длину строки 255 символами. Разработчик C Деннис Ритчи решил следовать соглашению о нулевом завершении, чтобы избежать ограничения на длину строки, и потому что подсчет казался, по его опыту, менее удобным, чем использование терминатора. [2] [3]
Это оказало некоторое влияние на разработку набора инструкций ЦП . Некоторые ЦП в 1970-х и 1980-х годах, такие как Zilog Z80 и DEC VAX , имели специальные инструкции для обработки строк с префиксом длины. Однако по мере того, как строки с нулевым завершением набирали популярность, проектировщики ЦП начали принимать их во внимание, как это видно, например, из решения IBM добавить инструкции «Logical String Assist» в ES/9000 520 в 1992 году и векторные строковые инструкции в IBM z13 в 2015 году. [4]
Разработчик FreeBSD Пол-Хеннинг Камп в своей статье в ACM Queue назвал победу строк с завершающим нулем над строками длиной в 2 байта (а не в один байт) «самой дорогой ошибкой в один байт» из когда-либо существовавших. [5]
Несмотря на простоту реализации, это представление подвержено ошибкам и проблемам с производительностью.
Нулевое завершение исторически создавало проблемы безопасности . [6] NUL, вставленный в середину строки, неожиданно ее обрезает. [7] Распространенной ошибкой было не выделять дополнительное пространство для NUL, поэтому он записывался в смежную память. Другой ошибкой было вообще не записывать NUL, что часто не обнаруживалось во время тестирования, поскольку блок памяти уже содержал нули. Из-за затрат на определение длины многие программы не утруждали себя копированием строки в буфер фиксированного размера , что приводило к переполнению буфера, если она была слишком длинной.
Невозможность сохранения нуля требует, чтобы текстовые и двоичные данные хранились отдельно и обрабатывались разными функциями (причем последние требуют указания длины данных). Это может привести к избыточности кода и ошибкам при использовании неправильной функции.
Проблемы со скоростью нахождения длины обычно можно смягчить, объединив ее с другой операцией, которая в любом случае является O( n ), например, в strlcpy
. Однако это не всегда приводит к интуитивно понятному API .
Строки с нулевым завершением требуют, чтобы кодировка нигде не использовала нулевой байт (0x00); поэтому невозможно сохранить все возможные строки ASCII или UTF-8 . [8] [9] [10] Однако обычно подмножество ASCII или UTF-8 — каждый символ, кроме NUL — хранится в строках с нулевым завершением. Некоторые системы используют « модифицированный UTF-8 », который кодирует NUL как два ненулевых байта (0xC0, 0x80) и, таким образом, позволяет сохранять все возможные строки. Это не допускается стандартом UTF-8, поскольку это слишком длинное кодирование , и это рассматривается как риск безопасности. Вместо этого в качестве конца строки может использоваться какой-то другой байт, например 0xFE или 0xFF, которые не используются в UTF-8.
UTF-16 использует 2-байтовые целые числа, и поскольку любой из байтов может быть равен нулю (и фактически любой другой байт равен нулю при представлении текста ASCII), не может быть сохранен в строке байтов с завершающим нулем. Однако некоторые языки реализуют строку из 16-битных символов UTF-16 , завершаемую 16-битным NUL (0x0000).
Было сделано много попыток сделать обработку строк C менее подверженной ошибкам. Одна стратегия заключается в добавлении более безопасных функций, таких как strdup
и strlcpy
, при этом исключая использование небезопасных функций, таких как gets
. Другая стратегия заключается в добавлении объектно-ориентированной оболочки вокруг строк C, чтобы можно было выполнять только безопасные вызовы. Однако в любом случае можно вызывать небезопасные функции.
Большинство современных библиотек заменяют строки C структурой, содержащей 32-битное или большее значение длины (гораздо больше, чем когда-либо рассматривалось для строк с префиксом длины), и часто добавляют еще один указатель, счетчик ссылок и даже NUL для ускорения обратного преобразования в строку C. Память теперь намного больше, так что если добавление 3 (или 16, или более) байтов к каждой строке является реальной проблемой, программному обеспечению придется иметь дело с таким количеством маленьких строк, что какой-то другой метод хранения сэкономит еще больше памяти (например, может быть так много дубликатов, что хэш-таблица будет использовать меньше памяти). Примерами являются C++ Standard Template Library std::string
, Qt QString
, MFC CString
и реализация на основе C CFString
от Core Foundation , а также ее родственный Objective-CNSString
от Foundation , обе от Apple. Более сложные структуры также могут использоваться для хранения строк, таких как rope .