Теперь нам всем приходится работать с двоичными данными. В С++ мы работаем с последовательностями байтов, а с начала 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. Что теперь?