Любая фото, песня, переписка и даже перевод денег - это цепочки из 0 и 1. Компьютеры говорят на бинарном языке. Кстати, в вопросе подвох: не буква I, а цифра 1. И да, это единственный язык, который железо понимает напрямую.
Почему именно два символа? Потому что физике так проще и надёжнее. В проводнике есть напряжение или его нет. В магнитном слое ячейка намагничена так или иначе. Во флеш-памяти заряд есть или нет. Два устойчивых состояния - меньше ошибок и дешевле схемы.
Основа - бит (0 или 1). Восемь бит - байт. Полезно держать в голове опорные числа: 2^10 = 1024 (≈1 КиБ), 2^20 ≈ 1 МиБ, 2^30 ≈ 1 ГиБ. Это быстро объясняет, откуда берутся «странные» 1024 вместо 1000.
Интересный факт: были попытки уйти от двоичной логики. В 1958 в МГУ сделали троичный компьютер «Сетунь» на сбалансированном троичном коде. Он работал, но индустрия выбрала двоичную электронику - проще компоненты, лучше помехоустойчивость и инструменты.
Биты сами по себе - просто числа. Чтобы показать буквы и эмодзи, нужны кодировки. Классика: ASCII - буква A это десятичное 65, в двоичном 01000001. Русские буквы живут в Unicode. В UTF-8 заглавная Я (U+042F) - это два байта: D0 AF, в двоичном 11010000 10101111. Один и тот же текст в разных кодировках - разные байты.
Хочется увидеть биты у себя? Быстро и без магии: на Linux/macOS - xxd -b файл.bin (покажет биты) или hexdump -C файл.bin (шестнадцатеричный дамп с ASCII). На Windows - PowerShell: Format-Hex .\файл.bin. Для строки - echo "A" | xxd -b и любуйтесь 01000001.
Как же из битов складывается «ум»? На уровне железа всё строится на логических воротах: AND, OR, NOT, XOR. Из ворот - сумматоры, регистры, АЛУ, кэш, процессор. А уже поверх - компиляторы и языки. Вы пишете на Python/Go, компилятор и ОС превращают это в машинный код - те самые 0 и 1.
Немного практики для повседневной пользы. Права Unix - это биты: rwx для владельца, группы и всех. Команда chmod 644 - это 110 100 100: владелец читает и пишет, остальные только читают. Флаги в конфигурациях - тоже биты: 1 (0001), 2 (0010), 4 (0100), 8 (1000). Комбинируете их через OR, проверяете через AND - быстро и экономно.
- Что значит язык 0 и 1
- Биты, байты и кодировки
- Как железо хранит нули и единицы
- От логических ворот к языкам высокого уровня
- Практика: смотрим и трогаем биты
Что значит язык 0 и 1
Под «языком 0 и 1» обычно понимают бинарный язык - способ представить любые данные и команды как последовательность двух стабильных состояний. Это не буква «I», а цифра «1». Два состояния удобны железу: их проще различать в схеме и удерживать без ошибок.
На физическом уровне «0» и «1» - не конкретные числа, а диапазоны напряжений. Например, в классической 5‑вольтовой TTL‑логике вход меньше или равен 0,8 В считается нулём, а больше или равен 2,0 В - единицей. В современных CMOS‑семействах пороги обычно задают как долю от питания (около 0,3·Vdd для «0» и 0,7·Vdd для «1»). Конкретные цифры всегда смотрите в даташите микросхемы.
Логика / питание | Порог «0» (VIL, макс.) | Порог «1» (VIH, мин.) | Примечание |
---|---|---|---|
TTL 5 В (74LS) | ≤ 0,8 В | ≥ 2,0 В | Нечётные пороги, совместимость с широким семейством TTL |
CMOS 3,3 В | ≈ 0,3·Vdd (≤ 0,99 В) | ≈ 0,7·Vdd (≥ 2,31 В) | Типичные значения для 3,3 В CMOS |
CMOS 1,8 В | ≈ 0,3·Vdd (≤ 0,54 В) | ≈ 0,7·Vdd (≥ 1,26 В) | Типичные значения для 1,8 В CMOS |
Логическая «1» и «0» дальше проходят через логические ворота (AND, OR, NOT, XOR) и регистры. Из этих кирпичиков строятся сумматоры, кэш, контроллеры, а в итоге - процессор. На другом конце - память и шины, которые переносят те же биты.
Бит - минимальная единица информации. Из 8 бит получается байт, а из n бит можно представить 2^n различных состояний. Поэтому 8 бит дают 256 вариантов: достаточно, чтобы закодировать символ из ASCII, небольшой цвет палитры или часть машинной команды.
Числа в процессоре хранятся не «как в тетради», а по соглашениям. Для со знаком почти везде используется двоичное дополнение (two’s complement): самый старший бит - знак, а отрицательные числа считаются как 2^n − |x|. Это даёт одну арифметику для сложения и вычитания без дополнительных правил.
Текст - это тоже числа. Буква A в ASCII - десятичное 65, в шестнадцатеричном 0x41, в двоичном 01000001. Для мировых языков используют Unicode. В UTF‑8 один и тот же символ занимает разное число байт: латиница - 1 байт, кириллица - чаще 2, эмодзи - 4. Например, «Я» - два байта: 0xD0 0xAF.
Команды процессора - машинный код, то есть такие же байты. Архитектура (x86‑64, ARM, RISC‑V) описывает, как байты раскладываются на опкод и операнды. Примеры для x86‑64: NOP - 0x90; MOV EAX, 1 - байты B8 01 00 00 00; ADD EAX, EBX - 01 D8 (в двоичном это 00000001 11011000). Эти байты идут по шине, попадают в кэш инструкций, декодируются и исполняются.
- Процессор выбирает (fetch) следующую команду по адресу из IP/RIP.
- Декодирует (decode) байты: что за операция, где операнды.
- Исполняет (execute): АЛУ делает вычисления, логика управляет переходами.
- Записывает результаты (writeback) в регистры или память, двигает счётчик команд.
Исторический факт: ранний ENIAC работал на десятичной логике (кольцевые счётчики), но индустрия быстро перешла на двоичную электронику - проще схемы, выше помехоустойчивость, легче масштабирование.
Как быстро «читать» двоичное? Удобно держать в голове связь «одна шестнадцатеричная цифра = 4 бита». Тогда 0x90 - это 1001 0000, 0xAF - 1010 1111. Большие массивы байт обычно смотрят в hex, потому что он короче в 4 раза и легко переводится в биты.
- Для констант используйте префиксы: 0b1010 (двоичное), 0xFF (шестнадцатеричное), 42 (десятичное).
- Работайте с битовыми масками: OR (|) включает биты, AND (&) проверяет, XOR (^) переключает, NOT (~) инвертирует.
- За порогами логических уровней всегда идите в даташит: параметры VIL/VIH и тайминги спасают от «призрачных» багов железа.
Вывод простой: «язык 0 и 1» - это общий слой, где сходятся устройства, кодировки и инструкции. Всё остальное - способы удобно собрать и разобрать эти биты.
Биты, байты и кодировки
Бит - это минимальная единица данных: 0 или 1. 8 бит - байт. Полбайта (4 бита) - «ниббл». В современных системах байт почти всегда 8 бит, и это уже де-факто стандарт.
Память и диски любят степени двойки: 2^10 = 1024, 2^20 = 1048576, 2^30 ≈ 1,07 млрд. Отсюда и двоичные приставки: 1 KiB = 1024 B, 1 MiB = 1024 KiB. Десятичные - это 1 KB = 1000 B, 1 MB = 1000000 B. В утилитах можно встретить оба варианта, так что хорошо понимать разницу.
Шестнадцатеричная запись удобна, потому что 1 hex-цифра покрывает ровно 4 бита. Например, байт 0xAF - это биты 1010 1111. Поэтому дампы показывают байты в hex, а не в длинных цепочках из нулей и единиц.
То, что мы зовём бинарный язык, - это условные «слова» из битов. Для чисел важны знаковость и ширина. Без знака 8 бит дают диапазон 0…255, со знаком -128…127 (дополнительный код). 32-битное со знаком - примерно −2,147,483,648…2,147,483,647. 64-битное - около ±9,22×10^18. Эти границы влияют на переполнения и сериализацию данных.
Порядок байтов (endianness) относится к многобайтовым числам: x86 - little-endian, сетевые протоколы - big-endian (network byte order). Для UTF‑8 порядок байтов не важен - это поток байтов, читаемый слева направо. А вот для UTF‑16/32 порядок важен; его обозначают BOM или настройками протокола.
ASCII - это 7-битная таблица на 128 символов (U+0000…U+007F). Русские буквы, иероглифы и эмодзи там не помещаются. Unicode описывает весь набор символов (сейчас более 149 тысяч), до U+10FFFF (17 «плоскостей»). На практике на вебе доминирует UTF‑8 - более 97% сайтов используют его (по W3Techs), и это отличный дефолт для файлов и API.
UTF‑8 - это переменная длина: символ занимает 1-4 байта. Базовый ASCII кодируется одним байтом, кириллица - обычно двумя, «евро» - тремя, эмодзи - чаще четырьмя. Ниже - наглядная таблица с примерами и точными диапазонами.
Длина (байт) | Диапазон кодпоинтов | Префикс битов | Пример | UTF‑8 байты (hex) |
---|---|---|---|---|
1 | U+0000-U+007F | 0xxxxxxx | 'A' (U+0041) | 41 |
2 | U+0080-U+07FF | 110xxxxx 10xxxxxx | 'Я' (U+042F) | D0 AF |
3 | U+0800-U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx | '€' (U+20AC) | E2 82 AC |
4 | U+10000-U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | '🙂' (U+1F642) | F0 9F 99 82 |
Пара точных примеров в битах. 'A' - десятичное 65, hex 0x41, биты 01000001. 'Я' - кодпоинт U+042F, в UTF‑8 два байта: D0 AF, биты 11010000 10101111. Всё сходится с шаблонами из таблицы выше.
Про «рассинхрон» между байтами и «символами». В UTF‑8 длина строки в байтах почти всегда больше числа видимых знаков: одна кириллическая буква - 2 байта, большинство эмодзи - 4 байта. К тому же «символ на экране» может быть не одним кодпоинтом: флаг 🇺🇦 - это два кода (региональные индикаторы), буква с диакритикой может быть «буква + комбинирующий знак». Для поиска и сравнения помогает нормализация Unicode (NFC/NFD).
BOM: в UTF‑8 это три байта EF BB BF в начале файла. Он не обязателен и иногда мешает (скрипты с shebang, JSON в некоторых парсерах). Для кроссплатформенных текстов безопаснее сохранять UTF‑8 без BOM.
Про однобайтные «кодировки» вроде Windows‑1251. Они фиксируют 256 позиций и конфликтуют между собой. Если видите кракозябры, почти всегда виноват неверно угаданная кодировка. Универсальный совет: хранить в UTF‑8, явно указывать encoding в заголовках HTTP и в открытии файлов (напр., в Python: encoding='utf-8').
Короткие практические подсказки:
- Считайте в байтах, когда речь про лимиты API и дисковое место. «100 символов» в базе - не равно «100 байт».
- Для двоичных флагов используйте маски 1, 2, 4, 8… и операции OR/AND - это компактно и быстро.
- В протоколах фиксируйте endianness для чисел и кодировку для текста прямо в спецификации.

