uint8_t против неподписанного символа

231

В чем преимущество использования uint8_tнад unsigned charC?

Я знаю, что почти в каждой системе uint8_tесть просто typedef unsigned char, так зачем его использовать?

Линдон Уайт
источник

Ответы:

225

Он документирует ваше намерение - вы будете хранить маленькие цифры, а не персонажа.

Также это выглядит лучше, если вы используете другие определения типа, такие как uint16_tили int32_t.

Марк Рэнсом
источник
1
В исходном вопросе не было ясно, говорим ли мы о стандартном типе или нет. Я уверен, что за эти годы было много вариантов этого соглашения об именах.
Марк Рэнсом
8
Явное использование unsigned charили signed charдокументирование намерений тоже, так как неукрашенные charэто то, что показывает, что вы работаете с символами.
Кафе
9
Я думал, что неукрашенный unsignedбыл unsigned intпо определению?
Марк Рэнсом
5
@endolith, использование uint8_t для строки не обязательно неправильно, но определенно странно.
Марк Рэнсом
5
@endolith, я думаю, что могу сделать аргумент для uint8_t с текстом UTF8. Действительно, charкажется, подразумевает символ, тогда как в контексте строки UTF8, это может быть только один байт многобайтового символа. Использование uint8_t может прояснить, что не следует ожидать символа в каждой позиции - другими словами, что каждый элемент строки / массива является произвольным целым числом, о котором не следует делать никаких семантических предположений. Конечно, все программисты на Си знают это, но это может подтолкнуть новичков к постановке правильных вопросов.
TNE
70

Просто чтобы быть педантичным, некоторые системы могут не иметь 8-битного типа. Согласно Википедии :

Реализация должна определять целочисленные типы с точной шириной для N = 8, 16, 32 или 64 тогда и только тогда, когда она имеет какой-либо тип, соответствующий требованиям. Не требуется определять их для любого другого N, даже если он поддерживает соответствующие типы.

Так uint8_tчто не гарантируется существование, хотя это будет для всех платформ, где 8 бит = 1 байт. Некоторые встроенные платформы могут отличаться, но это происходит очень редко. Некоторые системы могут определять charтипы как 16-битные, и в этом случае, вероятно, не будет 8-битного типа.

Помимо этой (незначительной) проблемы, ответ @Mark Ransom является лучшим, на мой взгляд. Используйте тот, который наиболее четко показывает, для чего вы используете данные.

Кроме того, я предполагаю, что вы имели в виду uint8_t(стандартный typedef от C99, представленный в stdint.hзаголовке), а не uint_8(не является частью какого-либо стандарта).

Крис Лутц
источник
3
@caf, из чистого любопытства - можете ли вы дать ссылку на описание некоторых? Я знаю, что они существуют, потому что кто-то упомянул один (и связанный с ним документацию для разработчиков) в модерируемом обсуждении comp.lang.c ++. О том, слишком ли слабы гарантии типа C / C ++, но я больше не могу найти этот поток, и это всегда удобно ссылаться на это в любых подобных обсуждениях :)
Павел Минаев
3
«Некоторые системы могут определять типы символов как 16-битные, в этом случае, вероятно, не будет 8-битного типа». - и несмотря на некоторые неправильные возражения от меня, Павел продемонстрировал в своем ответе, что если char равен 16 битам, то даже если компилятор предоставляет 8-битный тип, он не должен вызывать его uint8_t(или typedef его к этому). Это связано с тем, что 8-битный тип имел бы неиспользуемые биты в представлении хранилища, чего uint8_tне должно быть.
Стив Джессоп
3
Архитектура SHARC имеет 32-битные слова. Смотрите en.wikipedia.org/wiki/… для подробностей.
BCran
2
А ЦСП T5 C5000 (которые были в OMAP1 и OMAP2) являются 16-битными. Я думаю, что для OMAP3 они пошли в серию C6000 с 8-битным символом.
Стив Джессоп
4
Копаясь в N3242 - «Рабочий проект, стандарт для языка программирования C ++», раздел 18.4.1 <cstdint> В синопсисе говорится - typedef unsigned integer type uint8_t; // optional Так что, по сути, стандартная соответствующая библиотека C ++ вообще не нужна для определения uint8_t (см. Комментарий // необязательно )
nightlytrails
43

