Стандартные функции работы со строками

Урок знакомит с библиотекой string.h: функции длины, копирования, склейки и сравнения строк, их главная опасность — переполнение буфера — и безопасные альтернативы.
Классические функции вроде strcpy и strcat не проверяют размер целевого буфера. Они пишут, пока не встретят нуль в источнике — и легко выходят за границы. Это причина множества громких уязвимостей в истории C.

Заголовок <string.h> даёт базовый набор функций. Самые употребимые:

#include <string.h>

char src[] = "Hello";
char dst[20];

size_t len = strlen(src);     // длина: 5
strcpy(dst, src);             // копируем src в dst
strcat(dst, " C!");           // дописываем в конец dst
int cmp = strcmp(dst, src);   // сравнение: 0 если равны

printf("%s (длина %zu)\n", dst, strlen(dst));  // Hello C! (8)

strlen считает символы, strcpy копирует строку, strcat приклеивает одну к другой, strcmp сравнивает (возвращает 0 при равенстве, иначе разницу). Удобно — но опасно.

Как работает под капотом

Проблема strcpy в том, что она не знает, сколько места в приёмнике. Она копирует символы из источника, пока не встретит '\0' — даже если приёмник давно кончился:

char dst[4];                   // место только под 4 байта
strcpy(dst, "Hello world");    // источник — 12 байт!

dst:  [ H ][ e ][ l ][ l ] | o ][ ' ][ w ]... -> ПЕРЕПОЛНЕНИЕ
       индексы 0..3         ^ дальше идёт ЧУЖАЯ память
                            |
                  strcpy не остановится здесь —
                  она пишет до '\0' источника,
                  затирая соседние данные на стеке.

Так возникает buffer overflow — классическая уязвимость,
которой пользуются для взлома программ.

Безопасные варианты принимают максимальный размер приёмника: strncpy(dst, src, n), strncat(dst, src, n), snprintf(dst, n, ...). Они не запишут больше n символов. Но и у них есть тонкости: strncpy, например, может не поставить завершающий нуль, если источник длиннее лимита.

Частые ошибки

  • Переполнение через strcpy/strcat. Копирование в слишком маленький буфер — главный источник уязвимостей в C.
  • Забыли место под нуль при копировании. Буфер должен вмещать символы плюс терминатор.
  • strncpy без ручного нуля. Если источник ровно длины лимита, нуль не поставится — нужно дописать вручную.
  • strcmp трактуют как «да/нет». strcmp возвращает 0 при равенстве, то есть if (strcmp(a,b)) истинно, когда строки РАЗНЫЕ.

Best practices

  • Избегайте strcpy и strcat с непроверенными данными. Предпочитайте snprintf — он удобен и всегда ставит нуль в пределах лимита.
  • Всегда передавайте размер приёмника в «n-версии» функций и держите буфер достаточного размера (символы + 1).
  • После strncpy вручную ставьте dst[n-1] = '\0', чтобы гарантировать терминатор.
  • Проверяйте код санитайзерами: -fsanitize=address и valgrind находят переполнения буфера на тестах.

Логику строковых операций удобно повторить на Python через посимвольную работу со списками — так видно, где именно происходит переполнение.

# Эмулируем strcpy с проверкой размера буфера (как безопасная версия)
def safe_strcpy(dst_size, src):
    result = []
    for ch in src:
        if len(result) >= dst_size - 1:   # оставляем место под '\0'
            print("СТОП: буфер переполнился бы!")
            break
        result.append(ch)
    result.append('\0')                   # обязательный терминатор
    return result

buf = safe_strcpy(4, "Hello")    # буфер маленький — обрежется
print("Результат:", buf)

buf2 = safe_strcpy(20, "Hello")  # места хватает
print("Результат:", buf2)

Та же логика на Python ▶ — мы заранее проверяем размер буфера, как делают strncpy/snprintf. Обычная strcpy такой проверки не делает и спокойно пишет за границу.

Функции памяти и поиск в строках

Помимо строковых, в <string.h> есть «байтовые» функции, не завязанные на нулевой терминатор: memcpy копирует заданное число байтов, memset заполняет область одним значением (часто — нулями), memmove безопасно копирует даже при перекрытии областей. Они работают с любыми данными — структурами, массивами чисел, — а не только со строками, и потому быстрее и универсальнее. Для разбора текста пригодятся strchr (найти символ), strstr (найти подстроку) и strtok (разбить строку на части по разделителям). У strtok есть коварная особенность: он изменяет исходную строку, вставляя в неё нули, и хранит внутреннее состояние, поэтому его нельзя использовать в многопоточном коде. Зная набор инструментов string.h, вы реже изобретаете велосипед и реже допускаете ошибки.

Итоги

Библиотека string.h даёт strlen, strcpy, strcat, strcmp и другие функции. Их опасность — отсутствие проверки размера приёмника, что ведёт к переполнению буфера, классической уязвимости C. Решение — «n-версии» (strncpy, strncat, snprintf) с явным лимитом, ручная установка нуль-терминатора и проверка кода санитайзерами. Помните: strcmp возвращает 0 при равенстве.

Проверьте себя
1. Почему функция strcpy считается опасной?
AОна работает слишком медленно
BОна не знает размер приёмника и копирует до нуля источника, легко выходя за границы буфера (переполнение)
CОна не умеет копировать буквы
DОна всегда стирает источник
2. Что возвращает strcmp(a, b), если строки a и b одинаковые?
A1
B0
CДлину строки
DИстину (true)