Каждая функция из <string.h> — это небольшой цикл по массиву char, останавливающийся на завершающем нуле. Как только это понятно, библиотека перестаёт выглядеть списком загадочных имён и становится горсткой циклов, которые вы написали бы сами. Здесь разобраны те, что выполняют почти всю реальную работу, и правила расчёта размеров, не дающие им писать за конец ваших буферов.
Всему на этой странице нужен один заголовок:
#include <string.h>
strlen: сколько символов
strlen считает байты до завершающего нуля, не включая его. Возвращает size_t, поэтому печатайте результат через %zu.
strlen даёт 6, sizeof — 32. Первое — это текст, второе — хранилище; почему это различие так важно, см. в статье строки.
strlen при каждом вызове проходит всю строку, поэтому не ставьте его в условие цикла по одной и той же неизменной строке:
/* пересчитывает всю строку на каждой итерации */
for (size_t i = 0; i < strlen(s); i++) { ... }
/* посчитать один раз */
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) { ... }
strcpy и strncpy: копирование
strcpy(dst, src) копирует символы вместе с завершающим нулём. Он не знает размера dst, поэтому вызывающий обязан гарантировать, что в dst помещается минимум strlen(src) + 1 байт.
strncpy принимает максимальное количество байт, но у него есть знаменитая ловушка: если источник длиной не меньше n, копируется ровно n символов и никакого завершающего нуля. Всегда завершайте строку сами:
sizeof dst - 1 плюс явный завершающий ноль — тот шаблон, который стоит запомнить. (strncpy к тому же добивает короткий источник нулями до n байт, что для больших буферов лишняя работа: функция создавалась для записей фиксированной ширины, а не ради безопасности.)
strcat и strncat: склейка
strcat(dst, src) дописывает src в конец того, что уже лежит в dst. Приёмник должен вмещать обе строки плюс один завершающий ноль и обязан уже содержать корректную строку — дописывание в неинициализированную память есть неопределённое поведение.
Обратите внимание на char path[64] = "/home/ada";, а не char path[64]; — именно инициализатор делает массив корректной строкой для первого strcat.
strncat(dst, src, n) дописывает не более n символов и всегда добавляет завершающий ноль, поэтому n — это оставшееся место, а не полный размер буфера:
strncat(dst, src, sizeof dst - strlen(dst) - 1);
Повторные вызовы strcat каждый раз заново просматривают приёмник в поисках его конца. Сборка длинной строки в цикле таким способом квадратична; для чего-то объёмного ведите собственную позицию записи или используйте snprintf, разобранный в статье преобразование строк.
strcmp: сравнение
== сравнивает адреса, поэтому для двух разных массивов с одинаковым текстом он ложен. strcmp сравнивает символы и возвращает знак разности.
Три правила:
- Для проверки равенства пишите
strcmp(a, b) == 0. Записьif (strcmp(a, b))означает «если они различаются», что почти для всех читается наоборот. - Определён только знак. Не сравнивайте результат с
1или-1. - Порядок задаётся значениями байтов, поэтому в ASCII
"Zebra"идёт раньше"apple". Для сравнения без учёта регистра сначала приведите обе копии к нижнему регистру: широко известныйstrcasecmp— расширение POSIX, а не стандартный C.
strncmp(a, b, n) сравнивает только первые n символов, и это опрятный способ проверить префикс:
strchr и strstr: поиск
strchr(s, ch) находит первое вхождение символа, strrchr — последнее. strstr(haystack, needle) находит подстроку. Все три возвращают указатель внутрь исходной строки или NULL, если совпадения нет.
Вычитание возвращённого указателя из начала даёт индекс; его тип — ptrdiff_t, печатается через %td. Поскольку результат указывает внутрь исходного массива, at + 1 — это остаток строки вообще без копирования, очень распространённая идиома C.
Всегда проверяйте на NULL перед разыменованием. strchr(email, '@') + 1 на строке без @ вычисляет адрес от NULL, и программа перестаёт быть определённой.
Разделение строки
Функции split здесь нет, но strchr плюс завершающий ноль делают работу прямо на месте. В этом примере адрес электронной почты разрезается на пользователя и домен:
memcpy копирует точное число байтов, не заботясь о завершающих нулях, — именно то, что нужно, когда длина уже известна. Завершающий ноль затем пишется вручную.
Разобранный пример: приведение имени в порядок
Соберём обзор воедино — обрезка, сравнение, копирование и склейка:
Каждая запись ограничена собственным sizeof приёмника, а за каждым strncpy следует явный завершающий ноль. Именно эта дисциплина, применяемая последовательно, и делает строковый код на C безопасным.
Правила размеров, которые стоит держать в голове
strcpyтребует свободныхstrlen(src) + 1байт в приёмнике.strcatтребуетstrlen(dst) + strlen(src) + 1.- После
strncpyсами пишитеdst[n - 1] = '\0'. - Для
strncatсчётчик — это оставшееся место:sizeof dst - strlen(dst) - 1. - Проверяйте результат
strchrиstrstrнаNULLперед использованием. - Используйте
sizeof dstтолько там, гдеdst— настоящий массив. Внутри функции, принимающейchar *dst,sizeofдаст размер указателя: передавайте длину буфера отдельным параметром.
Часто задаваемые вопросы
Что возвращает strcmp в C?
Ноль, когда строки одинаковы, отрицательное значение, когда первая идёт раньше по порядку, и положительное, когда позже. Смысл имеет только знак — никогда не рассчитывайте, что вернётся -1 или 1. Нужная вам проверка почти всегда выглядит как if (strcmp(a, b) == 0).
Почему нельзя сравнивать строки через == в C?
a == b сравнивает два адреса, а не символы. Два отдельных массива со словом "cat" лежат по разным адресам, поэтому сравнение ложно, хотя текст совпадает. Используйте strcmp(a, b) == 0 из <string.h>.
Чем strcpy отличается от strncpy?
strcpy копирует до '\0' источника, не имея понятия о размере приёмника. strncpy останавливается максимум на n байтах, но если источник такой длины, завершающий ноль не копируется, поэтому вы обязаны сами написать dst[n - 1] = '\0';. Автоматически безопасна ни та ни другая: дисциплина размеров на вас.
Как найти подстроку в C?
strstr(haystack, needle) возвращает указатель на первое вхождение или NULL, если его нет. Поскольку указатель ведёт внутрь исходной строки, found - haystack даёт индекс. Для поиска одного символа используйте strchr.