Весь смысл в том, чтобы написать независимый от реализации код. unsigned charне гарантируется быть 8-битным типом. uint8_tесть (если есть).

Муравей
источник
4
... если он существует в системе, но это будет очень редко. +1
Крис Лутц
2
хорошо, если у вас действительно были проблемы с вашим кодом, не компилируемым в системе, потому что uint8_t не существует, вы можете использовать find и sed, чтобы автоматически изменять все вхождения uint8_t на unsigned char или что-то более полезное для вас.
Bazz
2
@bazz - нет, если вы предполагаете, что это 8-битный тип, который вы не можете - например, для распаковки данных, упакованных в байтовом режиме удаленной системой. Неявное предположение заключается в том, что причина отсутствия uint8_t заключается в процессоре, в котором значение char превышает 8 бит.
Крис Страттон
добавить утверждение assert (sizeof (unsigned char) == 8);
Bazz
3
Боюсь, неверное утверждение @bazz. sizeof(unsigned char)вернется 1за 1 байт. но если системные char и int имеют одинаковый размер, например, 16-битный, то sizeof(int)также вернется1
Тоби
7

Как вы сказали, « почти каждая система».

charВероятно, это один из менее вероятных изменений, но как только вы начнете использовать uint16_tи друзей, uint8_tлучше использовать смеси, и даже можете стать частью стандарта кодирования.

Только в любви
источник
7

По моему опыту, есть два места, где мы хотим использовать uint8_t для обозначения 8 бит (и uint16_t и т. Д.) И где мы можем иметь поля размером менее 8 бит. В обоих случаях пространство имеет значение, и нам часто приходится смотреть на необработанный дамп данных при отладке и иметь возможность быстро определить, что он представляет.

Первый касается радиочастотных протоколов, особенно в узкополосных системах. В этой среде нам может понадобиться собрать как можно больше информации в одно сообщение. Второй - во флэш-памяти, где у нас может быть очень ограниченное пространство (например, во встроенных системах). В обоих случаях мы можем использовать упакованную структуру данных, в которой компилятор позаботится о упаковке и распаковке для нас:

#pragma pack(1)
typedef struct {
  uint8_t    flag1:1;
  uint8_t    flag2:1;
  padding1   reserved:6;  /* not necessary but makes this struct more readable */
  uint32_t   sequence_no;
  uint8_t    data[8];
  uint32_t   crc32;
} s_mypacket __attribute__((packed));
#pragma pack()

Какой метод вы используете, зависит от вашего компилятора. Вам также может потребоваться поддержка нескольких разных компиляторов с одинаковыми заголовочными файлами. Это происходит во встроенных системах, где устройства и серверы могут быть совершенно разными - например, у вас может быть устройство ARM, которое взаимодействует с сервером Linux x86.

Есть несколько предостережений с использованием упакованных структур. Самое важное, что вы должны избегать разыменования адреса члена. В системах с выровненными по многобайтовым словам словами это может привести к смещенному исключению - и coredump.

Некоторые люди также будут беспокоиться о производительности и утверждают, что использование этих упакованных структур замедлит работу вашей системы. Это правда, что за кулисами компилятор добавляет код для доступа к невыровненным элементам данных. Это можно увидеть, посмотрев код сборки в вашей IDE.

Но поскольку упакованные структуры наиболее полезны для связи и хранения данных, данные могут быть извлечены в неупакованное представление при работе с ним в памяти. Обычно нам вообще не нужно работать со всем пакетом данных в памяти.

Вот некоторые соответствующие обсуждения:

пакет pragma (1) и __attribute__ ((выровненный (1))) работает

Небезопасен ли пакет gcc для __attribute __ ((упакованный)) / #pragma?

http://solidsmoke.blogspot.ca/2010/07/woes-of-structure-packing-pragma-pack.html

Тереус Скотт
источник
6

