Строки как массивы символов

Урок объясняет фундаментальную для 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, а не ==.

Проверьте себя
1. Зачем в конце C-строки стоит нулевой символ?
AДля красоты вывода
BЭто маркер конца строки: по нему функции определяют, где строка заканчивается
CЧтобы строку нельзя было изменить
DОн увеличивает скорость печати
2. Почему для сравнения строк в C нельзя писать str1 == str2?
AПотому что == работает только с числами
BПотому что это сравнит адреса массивов, а не их содержимое; нужен strcmp
CПотому что строки нельзя сравнивать вообще
DПотому что == всегда возвращает истину