Строки как массивы символов
Урок объясняет фундаментальную для C идею: строки — это не отдельный тип, а массивы символов, завершённые невидимым нулевым байтом. Без него строка не имеет конца.
В C нет типа «строка». Строка — это массив char, в конце которого стоит нулевой символ '\0'. Именно по нему функции определяют, где строка заканчивается. Потеряете нуль-терминатор — и функции будут читать память за пределами строки.
Строковый литерал "Hi" на самом деле занимает три байта: 'H', 'i' и завершающий '\0' (нулевой байт). Этот невидимый символ — маркер конца строки. Объявить строку можно как массив char:
char greeting[] = "Hi"; // массив из 3 байт: 'H','i','\0'
printf("%s\n", greeting); // Hi
printf("Длина: %zu\n", strlen(greeting)); // 2 (без учёта '\0')
printf("Занимает байт: %zu\n", sizeof(greeting)); // 3 (с '\0')
greeting[0] = 'B'; // меняем первый символ
printf("%s\n", greeting); // Bi
Обратите внимание на различие: strlen возвращает количество видимых символов (2), а sizeof — полный размер массива в байтах вместе с нулём (3). Эта пара постоянно путает новичков.
Как работает под капотом
Покажем, как строка лежит в памяти. Ключевой момент — невидимый '\0' в конце:
char greeting[] = "Hi";
Индекс: 0 1 2
+-----+-----+------+
| 'H' | 'i' | '\0' |
+-----+-----+------+
72 105 0 <- коды символов (ASCII)
^
конец строки!
printf("%s") идёт по байтам, печатая символы,
ПОКА не встретит '\0'. Без нуля он пошёл бы дальше,
печатая чужую память, пока случайно не встретит ноль.
Все стандартные строковые функции (strlen, strcpy, printf("%s")) полагаются на нуль-терминатор. Если его нет — например, вы заполнили массив символами вручную и забыли поставить ноль — функции уйдут читать память за концом строки. Это источник многих уязвимостей.
Частые ошибки
- Забыли место под нуль-терминатор. Для строки из 5 видимых символов нужен массив минимум на 6 байт.
- Потеряли нуль при ручном заполнении. Строка без завершающего нуля «не имеет конца» для функций.
- Сравнение строк через
==.str1 == str2сравнивает адреса, а не содержимое. Для сравнения нуженstrcmp. - Изменение строкового литерала.
char *s = "Hi"; s[0] = 'B';— литералы часто в read-only памяти, это крах. Используйте массив.
Best practices
- Всегда выделяйте на один байт больше под нуль-терминатор. Это правило «+1» спасает от множества ошибок.
- Для изменяемых строк объявляйте массив (
char buf[32]), а не указатель на литерал. - Сравнивайте строки через
strcmp, длину считайте черезstrlen, помня, что нуль не учитывается. - Помните:
sizeofмассива включает нуль,strlen— нет.
В Python строки — отдельный тип со своей длиной, но смоделируем именно C-представление: список символов с явным маркером конца.
# Эмулируем C-строку: список char с завершающим '\0'
c_string = ['H', 'i', '\0'] # как char[] = "Hi"
# strlen: считаем символы ДО нулевого терминатора
def c_strlen(s):
n = 0
while s[n] != '\0': # пока не встретили конец
n += 1
return n
print("Символы:", c_string)
print("strlen =", c_strlen(c_string)) # 2, без учёта '\0'
print("Всего байт (sizeof):", len(c_string)) # 3, с '\0'
Та же логика на Python ▶ — функция c_strlen идёт по символам, пока не встретит маркер конца, ровно как strlen в C. Убери терминатор — и цикл уйдёт за границу списка.
Кодировки и «символ» в C
Тип char в C — это один байт, и для английского текста (ASCII) один байт равен одному символу. Но как только появляются кириллица, эмодзи или иероглифы, всё усложняется: в кодировке UTF-8, которая сегодня стандарт, один видимый символ может занимать от одного до четырёх байтов. Поэтому strlen для русской строки вернёт не число букв, а число байтов — обычно вдвое больше. Это не баг C, а следствие того, что char — про байты, а не про «символы» в человеческом смысле. Для серьёзной работы с текстом на разных языках используют специальные библиотеки или широкие символы (wchar_t). Но базовое правило стоит запомнить сразу: в C «строка из N байтов» и «строка из N символов» — это разные вещи, как только текст выходит за пределы латиницы.
Итоги
Строка в C — это массив char, завершённый нулевым символом '\0'. Этот невидимый байт — маркер конца, по которому все строковые функции понимают, где строка заканчивается. strlen считает видимые символы без нуля, sizeof — полный размер с нулём. Главные правила — выделять место «+1» под терминатор и сравнивать строки через strcmp, а не ==.