Язык программирования C имеет в своей стандартной библиотеке набор функций, реализующих операции над строками (символьными и байтовыми строками) . Поддерживаются различные операции, такие как копирование, конкатенация , токенизация и поиск. Для символьных строк стандартная библиотека использует соглашение о том, что строки завершаются нулем : строка из n символов представляется как массив из n + 1 элементов, последний из которых представляет собой « NUL- символ» с числовым значением 0.
Единственная поддержка строк в самом языке программирования заключается в том, что компилятор преобразует строковые константы в кавычках в строки с нулевым завершением.
Строка определяется как непрерывная последовательность кодовых единиц , оканчивающаяся первой нулевой кодовой единицей (часто называемой нулевой кодовой единицей). [1] Это означает, что строка не может содержать нулевую кодовую единицу, поскольку первая увиденная единица обозначает конец строки. Длина строки — это количество кодовых единиц до нулевой кодовой единицы . [1] Память, занимаемая строкой, всегда на одну кодовую единицу больше ее длины, поскольку для хранения нулевого терминатора требуется пространство.
Обычно термин «строка» означает строку, кодовая единица которой имеет тип char
, который на всех современных машинах составляет ровно 8 бит. C90 определяет широкие строки [1] , в которых используется кодовая единица типа wchar_t
, которая на современных машинах имеет длину 16 или 32 бита. Это было предназначено для Unicode , но вместо этого все чаще используется UTF-8 в обычных строках для Unicode.
Строки передаются функциям путем передачи указателя на первую единицу кода. Поскольку char*
и wchar_t*
являются разными типами, функции, обрабатывающие широкие строки, отличаются от функций, обрабатывающих обычные строки, и имеют разные имена.
Строковые литералы ( "text"
в исходном коде C) преобразуются в массивы во время компиляции. [2] Результатом является массив кодовых единиц, содержащий все символы плюс завершающую нулевую кодовую единицу. В С90 L"text"
получается широкая струна. Строковый литерал может содержать нулевую единицу кода (один из способов — поместить ее \0
в исходный код), но это приведет к тому, что строка закончится в этой точке. Остальная часть литерала будет помещена в память (с добавлением еще одной нулевой кодовой единицы в конце), но невозможно узнать, что эти кодовые единицы были переведены из строкового литерала, поэтому такой исходный код не является строковым литералом. [3]
Каждая строка заканчивается при первом появлении нулевой кодовой единицы соответствующего типа ( char
или wchar_t
). Следовательно, строка байтов ( char*
) может содержать символы, отличные от NUL , в ASCII или любом расширении ASCII , но не может содержать символы в таких кодировках, как UTF-16 (даже если 16-битная кодовая единица может быть ненулевой, ее старший или младший байт может быть нуль). Кодировки, которые можно хранить в широких строках, определяются шириной wchar_t
. В большинстве реализаций wchar_t
это не менее 16 бит, поэтому можно хранить все 16-битные кодировки, такие как UCS-2 . Если 32-бит, то можно хранить wchar_t
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, что приводит к тому, что многие [ who? ] полагать, что эти функции как-то не работают для UTF-8 . Фактически, все длины определяются как байты, и это верно для всех реализаций, и эти функции работают как с UTF-8, так и с однобайтовыми кодировками. Документация BSD была исправлена, чтобы прояснить это, но в документации POSIX, Linux и Windows по-прежнему используется «символ» во многих местах, где правильным термином является «байт» или «wchar_t».
Функции обработки буферов памяти могут обрабатывать последовательности байтов, которые включают в себя нулевые байты как часть данных. Имена этих функций обычно начинаются с mem
, в отличие от str
префикса.
Большинство функций, работающих со строками C, объявляются в string.h
заголовке ( cstring
в C++), тогда как функции, работающие со строками C, объявляются в заголовке wchar.h
( cwchar
в C++). Эти заголовки также содержат объявления функций, используемых для работы с буферами памяти; Таким образом, это название является неправильным.
Функции, объявленные в, string.h
чрезвычайно популярны, поскольку, как часть стандартной библиотеки C , они гарантированно работают на любой платформе, поддерживающей C. Однако с этими функциями существуют некоторые проблемы безопасности, такие как потенциальное переполнение буфера при неосторожном и правильном использовании. , в результате чего программисты отдают предпочтение более безопасным и, возможно, менее переносимым вариантам, из которых некоторые популярные перечислены ниже. Некоторые из этих функций также нарушают константную корректность , принимая const
указатель на строку и возвращая неуказатель const
внутри строки. Чтобы исправить это, в версии стандартной библиотеки C++ некоторые из них были разделены на две перегруженные функции .
Все эти функции требуютmbstate_tобъект, первоначально находившийся в статической памяти (что делает функции небезопасными для потоков), а в более поздних дополнениях должен поддерживать вызывающий объект. Первоначально это было предназначено для отслеживания состояний переключения вмбкодировки, но современные, такие как UTF-8, в этом не нуждаются. Однако эти функции были разработаны исходя из предположения, чтоТуалеткодирование не является кодированием переменной ширины и поэтому предназначено для работы ровно с однимwchar_tза раз, передавая его по значению, а не используя строковый указатель. Поскольку UTF-16 является кодировкой переменной ширины,mbstate_tбыл повторно использован для отслеживания суррогатных пар в широкой кодировке, хотя вызывающий объект все равно должен обнаружить и вызватьmbtowcдважды для одного символа. [80] [81] [82] Более поздние дополнения к стандарту признают, что единственное, что интересует программистов преобразования, — это преобразование между UTF-8 и UTF-16, и прямо обеспечивают это.
Стандартная библиотека C содержит несколько функций для числовых преобразований. Функции, работающие со строками байтов, определены в stdlib.h
заголовке ( cstdlib
header в C++). Функции, работающие с широкими строками, определены в wchar.h
заголовке ( cwchar
header в C++).
Функции strchr
, bsearch
, strpbrk
, strrchr
, и их широкие аналоги не являются константно-корректными , поскольку они принимают strstr
указатель на строку и возвращают неуказатель внутри строки. Это было исправлено в C23 . [95]memchr
const
const
Кроме того, начиная с Нормативной поправки 1 (C95), atoxx
функции считаются включенными в состав strtoxxx
функций, по этой причине ни C95, ни какой-либо более поздний стандарт не предоставляет версии этих функций с расширенными символами. Аргументом против atoxx
является то, что они не делают различия между ошибкой и 0
. [96]
Несмотря на общепризнанную необходимость замены strcat
[22] и strcpy
[18] функциями, не допускающими переполнения буфера, общепринятого стандарта не возникло. Частично это связано с ошибочным убеждением многих программистов на C, что strncat
и strncpy
имеют желаемое поведение; однако ни одна из функций не была разработана для этого (они предназначались для управления строковыми буферами фиксированного размера с заполненными нулями форматами данных, менее часто используемыми в современном программном обеспечении), а поведение и аргументы неинтуитивны и часто написаны неправильно даже экспертами. программисты. [108]
Наиболее популярной заменой [a] являются функции strlcat
и strlcpy
, которые появились в OpenBSD 2.4 в декабре 1998 года. [108] Эти функции всегда записывают один NUL в целевой буфер, усекая результат при необходимости и возвращая размер буфера, который будет Это необходимо, что позволяет обнаружить усечение и предоставляет размер для создания нового буфера, который не будет усекаться. Их критиковали за то, что они якобы неэффективны, [111] поощряют использование строк C (вместо какой-то более совершенной альтернативной формы строк), [112] [113] и сокрывают другие потенциальные ошибки. [114] [115] Следовательно, в течение многих лет они не были включены в библиотеку GNU C (используемую программным обеспечением в Linux), хотя это и изменилось. В FAQ по glibc Wiki о включении strlc{py|at} отмечается, что начиная с glibc 2.3.8 код был зафиксирован [116] и поэтому добавлен. [117] В объявлении о доступности glibc 2.38 говорилось, что эти функции «ожидается, будут добавлены в будущую версию POSIX». (Отслеживание дефектов Austin Group, ID 986, отслеживало некоторые дискуссии о таких планах для POSIX.) Несмотря на то, что в glibc не была добавлена поддержка, strlcat и strlcpy были реализованы в ряде других библиотек C, включая библиотеки для OpenBSD, FreeBSD , NetBSD , Solaris , OS X и QNX , а также в альтернативных библиотеках C для Linux, таких как libbsd, представленная в 2008 году, [118] и musl , представленная в 2011 году. [119] [120] Отсутствие поддержки библиотеки GNU C имело Это не помешало различным авторам программного обеспечения использовать его и включать замену, среди прочего, в SDL , GLib , ffmpeg , rsync и даже внутри ядра Linux . Доступны реализации этих функций с открытым исходным кодом. [121] [122]
Иногда используются memcpy
[53] или memmove
[55]strcpy
, поскольку они могут быть более эффективными, чем отсутствие повторной проверки NUL (это менее верно для современных процессоров). Поскольку в качестве параметра им требуется длина буфера, правильная установка этого параметра может избежать переполнения буфера.
В рамках своего жизненного цикла разработки безопасности в 2004 году Microsoft представила семейство «безопасных» функций, включая strcpy_s
и strcat_s
(наряду со многими другими). [123] Эти функции были стандартизированы с некоторыми незначительными изменениями как часть дополнительного C11 (Приложение K) , предложенного стандартом ISO/IEC WDTR 24731. Эти функции выполняют различные проверки, включая проверку того, не является ли строка слишком длинной для размещения в буфере. Если проверки не пройдены, вызывается определяемая пользователем функция «обработчик ограничений времени выполнения», [124] которая обычно прерывает выполнение программы. [125] [126] Некоторые функции выполняют разрушительные операции перед вызовом обработчика ограничений времени выполнения; например, strcat_s
устанавливает в качестве места назначения пустую строку, [127] что может затруднить восстановление после ошибок или их отладку. Эти функции вызвали серьезную критику, поскольку изначально они были реализованы только в Windows, и в то же время Microsoft Visual C++ начал выдавать предупреждающие сообщения, предлагающие использовать эти функции вместо стандартных. Некоторые полагают, что это попытка Microsoft привязать разработчиков к своей платформе. [128] Хотя реализации этих функций с открытым исходным кодом доступны, они отсутствуют в обычных библиотеках C для Unix. [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] принят большинством современных операционных систем и многими автономными программными пакетами [...].
Заметным исключением является стандартная библиотека C GNU, glibc, чей сопровождающий упорно отказывается включать эти улучшенные API, называя их «ужасно неэффективной чушью BSD», несмотря на предварительные доказательства того, что в большинстве случаев они быстрее, чем API, которые они заменяют.
Правильная обработка строк означает, что вы всегда знаете длину ваших строк и, следовательно, можете использовать memcpy (вместо strcpy).
{{cite web}}
: CS1 maint: местоположение ( ссылка ){{cite web}}
: CS1 maint: местоположение ( ссылка ){{cite web}}
: CS1 maint: местоположение ( ссылка )