Худшие побочные эффекты от подписи символов. (Объяснение эффектов подписи для символов и приведений) - PullRequest
12 голосов
/ 03 февраля 2010

Я часто работаю с библиотеками, которые используют char при работе с байтами в C ++. Альтернатива состоит в том, чтобы определить «байт» как неподписанный символ, но это не тот стандарт, который они решили использовать. Я часто передаю байты из C # в библиотеки C ++ и преобразую их в char для работы с библиотекой.

При приведении целых чисел к символам или символам других простых типов возможны побочные эффекты. В частности, когда у вас был сломанный код, над которым вы работали, и как вы узнали, что он был из-за подписи символа?

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

Ответы [ 8 ]

4 голосов
/ 03 февраля 2010

Одним из основных рисков является необходимость сдвига байтов. Знаковый символ сохраняет бит знака при смещении вправо, тогда как неподписанный символ - нет. Вот небольшая тестовая программа:

#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 голосов
/ 03 февраля 2010

Наиболее очевидные ошибки возникают, когда вам нужно сравнить числовое значение 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
1 голос
/ 03 февраля 2010

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

Я узнал, когда начал получать странные результаты, и ошибки, возникающие при поиске текстов, отличных от одногоЯ использовал во время начальной разработки (очевидно, что символы со значениями> 127 или <0 будут вызывать это и не обязательно будут присутствовать в ваших типичных текстовых файлах. </p>

Всегда проверяйте подпись переменной при работеобычно я делаю типы подписанными, если у меня нет веских причин, приводя их к необходимости. Это прекрасно согласуется с повсеместным использованием char в библиотеках для простого представления байта. Имейте в виду, что подпись char не определено (в отличие от других типов), вам следует уделить ему особое внимание и быть внимательным.

1 голос
/ 03 февраля 2010

Тот, который меня больше всего раздражает:

typedef char byte;

byte b = 12;

cout << b << endl;

Конечно, это косметика, но обр ...

0 голосов
/ 12 июня 2010

Знак расширения. Первая версия моей функции кодирования URL производила строки вроде «% FFFFFFA3».

0 голосов
/ 03 февраля 2010

Спецификации языка C и C ++ определяют 3 типа данных для хранения символов: char, signed char и unsigned char. Последние 2 были обсуждены в других ответах. Давайте посмотрим на тип char.

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

Таким образом, существует 3 версии типа данных char. Тип данных char хорошо работает для хранения символов, но не подходит для арифметики на платформах и трансляторах, поскольку его подписность определяется реализацией.

0 голосов
/ 03 февраля 2010

Вы потерпите неудачу при компиляции для нескольких платформ, потому что стандарт C ++ не определяет char, чтобы иметь определенную "подпись".

Поэтому GCC вводит опции -fsigned-char и -funsigned-char длязаставить определенное поведение.Более подробную информацию по этой теме можно найти, например, здесь .

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

Как вы просили привести примеры неработающего кода, существует множествоо возможностях взломать код, который обрабатывает двоичные данные.Например, изображение обрабатывает 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);

Надеюсь, вам понравился этот пример ;-) Но, честно говоря, я никогда не сталкивался с такими проблемами, дажекак начинающий, насколько я помню ...

Надеюсь, этого ответа достаточно для вас, внизу.Как насчет короткого комментария?

0 голосов
/ 03 февраля 2010

При преобразовании целых чисел в символы или символы в другие простые типы

Критическим моментом является то, что приведение значения со знаком из одного примитивного типа в другой (больший) тип не сохраняет битовую комбинацию (при условии дополнения до двух). Символ со знаком с битовой комбинацией 0xff равен -1, а знак со знаком с десятичным значением -1 равен 0xffff. Однако приведение беззнакового символа со значением 0xff к беззнаковому короткому дает 0x00ff. Поэтому всегда думайте о правильной подписи, прежде чем вводить тип данных большего или меньшего размера. Никогда не переносите неподписанные данные в подписанные типы данных, если вам не нужно - если внешняя библиотека заставляет вас сделать это, выполните преобразование как можно позже (или как можно раньше, если внешний код действует в качестве источника данных).

...