Язык программирования C имеет набор функций, реализующих операции над строками (строками символов и строками байтов) в своей стандартной библиотеке . Поддерживаются различные операции, такие как копирование, конкатенация , токенизация и поиск. Для строк символов стандартная библиотека использует соглашение, что строки заканчиваются нулем : строка из n символов представляется как массив из n + 1 элементов, последний из которых является « символом NUL » с числовым значением 0.
Единственная поддержка строк в самом языке программирования заключается в том, что компилятор транслирует заключенные в кавычки строковые константы в строки с завершающим нулем.
Строка определяется как непрерывная последовательность кодовых единиц, завершающаяся первой нулевой кодовой единицей (часто называемой нулевой кодовой единицей). [1] Это означает, что строка не может содержать нулевую кодовую единицу, так как первая увиденная отмечает конец строки. Длина строки — это количество кодовых единиц до нулевой кодовой единицы. [1] Память, занимаемая строкой, всегда на одну кодовую единицу больше длины, так как для хранения нулевого терминатора требуется место.
Обычно термин строка означает строку, в которой кодовая единица имеет тип char
, который на всех современных машинах составляет ровно 8 бит. C90 определяет широкие строки [1] , которые используют кодовую единицу типа wchar_t
, которая на современных машинах составляет 16 или 32 бита. Это было предназначено для Unicode , но все чаще вместо этого в обычных строках для Unicode используется UTF-8 .
Строки передаются в функции путем передачи указателя на первую кодовую единицу. Поскольку char *
и wchar_t *
являются разными типами, функции, обрабатывающие широкие строки, отличаются от функций, обрабатывающих обычные строки, и имеют разные имена.
Строковые литералы ( "text"
в исходном коде C) преобразуются в массивы во время компиляции. [2] Результатом является массив кодовых единиц, содержащий все символы плюс конечный нулевой кодовый блок. В C90 L"text"
создается широкая строка. Строковый литерал может содержать нулевой кодовый блок (один из способов — поместить \0
в исходный код), но это приведет к тому, что строка закончится в этой точке. Остальная часть литерала будет помещена в память (с добавлением еще одного нулевого кодового блока в конец), но невозможно узнать, были ли эти кодовые единицы переведены из строкового литерала, поэтому такой исходный код не является строковым литералом. [3]
Каждая строка заканчивается первым вхождением нулевой кодовой единицы соответствующего вида ( char
или wchar_t
). Следовательно, байтовая строка ( char*
) может содержать ненулевые символы в ASCII или любом расширении ASCII , но не символы в кодировках, таких как UTF-16 (даже если 16-битная кодовая единица может быть ненулевой, ее старший или младший байт может быть нулевым). Кодировки, которые могут быть сохранены в широких строках, определяются шириной wchar_t
. В большинстве реализаций wchar_t
составляет не менее 16 бит, и поэтому могут быть сохранены все 16-битные кодировки, такие как UCS-2 . Если wchar_t
составляет 32 бита, то могут быть сохранены 32-битные кодировки, такие как UTF-32 . (Стандарт требует «тип, который содержит любой широкий символ», что в Windows больше не выполняется после перехода с UCS-2 на UTF-16. Это было признано дефектом в стандарте и исправлено в C++.) [4] C++11 и C11 добавляют два типа с явной шириной char16_t
и char32_t
. [5]
Кодировки переменной ширины могут использоваться как в байтовых строках, так и в широких строках. Длина строки и смещения измеряются в байтах или wchar_t
, а не в «символах», что может сбивать с толку начинающих программистов. UTF-8 и Shift JIS часто используются в байтовых строках C, в то время как UTF-16 часто используется в широких строках C, когда wchar_t
составляет 16 бит. Усечение строк с символами переменной ширины с помощью таких функций strncpy
может привести к появлению недопустимых последовательностей в конце строки. Это может быть небезопасно, если усеченные части интерпретируются кодом, который предполагает, что входные данные допустимы.
Поддержка литералов Unicode, таких как (UTF-8) или (UTF-16 или UTF-32, в зависимости от ), определяется реализацией [6] и может потребовать, чтобы исходный код был в той же кодировке, особенно для случаев, когда компиляторы могут просто копировать то, что находится между кавычками. Некоторые компиляторы или редакторы потребуют ввода всех не-ASCII символов в виде последовательностей для каждого байта UTF-8 и/или для каждого слова UTF-16. Начиная с C11 (и C++11), доступен новый префикс литерала, который гарантирует UTF-8 для литерала строки байтов, как в . [7] Начиная с C++20 и C23 , был добавлен тип, предназначенный для хранения символов UTF-8, а типы префиксных символьных и строковых литералов u8 были изменены на и соответственно.char foo[512] = "φωωβαρ";
wchar_t foo[512] = L"φωωβαρ";
wchar_t
char
\xNN
\uNNNN
u8
char foo[512] = u8"φωωβαρ";
char8_t
char8_t
char8_t[]
В исторической документации термин «символ» часто использовался вместо «байта» для строк C, что приводит многих [ кто? ] к мысли, что эти функции каким-то образом не работают для UTF-8 . Фактически, все длины определяются как в байтах, и это верно во всех реализациях, и эти функции работают как с UTF-8, так и с однобайтовыми кодировками. Документация BSD была исправлена, чтобы прояснить это, но документация POSIX, Linux и Windows по-прежнему использует «символ» во многих местах, где «байт» или «wchar_t» являются правильным термином.
Функции для обработки буферов памяти могут обрабатывать последовательности байтов, включающие нулевой байт как часть данных. Имена этих функций обычно начинаются с mem
, в противоположность префиксу str
.
Большинство функций, работающих со строками C, объявляются в string.h
заголовке ( cstring
в C++), в то время как функции, работающие со строками C wide, объявляются в wchar.h
заголовке ( cwchar
в C++). Эти заголовки также содержат объявления функций, используемых для обработки буферов памяти; таким образом, это название является не совсем правильным.
Функции, объявленные в , string.h
чрезвычайно популярны, поскольку, как часть стандартной библиотеки C , они гарантированно работают на любой платформе, которая поддерживает C. Однако с этими функциями существуют некоторые проблемы безопасности, такие как потенциальное переполнение буфера при неаккуратном и неправильном использовании, заставляющие программистов предпочитать более безопасные и, возможно, менее переносимые варианты, из которых некоторые популярные перечислены ниже. Некоторые из этих функций также нарушают константную корректность , принимая const
указатель на строку и возвращая не const
указатель внутри строки. Чтобы исправить это, некоторые из них были разделены на две перегруженные функции в версии стандартной библиотеки C++.
Все эти функции требуютmbstate_tобъект, изначально в статической памяти (что делает функции не потокобезопасными) и в более поздних дополнениях вызывающий должен поддерживать. Первоначально это было предназначено для отслеживания состояний сдвига вмбкодировки, но современные, такие как UTF-8, не нуждаются в этом. Однако эти функции были разработаны на основе предположения, чтоТуалеткодировка не является кодировкой переменной ширины и, таким образом, предназначена для работы только с однимwchar_tза раз, передавая его по значению, а не используя указатель на строку. Поскольку UTF-16 — это кодировка переменной ширины,mbstate_tбыл повторно использован для отслеживания суррогатных пар в широкой кодировке, хотя вызывающая сторона все равно должна обнаружить и вызватьмбтовкдважды для одного символа. [80] [81] [82] Более поздние дополнения к стандарту признают, что единственное преобразование, которое интересует программистов, — это преобразование между UTF-8 и UTF-16, и напрямую предоставляют это.
Стандартная библиотека C содержит несколько функций для числовых преобразований. Функции, которые работают с байтовыми строками, определены в stdlib.h
заголовке ( cstdlib
header в C++). Функции, которые работают с широкими строками, определены в wchar.h
заголовке ( cwchar
header в C++).
Функции strchr
, bsearch
, strpbrk
, strrchr
, strstr
, memchr
и их широкие аналоги не являются const-correct , поскольку они принимают const
указатель на строку и возвращают не- const
указатель внутри строки. Это было исправлено в C23 . [95]
Также, начиная с Нормативной поправки 1 (C95), atoxx
функции считаются включенными в strtoxxx
функции, по этой причине ни C95, ни любой более поздний стандарт не предоставляют широкосимвольные версии этих функций. Аргумент против atoxx
заключается в том, что они не различают ошибку и 0
. [96]
Несмотря на общепризнанную необходимость замены strcat
[22] и strcpy
[18] функциями, которые не допускают переполнения буфера, общепринятого стандарта не появилось. Это отчасти связано с ошибочным убеждением многих программистов на языке C, что strncat
и strncpy
имеют желаемое поведение; однако ни одна из функций не была разработана для этого (они были предназначены для манипулирования буферами строк фиксированного размера с нулевым дополнением, формат данных, который реже используется в современном программном обеспечении), а поведение и аргументы неинтуитивны и часто пишутся неправильно даже опытными программистами. [108]
Наиболее популярной [a] заменой являются функции strlcat
[111] и strlcpy
[112] , которые появились в OpenBSD 2.4 в декабре 1998 года. [108] Эти функции всегда записывают один NUL в буфер назначения, при необходимости усекая результат, и возвращают размер буфера, который потребуется, что позволяет обнаружить усечение и предоставляет размер для создания нового буфера, который не будет усекаться. Долгое время они не были включены в библиотеку GNU C (используемую программным обеспечением в Linux) на основании того, что они якобы неэффективны, [113] поощряют использование строк C (вместо какой-либо превосходной альтернативной формы строки), [114] [115] и скрывают другие потенциальные ошибки. [116] [117] Даже несмотря на то, что glibc не добавила поддержку, strlcat и strlcpy были реализованы в ряде других библиотек C, включая библиотеки для OpenBSD, FreeBSD , NetBSD , Solaris , OS X и QNX , а также в альтернативных библиотеках C для Linux, таких как libbsd, представленная в 2008 году, [118] и musl , представленная в 2011 году, [119] [120] , а исходный код был добавлен непосредственно в другие проекты, такие как SDL , GLib , ffmpeg , rsync и даже внутри ядра Linux . Это изменилось в 2024 году, в часто задаваемых вопросах по glibc отмечается, что с версии glibc 2.38 код был зафиксирован [121] и, таким образом, добавлен. [122] Эти функции были стандартизированы как часть POSIX.1-2024, [123] Austin Group Defect Tracker ID 986 отслеживал некоторые обсуждения таких планов для POSIX.
Иногда используются memcpy
[53] или memmove
[55]strcpy
, так как они могут быть более эффективными, чем те, которые не проверяют повторно NUL (это менее верно для современных процессоров). Поскольку им нужна длина буфера в качестве параметра, правильная настройка этого параметра может избежать переполнения буфера.
В рамках своего жизненного цикла разработки безопасности 2004 года Microsoft представила семейство «безопасных» функций, включая strcpy_s
и strcat_s
(наряду со многими другими). [124] Эти функции были стандартизированы с некоторыми незначительными изменениями как часть необязательного C11 (Приложение K), предложенного ISO/IEC WDTR 24731. [125] Эти функции выполняют различные проверки, включая проверку того, является ли строка слишком длинной для помещения в буфер. Если проверки не пройдены, вызывается указанная пользователем функция «обработчика ограничений времени выполнения», [126] которая обычно прерывает программу. [127] [128] Эти функции вызвали значительную критику, поскольку изначально они были реализованы только в Windows, и в то же время Microsoft Visual C++ начал выдавать предупреждающие сообщения, предлагающие использовать эти функции вместо стандартных. Некоторые предполагали, что это попытка Microsoft запереть разработчиков на своей платформе. [129] Опыт работы с этими функциями показал существенные проблемы с их принятием и ошибками в использовании, поэтому предлагается удалить Приложение K для следующей редакции стандарта C. [130] Использование memset_s
было предложено как способ избежать нежелательных оптимизаций компилятора. [131] [132]
strlcpy
, по сравнению с 38 644 использованиями strcpy_s
(и 15 286 150 использованиями strcpy
). [ необходима цитата ]{{cite web}}
: CS1 maint: местоположение ( ссылка ){{cite web}}
: CS1 maint: местоположение ( ссылка ){{cite web}}
: CS1 maint: местоположение ( ссылка ){{cite web}}
: CS1 maint: местоположение ( ссылка )API [strlcpy и strlcat] принят большинством современных операционных систем и многими автономными программными пакетами [...]. Заметным исключением является стандартная библиотека GNU C, glibc, чей сопровождающий упорно отказывается включать эти улучшенные API, называя их "ужасно неэффективным дерьмом BSD", несмотря на предыдущие доказательства того, что в большинстве случаев они быстрее, чем API, которые они заменяют.
Правильная обработка строк означает, что вы всегда знаете длину своих строк, и поэтому вы можете использовать memcpy (вместо strcpy).
{{cite web}}
: CS1 maint: местоположение ( ссылка ){{cite web}}
: CS1 maint: местоположение ( ссылка )