Menu

Функции для строк в C: strlen, strcpy, strcat, strcmp и другие

Практический обзор <string.h>: измерение через strlen, копирование через strcpy и strncpy, склейка через strcat, сравнение через strcmp, поиск через strchr и strstr — и как рассчитать размеры буферов, чтобы ничего не переполнилось.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Каждая функция из <string.h> — это небольшой цикл по массиву char, останавливающийся на завершающем нуле. Как только это понятно, библиотека перестаёт выглядеть списком загадочных имён и становится горсткой циклов, которые вы написали бы сами. Здесь разобраны те, что выполняют почти всю реальную работу, и правила расчёта размеров, не дающие им писать за конец ваших буферов.

Всему на этой странице нужен один заголовок:

#include <string.h>

strlen: сколько символов

strlen считает байты до завершающего нуля, не включая его. Возвращает size_t, поэтому печатайте результат через %zu.

strlen даёт 6, sizeof32. Первое — это текст, второе — хранилище; почему это различие так важно, см. в статье строки.

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.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