Есть такое понятие как byte и нормальные люди думаю, что это просто uint8_t
,
то есть байт это просто переменная объёмам восемь бит, ну или просто двоичное «число» включающие в себя восемь бит.
Те же из нас,
что читали стандарт языка Си ISO-9899, при том выпущенный до 2023 года знают что byte это вообще то
минимальный адресуемый тип, char
в Си.
Дело в том, что Си был разработан на выдуманных архитектурах,
на которых длина байта была совсем не всегда восемь бит.
Понимаете, вы не можете просто так (без bitfields) сделать переменную меньше длины
байта, даже булевые значения принимающие либо true либо false занимают весь байт целиком,
если не больше, потому что иногда большие типы могут быть быстрее.
Конечно, сишный стандарт 2023 года отменил это правило и теперь байт это восемь бит, но это факт подводит нас к другому важном факту – кодировка ascii изначально была семибитной. И это было потому что такой длины был байт.
Под ЭВМ с таким байтом разработали операционную систему UNIX и язык программирования Си. Это было в какой-то степени прорывом, потому что UNIX был не первой, но одной из первых многопользовательских систем разделения времени с вытесняющей многозадачностью. Расшифрую: в этой операционной системе могли одновременно работать несколько человек и запускать несколько программ. Звучит банально, но это был 1970 год, а в начале девяностых люди ещё использовали DOS и MAC OS 9 в которых этого не было. На DOS вы не могли запустить одновременно несколько программ. Точнее это не было так просто.
Язык Си же, описываемый создателями, в то время, как высокоуровневый, позволял написать код на машине с одной архитектурой и скомпилировать на другой. Поэтому сейчас это один из всеми нелюбимых языков системного программирования.
Си также называют кроссплатформенным ассемблером и это даже отчасти справедливо:
дело в том, как в нём хранятся строки.
Хранятся просто, как массив байт то есть сhar str[SIZEBUF];
,
а в конце этого массива символ \0
,
который не символ вовсе, а так называемое магическое число.
То есть, если в паскале, так, очень зря, ненавидимом школотой и нормальными людьми
используется подход когда перед началом строки в памяти лежит число символов в строке,
то в Си когда мы, например, вычисляем длину строки мы просто проходим по каждому
символу до того как не встритим магическое число. Буквально так работает функция strlen(3)
Так было принято хранить строки у программистов на ассемблере.
Число магичское не потому что делает что-то полезное вроде хранения длины строки,
а потому что помечает собой конец строки.
В кодировке ascii и в языке Си эта управляющая последовательность и число равное нулю.
A если точнее 00000000
, то есть байту включающему в себя только биты в значении ноль.
Так сложились обстоятельства, что в мире серверных решений победили операционные
системы на ядре Linux или другие UNIX-like системы.
А так как всё это наследники того UNIX, с тем самым языком Си они
не могли нормально использовать кодировки текста в которых был бы символ \0
равный 00000000
. Ну просто слишком много кода уже от этого зависело.
Программист Кен Томпсон, работник Bell Labs, в котором это самый UNIX и создали,
придумал решешние проблемы кодировок, которая состояла в том как используя
всего байт показывать гигантское число символов из разных письменностей
стандартизированных консорциумом юникода и при этом не упираться в проблему с
управляющим символом \0
.
Ответ был такой: кодировки с переменной длиной. Про это хорошо объяснил Том Скотт: https://youtu.be/MijmeoH9LT4
Есть мануал OpenBSD: https://man.openbsd.org/man7/utf8.7
Но и я попробую:
UTF-8 полностью cовместима с ascii используя те же значения для символов, но
как выше написано ascii была изначально семибитной, то есть первый бит
в байте у ascii равнялся нулю и в диапозоне 00000001
и 01111111
находились все символы ascii, то есть первый бит был свободен.
В UTF-8 сделали так, если первый бит был равен единице, то
символ не входил в подмножество ascii (Да, UTF-8 это множество, а ascii его подмножество).
А если второй бит был равен единице, то это означало, что символ, а точнее руна, в терминологии
языка Go, состояла из двух байт.
Это выглядело так:
1100xxxx
это первый байт (x
здесь означает любое принимаемое значение),
так как первые два бита равны единице, то ожидаем второй байт,
он кстати обязан также начинаться с единицы, чтобы не возникло проблемы с \0
.
10xxxxxx
это второй байт он начинается с бита равного единице и бита равного нулю.
Остальные биты кодируют символ.
Если третий бит первого байта был также равен единице, то следовало ожидать ещё три байта,
то есть: видим три единицы в начале байта 1110xxxx
и ожидаем ещё три два байта, которые
будут начинаться с единицы и нуля.
Пример последовательности байт для UTF-8 руны из четырёх байт:
11110xxx
, 10xxxxxx
, 10xxxxxx
, 10xxxxxx
Это позволяет использовать очень много письменностей и сохранять обратную совместимость.
В OpenBSD не так давно решили также во всю использовать кодировку UTF-8, вместо любых других возможных. Для локализации многих программ используется функция isutf8cont, которая берёт байт и проверяте равны ли первые два бита единице и нулю соотвественно.
Это кодировка, которая портит мне жизнь в винде. В отличие от UTF-8 она не переменной длины и символ занимает один байт. То что первый бит ascii ни как не используется, позволят на его место поставить единицу и получить ещё 127 значений, которые можно распределить под символы. В CP1251 это символы кириллических алфавитов.
https://manpages.ubuntu.com/manpages/bionic/en/man7/cp1251.7.html
В CP1252 то символы других европейских языков с латинским алфавитом.
**Такие кодировки позволяют использовать гораздо меньше письменностей**