C / C ++ Зачем использовать unsigned char для двоичных данных?

Это действительно необходимо использовать unsigned char хранить двоичные данные, как в некоторых библиотеках, которые работают с кодировкой символов или двоичными буферами? Чтобы понять мой вопрос, взгляните на код ниже:

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

оба printf's выход �� правильно, где f0 a4 ad a2 кодировка для кодовой точки Unicode U+24B62 (��) в шестнадцатеричном виде

Четное memcpy также правильно скопировал биты, хранящиеся в символе.

Какие рассуждения могут способствовать использованию unsigned char вместо plain char?

По другим связанным вопросам unsigned char выделен, потому что это единственный (байтовый / наименьший) тип данных, который гарантированно не будет заполнен C-спецификацией. Но, как показано в приведенном выше примере, на вывод не влияют никакие дополнения как таковые.

Я использовал VC ++ Express 2010 и MinGW для компиляции выше. Хотя ВК дал предупреждение

warning C4309: '=' : truncation of constant value

вывод не отражает это.

Постскриптум Это может быть отмечено возможным дубликатом Должен ли быть буфер байтов со знаком или без знака в буфере символов? но мое намерение другое. Я спрашиваю, почему то, что, кажется, работает так хорошо с char должен быть напечатан unsigned char?

Обновить: Цитировать из N3337,

Section 3.9 Types

2 Для любого объекта (кроме подобъекта базового класса) тривиально
копируемый тип T, содержит ли объект допустимое значение типа
T, лежащие в основе байты (1.7), составляющие объект, могут быть скопированы в
массив char или unsigned char. Если содержимое массива char
или беззнаковый символ копируется обратно в объект, объект должен
впоследствии сохраните свое первоначальное значение.

Ввиду вышеуказанного факта и того, что мой оригинальный пример был на машине Intel, где char по умолчанию signed charЯ до сих пор не уверен, если unsigned char должно быть предпочтительнее, чем char,

Что-нибудь еще?

46

Решение

В С unsigned char тип данных является единственным типом данных, который имеет все следующие три свойства одновременно

  • он не имеет битов заполнения, поэтому все биты хранения вносят вклад в значение данных
  • никакая побитовая операция, начинающаяся со значения этого типа, при преобразовании обратно в этот тип не может привести к переполнению, представлению ловушек или неопределенному поведению
  • он может создавать псевдонимы других типов данных без нарушения «правил псевдонимов», то есть доступ к одним и тем же данным через указатель, который вводится по-разному, будет гарантированно видеть все модификации

если это свойства «двоичного» типа данных, который вы ищете, вам обязательно следует использовать unsigned char,

Для второго свойства нам нужен тип, который unsigned, Для них все преобразования определяются по модулю arihmetic, здесь по модулю UCHAR_MAX+1, 256 в большинстве 99% архитектур. Все преобразования более широких значений в unsigned char тем самым просто соответствует усечению до младшего байта.

Два других типа символов обычно не работают одинаково. signed char во всяком случае, подписано, поэтому преобразование значений, которые ему не подходят, не очень хорошо определено. char не фиксируется как подписанный или неподписанный, но на конкретной платформе, на которую переносится ваш код, он может быть подписан, даже если он не подписан на вашей.

79

Другие решения

Простой char Тип проблематичен и не должен использоваться ни для чего, кроме строк. Основная проблема с char в том, что вы не можете знать, подписан он или нет, это поведение, определяемое реализацией. Это делает char отличный от int так далее, int всегда гарантированно будет подписано.

Хотя ВК дал предупреждение … усечение константы

Он говорит вам, что вы пытаетесь хранить литералы int внутри переменных типа char. Это может быть связано со подписью: если вы попытаетесь сохранить целое число со значением> 0x7F внутри подписанного символа, могут произойти непредвиденные ситуации. Формально, это неопределенное поведение в C, хотя практически вы просто получите странный вывод, если попытаетесь напечатать результат в виде целочисленного значения, хранящегося в (подписанном) символе.

В этом конкретном случае предупреждение не должно иметь значения.

РЕДАКТИРОВАТЬ :

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

Теоретически, все целочисленные типы, кроме беззнакового символа и знакового символа, могут содержать «биты заполнения», согласно C11 6.2.6.2:

