Теперь нам всем приходится работать с двоичными данными. В С++ мы работаем с последовательностями байтов, а с начала char был наш строительный блок. Определено, что sizeof из 1, это байт. И все функции ввода-вывода библиотеки используют char по умолчанию. Все хорошо, но всегда была небольшая проблема, небольшая странность, которая прослушивала некоторых людей - количество бит в байте определяется реализацией.
Итак, на C99 было решено ввести несколько typedef, чтобы разработчики могли легко выразить себя, целые типы фиксированной ширины. Необязательно, конечно, поскольку мы никогда не хотим вредить переносимости. Среди них uint8_t, перенесенный в С++ 11 как std::uint8_t, 8-разрядный целочисленный целочисленный тип с фиксированной шириной, был идеальным выбором для людей, которые действительно хотели работать с 8-битными байтами.
Итак, разработчики приняли новые инструменты и начали создавать библиотеки, которые выразительно заявляют, что они принимают 8-битные последовательности байтов, как std::uint8_t*, std::vector<std::uint8_t> или иначе.
Но, возможно, с очень глубокой мыслью, комитет по стандартизации решил не требовать реализации std::char_traits<std::uint8_t>, поэтому запрещал разработчикам легко и портативно создавать экземпляры, скажем, std::basic_fstream<std::uint8_t>, и легко читать std::uint8_t как двоичные данные. Или, может быть, некоторые из нас не заботятся о количестве бит в байте и довольны им.
Но, к сожалению, два мира сталкиваются, и иногда вам нужно взять данные как char* и передать их в библиотеку, ожидающую std::uint8_t*. Но подождите, говорят, не char переменный бит, а std::uint8_t зафиксирован на 8? Это приведет к потере данных?
Ну, на этом есть интересный Стандард. char, определяемый для хранения только одного байта и байта, является самым младшим адресуемым блоком памяти, поэтому не может быть типа с шириной бита, меньшей, чем ширина char. Затем определено, что он может удерживать UTF-8. Это дает нам минимум - 8 бит. Итак, теперь у нас есть typedef, который должен иметь ширину 8 бит и тип шириной не менее 8 бит. Но есть ли альтернативы? Да, unsigned char. Помните, что подпись char определяется реализацией. Любой другой тип? К счастью, нет. Все другие интегральные типы требуют диапазонов, которые выходят за пределы 8 бит.
Наконец, std::uint8_t является необязательным, это означает, что библиотека, которая использует этот тип, не будет компилироваться, если она не определена. Но что, если он скомпилируется? Я могу с большой долей уверенности сказать, что это означает, что мы находимся на платформе с 8-битными байтами и CHAR_BIT == 8.
Как только у нас есть это знание, у нас есть 8-битные байты, что std::uint8_t реализуется как char или unsigned char, можно предположить, что мы можем сделать reinterpret_cast от char* до std::uint8_t* и наоборот? Он переносится?
Это то, где мои навыки чтения в Стандарте не позволяют мне. Я читал о безопасно полученных указателях ([basic.stc.dynamic.safety]) и, насколько я понимаю, следующее:
std::uint8_t* buffer = /* ... */ ;
char* buffer2 = reinterpret_cast<char*>(buffer);
std::uint8_t buffer3 = reinterpret_cast<std::uint8_t*>(buffer2);
является безопасным, если мы не касаемся buffer2. Исправьте меня, если я ошибаюсь.
Итак, учитывая следующие предпосылки:
-
CHAR_BIT == 8 -
std::uint8_t.
Является ли переносной и безопасный листинг char* и std::uint8_t* назад и вперед, если предположить, что мы работаем с двоичными данными, а потенциальный недостаток знака char не имеет значения?
Я был бы признателен за ссылки на Стандарт с пояснениями.
EDIT: Спасибо, Джерри Коффин. Я собираюсь добавить цитату из стандарта ([basic.lval], §3.10/10):
Если программа пытается получить доступ к сохраненному значению объекта через значение gl, отличное от одного из следующие типы: undefined:
...
- a char или неподписанный char тип.
EDIT2: Хорошо, идем глубже. std::uint8_t не гарантируется typedef unsigned char. Он может быть реализован как расширенный беззнаковый целочисленный тип, а расширенные беззнаковые целочисленные типы не включены в §3.10/10. Что теперь?