Худшие побочные эффекты от подписки на символы. (Объяснение эффектов подписи на символах и приведениях)
Я часто работаю с библиотеками, которые используют char при работе с байтами на С++. Альтернативой является определение байта как unsigned char, но это не тот стандарт, который они решили использовать. Я часто передаю байты из С# в С++ dll и отсылаю их к char для работы с библиотекой.
При нажатии ints на символы или символы другим простым типам некоторые из побочных эффектов могут возникать. В частности, когда у вас есть этот сломанный код, над которым вы работали, и как вы узнали, что это связано с подписью char?
Lucky, я не столкнулся с этим в своем коде, использовал char подписанный трюк для кастинга во встроенном классе систем в школе. Я ищу, чтобы лучше понять этот вопрос, поскольку считаю, что это относится к работе, которую я делаю.
Ответы
Ответ 1
Одним из основных факторов риска является необходимость сдвига байтов. Подписанный char хранит знаковый бит при правом смещении, тогда как без знака char нет.
Здесь небольшая тестовая программа:
#include <stdio.h>
int main (void)
{
signed char a = -1;
unsigned char b = 255;
printf("%d\n%d\n", a >> 1, b >> 1);
return 0;
}
Он должен печатать -1 и 127, даже если a и b начинаются с одного и того же шаблона бита (с учетом 8-битных символов, двухзначных и подписанных значений с использованием арифметического сдвига).
Короче говоря, вы не можете полагаться на то, что сдвиг работает одинаково для подписанных и неподписанных символов, поэтому, если вам нужна переносимость, используйте unsigned char
, а не char
или signed char
.
Ответ 2
Наиболее очевидные ошибки возникают, когда вам нужно сравнить числовое значение char
с шестнадцатеричной константой при реализации протоколов или схем кодирования.
Например, при реализации telnet вы можете сделать это.
// Check for IAC (hex FF) byte
if (ch == 0xFF)
{
// ...
Или при тестировании многобайтовых последовательностей UTF-8.
if (ch >= 0x80)
{
// ...
К счастью, эти ошибки обычно не выживают очень долго, так как даже самые беглые тесты на платформе с подписанным char
должны их раскрывать. Они могут быть исправлены с использованием символьной константы, преобразования числовой константы в char
или преобразования символа в unsigned char
, прежде чем оператор сравнения продвинет оба на int
. Однако преобразование char
непосредственно в unsigned
не будет работать.
if (ch == '\xff') // OK
if ((unsigned char)ch == 0xff) // OK, so long as char has 8-bits
if (ch == (char)0xff) // Usually OK, relies on implementation defined behaviour
if ((unsigned)ch == 0xff) // still wrong
Ответ 3
Тот, который меня больше всего раздражает:
typedef char byte;
byte b = 12;
cout << b << endl;
Конечно, это косметика, но arrr...
Ответ 4
Я был укушен знаком char в написании алгоритмов поиска, которые использовали символы из текста в качестве индексов в деревьях состояний. У меня также возникли проблемы при расширении символов в более крупные типы, и бит знака распространяется, вызывая проблемы в другом месте.
Я узнал, когда начал получать причудливые результаты, и segfaults, возникающие в результате поиска текстов, отличных от тех, которые я использовал во время начальной разработки (очевидно, что символы со значениями > 127 или 0 будут вызывать это и выиграть Обязательно присутствуйте в типичных текстовых файлах.
Всегда проверяйте переменную подписи при работе с ней. В общем, теперь я делаю типы подписанными, если у меня нет веских причин, иначе, при необходимости, при необходимости. Это хорошо вписывается в вездесущее использование char
в библиотеках, чтобы просто представлять байт. Имейте в виду, что подпись char
не определена (в отличие от других типов), вы должны уделять ей особое внимание и быть внимательным.
Ответ 5
При запуске ints для символов или символов другим простым типам
Критическая точка заключается в том, что приведение знакового значения из одного примитивного типа в другой (более крупный) тип не сохраняет битовый шаблон (предполагая два дополнения). Подписанный char с битовой схемой 0xff
равен -1, а подписанный короткий с десятичным значением -1 - 0xffff
. Однако выдача без знака char со значением 0xff
в unsigned short, дает 0x00ff
. Поэтому всегда думайте о правильной подписке перед тем, как придать тип более крупному или меньшему типу данных. Никогда не носите неподписанные данные в подписанных типах данных, если вам не нужно - если внешняя библиотека заставляет вас это делать, сделайте преобразование как можно позже (или как можно раньше, если внешний код выступает в качестве источника данных).
Ответ 6
Вы будете терпеть неудачу при компиляции для нескольких платформ, потому что стандарт С++ не определяет char
как определенную "подпись".
Поэтому GCC вводит опции -fsigned-char
и -funsigned-char
для принудительного выполнения определенного поведения. Например, здесь можно найти здесь.
EDIT:
Как вы просили примеры неработающего кода, существует множество возможностей для разрыва кода, обрабатывающего двоичные данные. Например, изображение обрабатывает 8-битные звуковые сэмплы (диапазон от -128 до 127), и вы хотите уменьшить громкость в два раза. Теперь представьте себе этот сценарий (в котором наивный программист предполагает char == signed char
):
char sampleIn;
// If the sample is -1 (= almost silent), and the compiler treats char as unsigned,
// then the value of 'sampleIn' will be 255
read_one_byte_sample(&sampleIn);
// Ok, halven the volume. The value will be 127!
char sampleOut = sampleOut / 2;
// And write the processed sample to the output file, for example.
// (unsigned char)127 has the exact same bit pattern as (signed char)127,
// so this will write a sample with the loudest volume!!
write_one_byte_sample_to_output_file(&sampleOut);
Надеюсь, вам понравится этот пример;-) Но, честно говоря, я никогда не сталкивался с такими проблемами, даже будучи новичком, насколько я помню...
Надеюсь, что этого ответа достаточно для вас. Как насчет короткого комментария?
Ответ 7
Спецификации языка C и С++ определяют 3 типа данных для хранения символов: char
, signed char
и unsigned char
. Последние 2 обсуждались в других ответах. Посмотрим на тип char
.
Стандарт говорит, что тип данных char
может быть подписан или без знака и является решением для реализации. Это означает, что некоторые компиляторы или версии компиляторов могут реализовать char
по-разному. Подразумевается, что тип данных char
не способствует арифметическим или булевым операциям. Для арифметических и булевых операций версии signed
и unsigned
версии char
будут работать нормально.
Таким образом, существует 3 версии типа данных char
. Тип данных char
хорошо подходит для хранения символов, но не подходит для арифметики между платформами и переводчиками, поскольку его подпись является определенной реализацией.
Ответ 8
Расширение знака. Первая версия моей функции кодирования URL-адресов создала строки типа "% FFFFFFA3".