Там мало С точки зрения переносимости, он charне может быть меньше 8 бит, и ничто не может быть меньше char, поэтому, если данная реализация C имеет 8-разрядный целочисленный тип без знака, это произойдет char. Альтернативно, у этого может не быть вообще ни одного, в котором пункте любые typedefуловки являются спорными.

Это может быть использовано для лучшего документирования вашего кода в том смысле, что вам очевидно, что вам нужны 8-битные байты и больше ничего. Но на практике это разумное ожидание практически где-то уже (есть платформы DSP, на которых это не так, но шансы на то, что ваш код работает там, невелики, и вы могли бы также с ошибкой использовать статическое утверждение вверху вашей программы на такая платформа).

Павел Минаев
источник
7
@Skizz - Нет, стандарт требует, unsigned charчтобы можно было хранить значения в диапазоне от 0 до 255. Если вы можете сделать это в 4 битах, моя шляпа для вас.
Крис Латс
1
«это было бы немного более громоздко» - громоздко в том смысле, что вам нужно было бы пройти (плыть, сесть на самолет и т. д.) до самого места, где находился автор компилятора, хлопнуть их по затылку и заставить их добавить uint8_tк реализации. Интересно, компиляторы для DSP с 16-битными символами обычно реализуют uint8_tили нет?
Стив Джессоп
6
Кстати, если подумать, это, пожалуй, самый простой способ сказать: «Мне действительно нужно 8 бит» - #include <stdint.h>и использовать uint8_t. Если у платформы есть это, это даст это Вам. Если у платформы его нет, ваша программа не будет компилироваться, и причина будет ясна и понятна.
Павел Минаев
2
Все еще нет сигары, извините: «Для целых типов без знака, кроме беззнакового символа, биты представления объекта должны быть разделены на две группы: биты значения и биты заполнения ... Если имеется N битов значения, каждый бит должен представлять разные степень 2 между 1 и 2 ^ (N-1), так что объекты этого типа должны быть способны представлять значения от 0 до 2 ^ (N-1), используя чисто двоичное представление ... Имя определения типа intN_t обозначает целочисленный тип со знаком со шириной N, без дополнительных битов и дополнения до двух. "
Павел Минаев
1
Если вам просто нужна арифметика по модулю, битовое поле без знака будет работать нормально (если неудобно). Это когда вам нужен, скажем, массив октетов без заполнения, тогда вы - SOL. Мораль этой истории не в том, чтобы кодировать DSP, а в том, чтобы придерживаться правильной, честной 8-битной архитектуры символов :)
Павел Минаев
4

Это действительно важно, например, когда вы пишете сетевой анализатор. заголовки пакетов определяются спецификацией протокола, а не тем, как работает компилятор C конкретной платформы.

VP.
источник
Когда я спрашивал об этом, у меня был простой протокол обмена данными по серийному номеру.
Линдон Уайт
2

Почти на каждой системе я встречал uint8_t == unsigned char, но это не гарантируется стандартом C. Если вы пытаетесь написать переносимый код, и важно, какой именно размер памяти, используйте uint8_t. В противном случае используйте неподписанный символ.

atlpeg
источник
3
uint8_t всегда соответствует диапазону, размеру unsigned charи отступу (нет), когда unsigned char 8-битный. Когда unsigned charне 8-битный, uint8_tне существует.
chux - Восстановить Монику
@chux, у тебя есть ссылка на точное место в стандарте, где это сказано? Если unsigned charэто 8-бит, будет uint8_tгарантированно будет typedefих , а не typedefиз расширенного целого числа без знака типа ?
Сивонен
@hsivonen "точное место в стандарте, где говорится это?" -> Нет - пока посмотрите 7.20.1.1. Он легко выводится, как unsigned char/signed char/charи самый маленький тип - не менее 8 бит. unsigned charне имеет отступов. Для того, uint8_tчтобы быть, это должно быть 8 битов, без заполнения, существуют из-за реализации целочисленного типа, обеспеченного: соответствие минимальным требованиям unsigned char. Что касается "... гарантированно будет typedef ...", то это хороший вопрос для публикации.
chux - Восстановить Монику