Как железо хранит нули и единицы
Компьютер держит данные в виде двух устойчивых состояний. Это может быть заряд есть/нет, магнит туда/сюда, свет отражается/нет. По сути, железо говорит на бинарный язык, потому что два состояния проще надёжно различать и дальше усиливать, копировать и передавать.
Что считается «нулём» и «единицей» на проводе? В цифровых схемах задают уровни. В классических TTL-входах «0» - до 0.8 В, «1» - от 2.0 В (при питании 5 В). В современном CMOS пороги зависят от питания (ядро CPU может работать около 0.7-1.2 В), но идея та же: ниже порога - «0», выше - «1». Для «грязных» сигналов ставят входы со Шмитт-триггером, чтобы отсечь шум и дрожание фронтов.
Дальше - носители состояния. Их два больших семейства: volatile (всё пропадает без питания) и non‑volatile (питание сняли - биты сохранились). Волатильная память нужна для скорости (регистры, кэши, ОЗУ), неволетильная - для долгого хранения (SSD, HDD, ПЗУ).
SRAM - это 6 транзисторов, скрещённых в две «защелкнутые» инверторные пары. Пока есть питание, ячейка удерживает «0» или «1». Очень быстро (наносекунды), но дорого по площади, поэтому из SRAM делают кэши (L1/L2/L3) и регистровые файлы.
DRAM хранит бит в конденсаторе (1T1C): заряд есть - «1», стекло - «0». Заряд утекает, поэтому строки памяти обновляют каждые ~64 мс (чаще при высоких температурах). Мизерный сигнал считывают усилители-сенсоры, которые одновременно «переписывают» заряд назад. Типичные задержки доступа - десятки наносекунд. Отсюда знакомые тайминги вроде tRCD, CL, tRP. Известная уязвимость Rowhammer как раз бьёт по этим механикам - частыми чтениями вызывает наводки и случайные флипы в соседних строках.
Флеш-память (NAND) использует «плавающий затвор»: на нём накапливается заряд, который сдвигает порог транзистора. SLC хранит 1 бит на ячейку (2 уровня), MLC - 2 бита (4 уровня), TLC - 3 (8 уровней), QLC - 4 (16 уровней). Больше уровней - выше плотность, но ниже ресурс и скорость. Примерные циклы записи/стирания: SLC 50-100 тыс., MLC ~3 тыс., TLC 1-3 тыс., QLC ~200-1 тыс. Поэтому контроллеры делают выравнивание износа (wear leveling) и сильную коррекцию ошибок (LDPC/BCH).
Жёсткие диски записывают биты как направление намагниченности доменов на дорожке. Современная перпендикулярная запись даёт плотности свыше 1 Тбит/дюйм², а новые технологии (HAMR/MAMR) поднимают потолок ещё выше. Время доступа ограничено механикой: перемещение головки и ожидание сектора под ней - это миллисекунды. Для надёжности на каждом секторе есть свой ECC и CRC; у современных дисков размер сектора - 4096 байт (4Kn) или эмуляция 512e.
Оптика (CD/DVD/BD) читает разницу отражения «ямок» и «площадок». Это тоже два состояния, но физически - про свет. Задержки выше, носители чувствительны к царапинам, зато дешёво архивировать и передавать.
Носитель | Как кодируется бит | Волатильность | Типичные задержки | Ресурс/особенности |
---|---|---|---|---|
SRAM (кэш) | Стабильное состояние защёлкнутых инверторов | Да | ~0.5-2 нс (L1), единицы-десятки нс (L2/L3) | Быстро и дорого по площади |
DRAM (ОЗУ) | Заряд конденсатора + sense-усилители | Да (refresh ~64 мс) | ~50-100 нс до строки; полная память ~70-100 нс+ | Дёшево и ёмко; чувствительно к помехам (Rowhammer) |
NAND Flash (SSD) | Заряд на плавающем затворе (уровни порога) | Нет | Чтение ~50-100 мкс; запись ~0.5-3 мс; erase ~2-10 мс | Выравнивание износа; SLC>MLC>TLC>QLC по ресурсу |
HDD | Направление намагниченности доменов | Нет | Поиск/вращение ~5-15 мс | Дёшево за ГБ; механика и вибрации влияют на ошибки |
Оптические диски | Отражение от «ямок» и «площадок» | Нет | ~100 мс и выше | Дёшево хранить, чувствительно к царапинам/сроку |
Передача нулей и единиц по плате и кабелям тоже не всегда «одним проводом и напряжением». На скоростях PCIe/USB/DisplayPort используют дифференциальные пары и кодирование (раньше 8b/10b, сейчас в PCIe 3.0+ - 128b/130b), чтобы лучше синхронизироваться и ловить ошибки.
Ошибки случаются. Космические частицы могут флипнуть бит в DRAM (soft error). ECC-память с кодами типа SECDED ловит и исправляет одиночные ошибки. В большом исследовании Google (2009) около 8% модулей DRAM в дата-центрах за год ловили исправляемые ошибки - это нормальная статистика боевой эксплуатации. В SSD контроллер постоянно считает и исправляет биты по LDPC, а уровень необратимых ошибок (UBER) держат в районе 10⁻¹⁵-10⁻¹⁷.
Немного практики, чтобы держать биты в порядке:
- Проверьте, есть ли у вас ECC: в Linux - dmidecode -t memory, в Windows - wmic MemoryChip get DataWidth,TotalWidth (если TotalWidth больше DataWidth - есть ECC).
- Смотрите износ SSD: smartctl -a /dev/nvme0 (SATA - атрибуты Wear_Leveling_Count/Media_Wearout_Indicator; NVMe - Percentage Used).
- Оставляйте SSD 10-20% свободного места - контроллеру легче распределять записи и меньше износ.
- На HDD держите резерв под SMART-переназначение секторов, следите за Reallocated_Sector_Ct и Pending_Sector.
- Тепло ускоряет деградацию. Хороший обдув продлевает жизнь и DRAM, и NAND.
Если коротко: «0» и «1» - это просто договорённость поверх конкретной физики. Мы выбираем стабильные состояния, задаём пороги, фильтруем шум и прикручиваем исправление ошибок. Дальше - дело схем и протоколов, которые быстро и предсказуемо тасуют эти состояния между регистрами, памятью и дисками.
От логических ворот к языкам высокого уровня
Внизу всё начинается с логических ворот: AND, OR, NOT, XOR. Из двух NAND можно собрать NOT, а из NAND и OR - что угодно. Это не теория для учебника - NAND и NOR функционально полны, этого хватает, чтобы построить любой логический блок. Дальше - триггеры (SR, D), из них - регистры, счётчики и память. К этому добавляем АЛУ - устройство, которое умеет складывать, вычитать, сдвигать и сравнивать биты. Так из ворот получаются «кирпичи» процессора. Такт подаёт ритм (сейчас частоты обычно 2-5 ГГц), и за каждый тик данные проходят кусочек пути по конвейеру.
Правила, на каком наборе бит что делать, описывает ISA - архитектура набора инструкций. Примеры: x86-64 (Intel/AMD, корни в 8086 1978 года), ARMv8-A (мобильные и серверы), RISC‑V (свободная архитектура из Berkeley, спецификация открыта). Инструкция - это опкод плюс операнды и режим адресации. Для наглядности: в x86-64 «add eax, ebx» превращается в байты 01 D8 (опкод 0x01 и байт ModR/M 0xD8). Эти два байта для процессора - команда «сложи содержимое EBX к EAX».
ISA - это «договор» между софтом и железом. Как именно чип исполняет команды - дело микроархитектуры. Современные x86-64 разбирают сложные инструкции на микро‑операции, выполняют их вне очереди, предсказывают ветвления и держат кэш инструкций/данных (обычно L1/L2/L3). Микрокод можно обновлять - Linux и Windows грузят патчи микрокода для процессоров Intel и AMD, чтобы чинить уязвимости и баги без замены железа. Всё это не меняет ISA: собранный файл продолжает работать.
Чтобы людям не писать байты вручную, есть ассемблер - удобные мнемоники и синтаксис. Ассемблер превращает текст в объектные файлы (ELF в Linux, PE/COFF в Windows) с таблицами символов и релокациями. Линкер склеивает объектники и библиотеки, раскладывает код и данные по сегментам, правит адреса. Динамическая линковка подключает .so/.dll во время запуска через загрузчик (ld-linux.so, ntdll/loader). Посмотреть, что получилось, можно так:
- Linux/macOS: objdump -d -M intel ./prog, readelf -a ./prog, nm ./prog
- Windows (MSVC/PowerShell): dumpbin /DISASM prog.exe, link /DUMP /ALL prog.obj
- Сразу из исходников: gcc -O3 -S file.c (даст .s), clang -S -emit-llvm file.c (IR)
Компилятор мостит путь от кода на C/Go/Rust к байтам. Конвейер такой: парсер строит AST, затем преобразует в промежуточное представление (IR), чаще в SSA‑форме. Дальше идут оптимизации (inline, удаление мёртвого кода, распространение констант, векторизация), распределение регистров, выбор инструкций под конкретную ISA и уже потом - генерация машинных инструкций и линковка. LLVM - популярный стек (Clang/LLVM IR/LLD). GCC имеет свой GIMPLE/RTL. Да, опции важны: -O0 - быстрые сборки для отладки, -O3 - агрессивная оптимизация, -march=native - использовать возможности конкретного CPU.
Функции между собой договариваются через ABI и соглашения о вызовах. На x86-64 в Linux/BSD (System V AMD64) первые шесть целочисленных аргументов идут в rdi, rsi, rdx, rcx, r8, r9, результат - в rax, стек 16-байтно выровнен. В Windows x64 - rcx, rdx, r8, r9. Пролог/эпилог функции готовит стековый фрейм, сохраняет нужные регистры и возвращает управление через ret. Эти детали важны для FFI, написания обёрток и чтения дампов стека.
Не весь код заранее компилируется в нативные инструкции. Есть байткоды и JIT. CPython исполняет байткод .pyc интерпретатором. JVM берёт .class, профилирует и JIT’ит горячие участки (HotSpot C2). .NET Core использует RyuJIT. V8 в JavaScript сначала интерпретирует (Ignition), потом оптимизирует (Turbofan). Обратная сторона - паузы и прогрев. Альтернатива - AOT: Rust и Go сразу в нативный код, Java может через GraalVM Native Image, .NET - NativeAOT. Есть и переносимый «низкоуровневый» формат - WebAssembly: компактные инструкции, безопасная песочница, исполняется в браузерах и не только.
Чуть про память и биты, которые всплывают в «высоком» коде. Большинство настольных и серверных систем - little‑endian (x86-64 таков; ARM умеет оба, но в Windows/Android/Linux обычно little‑endian). Страница памяти обычно 4 КиБ, виртуальная память изолирует процессы, а MMU мапит страницы на физику. В C/C++ есть строгая алиасинг‑модель и «неопределённое поведение»: например, переполнение знакового int - UB, и оптимизатор вправе удалять «невозможные» ветви. Отсюда редкие, но злые баги.
Как всё это применить на практике уже сегодня:
- Соберите функцию с разными оптимизациями и посмотрите ассемблер: gcc -O0/-O3 -S file.c. Обратите внимание, как меняются циклы и работают векторы (инструкции AVX2/AVX‑512).
- Разберите байты инструкции: echo -ne "\x01\xD8" | ndisasm -b32 - покажет «add eax, ebx».
- Поймайте реальные вызовы: perf top (Linux) или Windows Performance Analyzer - увидите горячие функции и их символы.
- Поиграйте с Compiler Explorer (godbolt.org): справа ассемблер, меняйте флаги и код, следите, как меняются инструкции.
И да, всё это по итогу сводится к одному: исходник превращается в машинный код, тот в процессоре разлетается по конвейеру, а ворота щёлкают такты за тактом. Разница «высокий-низкий» - это удобство для человека. Для железа это всегда нули и единицы.

