Как устроен компьютер: биты, память, процессор и файлы простыми словами
Сразу успокою: чтобы написать первую программу, знать устройство компьютера не обязательно.
Многие начинают с кода и разбираются потом. Но есть момент, который наступает у всех примерно на второй неделе:
программа выдаёт странную ошибку, и объяснение звучит как «файл не найден», хотя файл вот он, на экране.
Или число внезапно оказывается 0.30000000000000004 вместо 0.3.
В этот момент человек либо говорит «компьютеры — это магия, я не понимаю», либо вспоминает базовую картину и спокойно чинит проблему. Эта статья — про базовую картину. Мы не будем лезть в физику транзисторов. Нам хватит четырёх идей: бит, память, процессор, файл.
Читать её лучше не спеша, с открытым Python под рукой — хотя, если Python ещё не установлен, ничего страшного, установку мы делаем в статье Первая программа, и туда можно вернуться позже.
Компьютер очень глупый, но чудовищно быстрый
Первое, что стоит принять: компьютер не «понимает». Он не догадывается, не додумывает, не прощает опечатки. Он умеет делать буквально несколько простых вещей: сложить два числа, сравнить два числа, взять число из одной ячейки и положить в другую, перейти к другой инструкции.
Всё остальное — видеозвонки, игры, нейросети — построено из этих простых действий, которых выполняются миллиарды в секунду. Ощущение «умной машины» возникает не от сложности операций, а от их количества.
Аналогия. Представьте человека, который умеет только складывать однозначные числа, но делает это со скоростью миллиард действий в секунду и никогда не устаёт. Дайте ему достаточно бумаги и очень подробную инструкцию — и он посчитает вам траекторию ракеты. Именно этим и занимается программист: пишет очень подробную инструкцию для очень быстрого и очень буквального исполнителя.
Где аналогия ломается: человек-вычислитель поймёт «ну ты понял, что я имею в виду». Компьютер — нет. Если в инструкции написано «раздели на ноль», он попытается разделить на ноль и остановится с ошибкой. Буквальность — не недостаток, а свойство, на которое мы опираемся: одна и та же программа при одних и тех же данных всегда даёт один и тот же результат.
Бит: самая маленькая единица информации
Внутри компьютера нет цифр 0–9 и нет букв. Есть миллиарды крошечных элементов, каждый из которых находится в одном из двух состояний: есть напряжение или нет, намагничено или нет.
Одно такое состояние называется бит (от англ. binary digit — двоичная цифра).
Записывают его как 0 или 1.
Почему именно два состояния, а не десять? Потому что «есть сигнал / нет сигнала» — это то, что железо различает надёжно даже при помехах. А различить десять уровней напряжения миллиард раз в секунду без ошибок гораздо сложнее. Двоичная система — это не красота математики, это инженерный компромисс в пользу надёжности.
Как из двух цифр получаются любые числа
Один бит — два варианта: 0 или 1. Два бита — уже четыре комбинации: 00, 01, 10, 11. Три бита — восемь. Каждый новый бит удваивает количество вариантов.
Мы привыкли к десятичной системе, где разряды — это единицы, десятки, сотни (степени десятки). В двоичной ровно та же логика, только разряды — это 1, 2, 4, 8, 16, 32 (степени двойки).
Число 1000010 в двоичной системе читается справа налево:
| Разряд | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| Бит | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
Складываем те разряды, где стоит единица: 64 + 2 = 66.
Проверим это в Python. Функция bin() показывает число в двоичном виде,
а int(строка, 2) переводит двоичную запись обратно в обычное число:
# bin() превращает обычное число в строку с его двоичной записью.
# Префикс "0b" — просто пометка "дальше идут двоичные цифры", он не часть числа.
print(bin(66)) # смотрим, как 66 выглядит в двоичном виде
print(bin(5)) # и заодно маленькое число для сравнения
# int(строка, 2) делает обратное: читает строку как двоичное число.
# Вторая цифра 2 — это основание системы счисления.
print(int("1000010", 2))
Ожидаемый вывод:
0b1000010
0b101
66
Обратите внимание: 66 и 0b1000010 — это одно и то же число, записанное по-разному.
Как «двенадцать», «12» и «XII». Внутри компьютера оно в любом случае хранится битами;
десятичная запись существует только для нас, людей.
Байт: восемь бит, ставших стандартом
Работать с отдельными битами неудобно, поэтому их сгруппировали по восемь. Восемь бит — это байт.
Сколько разных значений помещается в байт? Каждый из 8 битов даёт двойку вариантов, значит 2 в восьмой степени:
print(2 ** 8) # ** — это возведение в степень
print(bin(255)) # самое большое число, влезающее в один байт
256
0b11111111
256 вариантов: от 0 до 255. Отсюда растут ноги у множества «странных» чисел в IT — 256 цветов,
адреса вида 192.168.0.1 (каждое число там — один байт), ограничение «максимум 255 символов» в старых системах.
Дальше идут привычные приставки: килобайт (примерно тысяча байт), мегабайт (миллион), гигабайт (миллиард). Строго говоря, в компьютерном мире исторически считали по 1024, а не по 1000, — поэтому купленный «терабайтный» диск в системе показывает 931 ГБ. Диск не обманул, просто производитель считал по 1000, а система по 1024.
Буквы — это тоже числа
Раз внутри только биты, то буква «А» тоже должна быть числом. Так и есть. Люди договорились о таблице соответствий: какое число какую букву обозначает. Это называется кодировка.
# ord() показывает числовой код символа
print(ord("A")) # латинская заглавная A
print(ord("a")) # латинская строчная a — это другой символ и другое число
print(ord("П")) # кириллица живёт в далёких номерах
# chr() делает обратное — по числу возвращает символ
print(chr(66))
65
97
1055
B
Именно поэтому "A" и "a" для компьютера — разные вещи: 65 и 97, разница как между 5 и 7.
Компьютер не «видит букву», он сравнивает числа.
Сегодняшний стандарт — Unicode (таблица, где есть символы всех письменностей мира, эмодзи и математические знаки), а самый популярный способ записать его в байты — UTF-8. В UTF-8 латинская буква занимает 1 байт, кириллическая — 2, эмодзи — обычно 4:
text = "Привет"
# len() для строки считает СИМВОЛЫ — то, что видит человек
print(len(text))
# .encode("utf-8") превращает строку в последовательность байтов —
# то, что реально уйдёт в файл или в сеть
raw = text.encode("utf-8")
print(len(raw))
print(raw)
6
12
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
Шесть букв — двенадцать байт. Буква b перед кавычками означает «это байты, а не текст»,
а \xd0 — способ записать один байт в шестнадцатеричном виде.
Запоминать шестнадцатеричную систему прямо сейчас не нужно; достаточно понимать,
что перед вами сырые байты, а не буквы.
Эта разница между «символами» и «байтами» — источник половины проблем с кракозябрами.
Если текст сохранили в одной кодировке, а прочитали в другой, компьютер честно применит не ту таблицу
и покажет привет вместо привет. Файл при этом не испорчен — просто его читают не теми «очками».
Память: длинная улица пронумерованных ячеек
Биты надо где-то держать. Место, где программа держит данные прямо сейчас, называется оперативная память (ОЗУ, RAM).
Представьте очень длинную улицу с одинаковыми ящиками. Каждый ящик хранит ровно один байт, и у каждого есть номер — адрес. Процессор умеет сказать: «дай содержимое ящика номер 4 812 366» — и получить его почти мгновенно, независимо от того, где этот ящик находится. Отсюда и английское название: Random Access Memory, память произвольного доступа — любой ящик доступен одинаково быстро.
Ключевая мысль со схемы: сами биты не несут смысла. Байт 01000010 — это одновременно число 66,
буква «B» и часть какой-нибудь картинки. Смысл появляется только тогда, когда программа решает,
как их читать. Именно поэтому в программировании так важны типы данных — они и есть эта договорённость
о правилах чтения. Подробно разберём в статье Переменные и типы данных.
Оперативная память и диск — это разные вещи
Новички часто путают «память» и «место на диске». Разница принципиальная.
| Оперативная память (RAM) | Диск (SSD/HDD) | |
|---|---|---|
| Зачем | данные, с которыми работают прямо сейчас | долгое хранение |
| Скорость | очень высокая | в сотни-тысячи раз медленнее |
| При выключении | всё стирается | сохраняется |
| Объём | 8–32 ГБ типично | 256 ГБ – 4 ТБ типично |
Аналогия: оперативная память — это письменный стол, диск — шкаф с папками. На стол вы выкладываете то, с чем работаете сейчас; в шкафу лежит всё остальное. Стол маленький, но всё под рукой; шкаф большой, но за папкой надо встать и пойти.
Где аналогия ломается: уходя домой, вы не сметаете стол в мусорку — а компьютер при выключении делает именно это. Всё, что было в оперативной памяти, исчезает. Поэтому несохранённый документ пропадает при отключении света: он жил на «столе», а в «шкаф» его не положили.
Из этой же разницы растёт понимание, почему программы вообще работают с файлами: чтобы результат пережил завершение программы, его нужно осознанно записать на диск. Об этом — статья Файлы и форматы данных.
Память быстрая не одинаково
На самом деле «улица ящиков» — не одна. Их несколько уровней, и чем ближе к процессору, тем быстрее и меньше:
десятки байт
~1 такт"] --> L1["Кэш L1/L2/L3
от килобайт до мегабайт
единицы-десятки тактов"] L1 --> RAM["Оперативная память
гигабайты
сотни тактов"] RAM --> DISK["SSD / жёсткий диск
терабайты
десятки тысяч тактов"] DISK --> NET["Сеть
практически безгранично
миллионы тактов"]
Регистры — это несколько крошечных ячеек внутри самого процессора, в которых он держит числа, над которыми работает буквально в эту наносекунду. Кэш — небольшой быстрый склад рядом с процессором, куда автоматически копируются недавно использованные данные.
Аналогия: регистры — это то, что вы держите в руках; кэш — стол; оперативная память — полка за спиной; диск — шкаф в подвале; сеть — библиотека в другом городе. Каждый шаг вниз — примерно в 10–100 раз дольше.
Начинающему программисту не нужно управлять кэшем вручную — это делает железо. Но полезно запомнить: обращение к диску и к сети в тысячи раз дороже, чем к памяти. Когда позже вы будете думать, почему программа тормозит, ответ чаще всего окажется не «мало вычислений», а «слишком много походов в подвал».
Процессор: вечный цикл из трёх шагов
Процессор (CPU, центральное процессорное устройство) — та самая деталь, которая выполняет инструкции. Устроен его рабочий день предельно однообразно: он крутит один и тот же цикл, пока компьютер включён.
прочитать очередную инструкцию из памяти"] D["Декодирование (decode)
понять, что за операция и над какими данными"] E["Исполнение (execute)
сложить, сравнить, скопировать"] W["Запись результата (write back)
положить ответ в регистр или память"] N["Сдвинуть указатель
на следующую инструкцию"] F --> D --> E --> W --> N --> F
Всё. Никакой магии: взял инструкцию, понял, сделал, записал, взял следующую. Процессор с частотой 3 ГГц прокручивает этот цикл около трёх миллиардов раз в секунду.
Пара понятий, которые вы будете встречать постоянно:
- Тактовая частота (гигагерцы) — сколько «тиков» в секунду делает процессор. Один такт — один шажок работы. Больше герц — быстрее, но сравнивать по частоте процессоры разных поколений бессмысленно: за один такт они успевают разный объём работы.
- Ядро — по сути отдельный процессор в одном корпусе. Четыре ядра — четыре инструкции могут выполняться по-настоящему одновременно. Аналогия: не «повар работает быстрее», а «на кухне стало четыре повара». И, как на настоящей кухне, четыре повара не готовят блюдо ровно в четыре раза быстрее — им нужно договариваться, кто берёт какую сковородку.
- Архитектура (x86-64, ARM) — «язык», на котором процессор принимает инструкции. Программа, скомпилированная под один язык, не запустится на другом без перевода. Именно поэтому приложения для ноутбуков Apple на чипах M-серии и для обычных ПК — разные файлы.
Что происходит, когда вы запускаете программу
Соберём картину целиком. Вы дважды кликнули на иконку или набрали python my_program.py:
Операционная система (Windows, macOS, Linux) — это программа-распорядитель. Она решает, кому из запущенных программ сейчас дать процессор, следит, чтобы одна программа не залезла в память другой, и предоставляет всем общий доступ к диску, экрану и сети. Запущенная программа со своим кусочком памяти называется процесс.
Отдельный вопрос — как ваш текст на Python вообще превращается в инструкции процессора. Коротко: Python-код читает специальная программа-интерпретатор, которая шаг за шагом выполняет написанное вами, сама обращаясь к процессору. Поэтому Python-программе нужен установленный Python, а, скажем, скомпилированной программе на Go — нет: её заранее перевели в машинные инструкции целиком. Разницу подробнее разберём в Первой программе.
Побочный эффект битов: та самая история с 0.1 + 0.2
Теперь можно объяснить самую известную «странность», на которую натыкается каждый новичок:
print(0.1 + 0.2)
0.30000000000000004
Это не баг Python — то же самое покажут JavaScript, Java и C.
Причина в битах: дробные числа хранятся в двоичной системе, а 0.1 в двоичной записи —
бесконечная дробь, ровно как 1/3 = 0.333... в десятичной. Компьютер вынужден округлить её
до доступного числа битов, и крошечная погрешность при сложении вылезает наружу.
Практический вывод: для денег и точных расчётов не используют обычные дробные числа
(в Python для этого есть модуль decimal), а сравнивать дробные числа через == — плохая идея.
Формат хранения описан в стандарте IEEE 754, а подробный разбор есть на
floating-point-gui.de.
Файлы: имена, данные и договорённости
Файл — это именованная последовательность байтов на диске. Всё. Никаких других свойств у него нет.
Это стоит перечитать, потому что отсюда следует несколько неочевидных вещей.
Расширение — это просто часть имени. .txt, .jpg, .py — не встроенное свойство файла,
а хвост его названия, по которому операционная система догадывается, какой программой его открыть.
Переименовав фото.jpg в фото.txt, вы не превратили картинку в текст — байты остались теми же,
просто система теперь ошибётся в выборе программы.
import os
# splitext() разрезает имя на две части по ПОСЛЕДНЕЙ точке.
# Это чисто работа со строкой — файл при этом даже не открывается.
print(os.path.splitext("report.final.csv"))
('report.final', '.csv')
Текстовые и бинарные файлы отличаются только договорённостью.
Внутри и там и там байты. Если байты задуманы как символы в какой-то кодировке — файл называют текстовым
и открывают в режиме "r". Если это картинка, видео или архив — бинарным, режим "rb" (b — binary).
Попытка прочитать картинку как текст даст ошибку именно потому, что байты не складываются в валидные символы.
Папки и пути
Файлы лежат в папках (каталогах), папки — в других папках. Получается дерево:
Путь — это маршрут до файла. Путей два вида, и путаница между ними даёт самую частую ошибку новичка.
- Абсолютный путь начинается от корня и полностью определяет место:
/home/anna/projects/my-first-program/main.py(Linux, macOS) илиC:\Users\anna\projects\my-first-program\main.py(Windows). Аналогия: полный почтовый адрес с городом и улицей. - Относительный путь отсчитывается от папки, в которой программа работает прямо сейчас:
data.csvилиnotes/idea.txt. Аналогия: «вторая дверь налево» — работает, только если вы стоите в нужном коридоре.
Та самая папка, «в которой программа работает сейчас», называется текущей рабочей директорией. Её легко посмотреть:
import os
# getcwd = get current working directory, "получить текущую рабочую папку"
print(os.getcwd())
# listdir показывает, что лежит в указанной папке.
# Точка "." означает "текущая папка"
print(os.listdir("."))
Вывод у каждого свой, например:
/home/anna/projects/my-first-program
['main.py', 'data.csv', 'notes']
Важный момент: текущая папка — это не папка, где лежит файл программы.
Это папка, из которой программу запустили. Если вы из домашней директории набрали
python projects/my-first-program/main.py, то текущей будет домашняя,
и относительный путь data.csv укажет не туда, куда вы ожидали.
Напишем маленькую программу, которая создаёт файл и читает его обратно:
# Открываем файл на запись: "w" = write.
# Если файла нет — он создастся; если есть — содержимое будет стёрто.
# encoding="utf-8" явно говорит, какой таблицей кодировать буквы —
# без него Windows может выбрать свою и получатся кракозябры.
with open("hello.txt", "w", encoding="utf-8") as f:
f.write("Привет, диск!\n") # \n — это символ перевода строки
# Конструкция with ... as f сама закрывает файл, когда блок закончится.
# Это важно: пока файл не закрыт, данные могут ещё не дойти до диска.
# Теперь читаем обратно: "r" = read
with open("hello.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
print(len(content), "символов")
Привет, диск!
13 символов
Тринадцать символов — это 12 букв со знаками препинания плюс невидимый перевод строки.
Пустая строка в выводе появилась как раз из-за него: \n внутри текста плюс перевод строки от print().
Типичные ошибки новичков и что означают сообщения
Сообщения об ошибках выглядят пугающе, но они устроены логично: последняя строка — суть проблемы. Читайте снизу вверх. Разберём три, с которыми сталкиваются буквально все.
FileNotFoundError
FileNotFoundError: [Errno 2] No such file or directory: 'data.csv'
Дословно: «ошибка номер 2, нет такого файла или папки: data.csv».
В девяти случаях из десяти файл существует — просто программа ищет его не там,
потому что data.csv это относительный путь. Что делать:
import os
print("Ищу здесь:", os.getcwd()) # где программа сейчас "стоит"
print("Вижу тут:", os.listdir(".")) # что она отсюда видит
Если в списке нужного файла нет — либо перейдите в правильную папку перед запуском,
либо укажите абсолютный путь. Второй частый вариант: в Windows включено скрытие расширений,
и файл на самом деле называется data.csv.txt.
UnicodeDecodeError
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xcf in position 0: invalid continuation byte
Дословно: «кодировщик utf-8 не может расшифровать байт 0xcf на позиции 0».
Перевод на человеческий: вы попросили читать байты как текст в UTF-8, но эти байты
по правилам UTF-8 не складываются в символ. Либо файл сохранён в другой кодировке
(частый гость — cp1251 для старых русских текстов), либо это вообще не текст, а картинка или архив.
Лечится указанием правильной кодировки: open(path, encoding="cp1251") —
или чтением в бинарном режиме "rb", если файл и не должен быть текстом.
PermissionError
PermissionError: [Errno 13] Permission denied: 'report.xlsx'
Дословно: «доступ запрещён». Обычно означает, что файл уже открыт другой программой (классика — Excel), либо вы пытаетесь писать в системную папку, куда обычному пользователю нельзя.
Ещё три ловушки
- «Программа ничего не делает». Если запустить
pythonбез имени файла, вы попадёте в интерактивный режим — приглашение>>>. Это не зависание, это ожидание команды. Выйти:exit(). - «Я сохранил файл, но программа видит старую версию». Проверьте, что редактор действительно сохранил (в большинстве редакторов несохранённый файл помечен точкой или звёздочкой у названия вкладки).
- «Всё пропало после перезагрузки». Значит, данные жили в оперативной памяти и на диск их никто не записывал. Смотрите раздел про стол и шкаф выше.
Практика
Задания идут от простого к сложному. Если Python ещё не установлен, вернитесь к ним после статьи Первая программа — ничего не потеряете.
1. Разминка с битами. Выведите двоичную запись чисел 1, 2, 4, 8, 16 и посмотрите на закономерность.
Затем угадайте (сначала на бумаге!), какому обычному числу равно двоичное 1010, и проверьте себя через int("1010", 2).
Подсказка: разряды двоичного числа справа налево — это 1, 2, 4, 8.
2. Своя мини-таблица кодировки. Напечатайте символы с кодами от 65 до 90 через chr().
Что получилось? Затем найдите код своей первой буквы имени через ord().
Подсказка: понадобится цикл for code in range(65, 91):. Циклы мы разбираем в статье
Циклы, но здесь можно просто скопировать эту строку и написать под ней
с отступом print(code, chr(code)).
3. Символы против байтов. Возьмите строку из пяти русских букв и пяти латинских.
Выведите len(строка) и len(строка.encode("utf-8")). Объясните себе разницу вслух.
Затем добавьте в строку эмодзи и посмотрите, на сколько выросло число байтов.
Подсказка: ответ должен получиться в духе «латиница по 1 байту, кириллица по 2, эмодзи по 4».
4. Разведка файловой системы. Напишите программу, которая печатает текущую рабочую директорию, список файлов в ней, а для каждого файла — его размер в байтах.
Подсказка: размер даёт os.path.getsize(имя). Пройдитесь циклом по os.listdir(".").
Осторожно: в списке могут быть папки, у которых размер означает не то, что вы ожидаете, — это нормально.
5. Файл-счётчик (посложнее). Сделайте программу, которая при каждом запуске увеличивает
число в файле counter.txt на единицу и печатает его. При самом первом запуске файла ещё нет —
программа должна это пережить и начать с единицы.
Подсказка: проверить наличие файла можно через os.path.exists("counter.txt").
Прочитанное из файла — всегда строка, её нужно превратить в число через int(...),
а перед записью обратно — в строку через str(...). Это задание объединяет всё:
байты, файлы, типы и обработку ситуации «а если файла нет».
Мини-итог
- Внутри компьютера только биты — нули и единицы. Восемь бит — байт, 256 возможных значений.
- Числа, буквы, картинки, звук — всё это байты. Смысл байтам придаёт договорённость о том, как их читать: кодировка для текста, тип данных для чисел, формат для картинки.
- Оперативная память — быстрый стол, который стирается при выключении. Диск — медленный, но постоянный шкаф. Чем дальше данные от процессора, тем дороже до них добираться.
- Процессор бесконечно крутит цикл «взять инструкцию — понять — выполнить — записать», миллиарды раз в секунду. Он не умный, он быстрый и буквальный.
- Файл — это именованная последовательность байтов. Расширение — часть имени, а не свойство содержимого.
- Путь бывает абсолютным (полный адрес) и относительным (от текущей папки). Большинство ошибок «файл не найден» — про относительные пути.
Если из всей статьи запомнить одну мысль, пусть будет эта: данные не имеют смысла сами по себе, смысл задаёт программа. Дальше всё программирование — про то, как аккуратно задавать этот смысл.
Что почитать и посмотреть
- Крэш-курс по информатике (Crash Course Computer Science) — очень наглядные короткие видео на английском с субтитрами.
- Чарльз Петцольд, «Код: тайный язык информатики» — лучшая книга о том, как из выключателя получается компьютер. Читается как детектив.
- The Absolute Minimum Every Software Developer Must Know About Unicode — классическая статья Джоэла Спольски про кодировки.
- Документация Python: работа с файлами — официальный туториал.
- floating-point-gui.de — почему
0.1 + 0.2 != 0.3, подробно и по-доброму.
Что дальше
Мы разобрались, как устроена одна машина. Но почти всё интересное сегодня происходит, когда машины разговаривают друг с другом: вы открываете сайт, и байты летят через полмира.
В следующей статье — Как работают интернет и сайты: клиент, сервер, домены, HTTP — посмотрим, что происходит между нажатием Enter в адресной строке и появлением картинки на экране.