«Для целочисленных типов без знака, кроме беззнаковых символов, биты
представление объекта должно быть разделено на две группы: биты значения и
биты заполнения (не должно быть ни одного из последних). «

«Для целочисленных типов со знаком биты представления объекта должны
разделить на три группы: биты значения, биты заполнения и знак
немного. Там не должно быть никаких битов заполнения; подписанный чар не должен иметь
любые биты заполнения. «

Стандарт C намеренно расплывчатый и нечеткий, что позволяет использовать эти теоретические биты заполнения, потому что:

  • Он допускает таблицы символов, отличные от стандартных 8-битных.
  • Он допускает определяемую реализацией подписанность и странные знаковые целочисленные форматы, такие как дополнение или «знак и величина».
  • Целое число может не обязательно использовать все выделенные биты.

Однако в реальном мире за пределами стандарта C применяется следующее:

  • Таблицы символов почти наверняка 8-битные (UTF8 или ASCII). Существуют некоторые странные исключения, но чистые реализации используют стандартный тип wchar_t при реализации таблиц символов размером более 8 бит.
  • Подпись всегда является дополнением к двум.
  • Целое число всегда использует все выделенные биты.

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

12

Вы получите большинство своих проблем при сравнении содержимого отдельных байтов:

char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
printf("good\n");
}
else
{
printf("bad\n");
}

может вывести «bad», потому что, в зависимости от вашего компилятора, c [0] будет расширяться до -1, что совсем не то же самое, что 0xff

12

Байты обычно предназначены как 8-битные целые числа без знака.

Теперь char не указывает знак целого числа: на некоторых компиляторах char может быть подписан, на других он может быть без знака.

Если я добавлю операцию сдвига в код, который вы написали, то у меня будет неопределенное поведение. Добавленное сравнение также будет иметь неожиданный результат.

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';
c[0] >>= 1; // If char is signed, will the 7th bit go to 0 or stay the same?

bool isBiggerThan0 = c[0] > 0; // FALSE if char is signed!

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

Что касается предупреждения во время компиляции: если char подписан, то вы пытаетесь присвоить значение 0xf0, которое не может быть представлено в подписанном char (в диапазоне от -128 до +127), поэтому оно будет приведено к значению со знаком (- 16).

Объявление char как подписанного уберет предупреждение, и всегда хорошо иметь чистую сборку без какого-либо предупреждения.

5

Подпись равнины char Тип определяется реализацией, поэтому, если вы на самом деле не имеете дело с символьными данными (строка, использующая набор символов платформы — обычно ASCII), обычно лучше явно указывать подпись, используя либо signed char или же unsigned char,

Для двоичных данных наилучший выбор наиболее вероятен unsigned charособенно, если с данными будут выполняться побитовые операции (в частности, сдвиг битов, который для подписанных типов ведет себя не так, как для неподписанных типов).

4

Я спрашиваю, почему то, что, кажется, работает нормально с char, должно быть напечатано без знака char?

Если вы делаете вещи, которые не являются «правильными» в смысле стандарта, вы полагаетесь на неопределенное поведение. Ваш компилятор может сделать это так, как вы хотите сегодня, но вы не знаете, что он сделает завтра. Вы не знаете, что делает GCC или VC ++ 2012. Или даже если поведение зависит от внешних факторов или отладки / выпуска и т. Д. Как только вы выходите из безопасного пути стандарта, вы можете столкнуться с проблемами.

2

Ну, что вы называете «двоичными данными»? Это набор битов, без какого-либо значения, присваиваемого им той конкретной частью программного обеспечения, которая называет их «двоичными данными». Какой тип данных наиболее близок к примитиву, который передает идею отсутствия какого-либо конкретного значения для любого из этих битов? Я думаю unsigned char,

2

Действительно ли необходимо использовать unsigned char для хранения двоичных данных, как в некоторых библиотеках, работающих с кодировкой символов или двоичными буферами?

«действительно» необходимо? Нет.

Это очень хорошая идея, и для этого есть много причин.

В вашем примере используется printf, который не является типобезопасным. То есть printf получает сигналы форматирования из строки формата, а не из типа данных. Вы могли бы так же легко попробовать:

printf("%s\n", (void*)c);

… и результат был бы таким же. Если вы попробуете то же самое с c ++ iostreams, результат будет другим (в зависимости от подписи c).

Какие рассуждения могут способствовать использованию неподписанного символа вместо простого символа?

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

2