Практика: смотрим и трогаем биты
Разберёмся без теории на пальцах: как увидеть байты любого файла, чем отличаются выводы на разных ОС, как проверить кодировку текста, что такое «магические» сигнатуры, и как аккуратно менять отдельные биты.
Начнём с просмотра байтов. На Linux и macOS есть hexdump и часто xxd (ставится вместе с Vim). В Windows это делает PowerShell командой Format-Hex.
Создайте маленький файл без перевода строки (чтобы не мешал байт 0A):
printf 'AЯ€' > sample.txt # macOS/Linux, UTF-8 по умолчанию # или точно те же байты: printf '\x41\xD0\xAF\xE2\x82\xAC' > sample.bin
Посмотрите шестнадцатеричный и двоичный дамп:
# macOS/Linux hexdump -C sample.txt xxd -b sample.txt # Windows PowerShell (v5+) Format-Hex .\sample.txt
Факт: hexdump -C и Format-Hex по умолчанию показывают по 16 байт в строке. xxd -b выводит биты для каждого байта, слева - смещение (offset) в файле.
Проверьте, как кодируются символы:
# Посчитать байты (не символы) wc -c < sample.txt # Linux/macOS (Get-Item .\sample.txt).Length # Windows
В UTF-8: «A» - 1 байт (0x41), «Я» - 2 байта (0xD0 0xAF), «€» - 3 байта (0xE2 0x82 0xAC). Это видно в дампе.
Если вместо printf вы сделали echo, не забудьте флаг -n. Иначе попадёт перевод строки (LF, байт 0x0A). В Windows программы иногда добавляют CRLF (0x0D 0x0A). Это не «мусор», а часть текста. Просто учитывайте.
Хотите увидеть «магические» сигнатуры файлов? Они стоят в начале и помогают системам понять тип без расширения.
Формат | Первые байты (hex) | Комментарий |
---|---|---|
PNG | 89 50 4E 47 0D 0A 1A 0A | Подпись PNG фиксирована, 8 байт |
JPEG | FF D8 FF | Начало файла (SOI + маркер) |
ZIP | 50 4B 03 04 | PK.. - заголовок локального файла |
25 50 44 46 2D | %PDF- - версия дальше | |
GIF87a/GIF89a | 47 49 46 38 37|39 61 | ASCII "GIF87a" или "GIF89a" |
Проверьте сами: возьмите любую картинку .png и сделайте hexdump первых 8 байт - сигнатура совпадёт. Это чистая проверка типа по байтам, без магии.
Теперь немного «ручной работы» с битами. Часто настройки и флаги - это набор битов в одном числе. Примеры: права доступа в Unix, сетевые флаги, код состояния.
Быстрые операции с флагами (на Python):
# флаги: 1=READ, 2=WRITE, 4=EXEC READ, WRITE, EXEC = 1, 2, 4 perm = READ | WRITE # установить READ и WRITE has_exec = (perm & EXEC) != 0 # проверить EXEC perm |= EXEC # добавить EXEC perm &= ~WRITE # убрать WRITE perm ^= READ # переключить READ
Права 644 в Unix - это 110 100 100 по тройкам: владелец rw-, группа r--, остальные r--. Быстро декодировать удобно в двоичном виде.
Аккуратно изменим байт в копии файла и посмотрим эффект. Делайте только на копиях - вы всё ломаете осознанно.
Сделайте копию PNG и испортите 1 байт сигнатуры (macOS/Linux):
cp image.png broken.png # XOR первого байта с 1: 0x89 -> 0x88 python3 - <<'PY' with open('broken.png','r+b') as f: b = bytearray(f.read(8)) b[0] ^= 0x01 f.seek(0) f.write(b) PY hexdump -C broken.png | head -n1
Большинство просмотрщиков и библиотек откажутся открывать broken.png: сигнатура не проходит проверку.
Тот же трюк в Windows PowerShell:
Copy-Item image.png broken.png $fs = [IO.File]::Open("broken.png", 'Open', 'ReadWrite') $bytes = New-Object byte[] 8 $fs.Read($bytes,0,8) | Out-Null $bytes[0] = $bytes[0] -bxor 0x01 $fs.Seek(0, 'Begin') | Out-Null $fs.Write($bytes,0,8) $fs.Close() Format-Hex .\broken.png -Count 8
Ещё один жизненный приём: проверяйте «сколько байт съел» ваш текст ввода/вывода.
Символ «Я» в UTF-8 - 2 байта. Проверка:
printf 'Я' | wc -c # 2 printf 'Я' | hexdump -C # D0 AF
Эмодзи «😀» - 4 байта (U+1F600):
printf '\xF0\x9F\x98\x80' | wc -c # 4 hexdump -C <<< $'\xF0\x9F\x98\x80'
Когда это реально помогает? В траблшутинге. Выяснить, почему сервис «ломает» строки (два лишних байта CRLF), почему JSON «не парсится» (скрытый BOM: EF BB BF), почему приложение ругается на «формат файла». Открыли дамп - увидели байты - приняли решение.
И финальный мостик: всё, что мы видим в дампе, в итоге исполняется как машинный код или трактуется как данные. Понимание уровня байтов даёт контроль: вы перестаёте гадать и начинаете проверять.
Написать комментарий