Поведение целочисленных типов
19/01/2010 - 07:33
Pavel Bobkov
Несколько примеров возможного запутанного поведения описаны ниже и это далеко не то, что бы вы назвали закоулками языка Си, выражения подобные этим можно найти в реальном приложении. Но прежде чем погрузиться в поведение, которое может показаться запутанным, давайте сначала разберемся, что стандарт говорит относительно целочисленного расширения и целочисленных констант.
Целочисленное расширение
Си стандарт 6.3.1.1: Если int может представить все значения оригинального типа, значение преобразуется к int; в противном случае, значение преобразуется к unsigned int. Это называется целочисленное расширение. Целочисленное расширение сохраняет значение переменной, включая ее знак.
Согласно 6.3.1.1, bool, char, signed, unsigned char, short int и перечислимые типы данных конвертируются к int или unsigned int целочисленным расширением, когда они используются в выражениях. В большинстве случаев это дает результат, который вы бы получили из выражения математически. Например:
char c1,c2,c3;
c1 = 0xA
c2 = 0xB;
c3 = c1+c2;
Для компилятора с 16 разрядным типом int, это бы было выполнено как суммирование int с усечением результата к char
0x0A + 0x0B --> 0x000A+ 0x000B --> 0x0015 --> 0x15.Заметьте, что для этого примера, результирующее значение вписывается в тип адресата. Когда этого не происходит, ситуация становится немного более сложной.
Константы целочисленного типа
Согласно параграфу 6.4.4.1, тип целочисленной константы задается ее суффиксом (таким как – u или U – unsigned, l или L – long, ll или LL – long long и т.д.) и основанием системы исчисления (десятичной, восмеричной, шестнадцатиричной). Тип безсуффиксной шестнадцатиричной константы, в которой она может быть представлена – int, unsigned int, long int, unsigned long int, long long int, unsigned long long int. Интересно, что наименьший тип это int, а не char. Например:
char c1;
c1 = 0xA
Для компилятора с 16 разрядным типом int, присваивание требует усечения. 0xA -> 0x000A и это число должно быть усечено, чтобы соответствовать char.
Возможное запутанное поведение
Правила для целочисленных типов и их преобразования в некоторых ситуациях могут привести к запутывающему поведению. Например, присваивания и/или условные выражения, включающие типы с различным размером, и/или логические операции, например побитовая инверсия. Типы здесь также включают типы констант.
В некоторых случаях при компиляции могут быть предупреждения (например, "constant conditional" – постоянное условное выражение, "pointless comparison" – бессмысленное сравнение), в других вы просто получите результат отличный от ожидаемого. При определенных обстоятельствах компилятор может выдать предупреждения только при высоком уровне оптимизации.
Пример 1
void f1(unsigned char c1)
{
if (c1 == ~0x80)
;
}
Здесь тест всегда ложь!
И вот почему. С правой стороны: 0х80->0x0080 и ~0x0080 --> 0xFF7F. С левой стороны переменная с1 – unsigned char. Она должна быть меньше чем 256 и положительной, таким образом, после целочисленного расширения ее значение никогда не будет иметь установленными больше 8 бит. (Например, если с1 равна 0xFF, после расширения 0хFF -> 0x00FF)
Пример 2
void f2(void)
{
char c1;
c1 = ~0x80;
if (c1 == ~0x80)
;
}
Поразрядное отрицание выполняется на объекте типа int – (~0х80) --> (~0x0080) --> 0xFF7F. Это значение затем приваивается char, то есть у с1 будет положительное значение 0х7F (127 в десятичной системе). В условии, оно расширяется до 0х007F, которое сравниваетвся с 0хFF7F и тест проваливается. Если char знаковый и константа имела старший бит очищенным (это любое значение в диапазоне 0x00-0x7F), поразрядно проинвертированное значение будет отрицательным и тест может быть успешным (смотрите пример ниже)
Пример 3
void f3(void)
{
signed char c1;
c1 = ~0x70;
if (c1 == ~0x70)
;
}
В присваивании, поразрядная инверсия выполняется над объектом типа int, (~0x70) --> (~0x0070) --> 0xFF8F. Это значение затем присваивается char, то есть char будет иметь значение 0x8F (-113 в десятичной системе). В сравнении значение расширяется до 0xFF8F (знаковый бит распространяется в старшие разряды) и тест работает.
Пример 4
void f4(void)
{
signed char c1;
signed int i1;
i1 = 0xFF;
c1 = 0xFF;
if (c1 == i1)
;
}
В первом присваивании i1 становится равным 255, тогда как во втором присваивании (с1) 255 не укладывается в тип signed char (потому что диапазон его значений -127..127). В сравнении значение с1 преобразуется к 0xFFFF и тест проваливается.
Заметьте, что эта проблема очевидна с явно объявленным signed char, гораздо сложнее обнаружить ее, если используется char и по умолчанию он знаковый – signed char.
По материалам фирмы IAR Systems
P.S.
Интересная на мой взгляд статья. Перевел как мог.
Comments
объявляешь явно , а не по умолчанию и
потому уже за все сам отвечаешь .
Мне бы хотелось здесь спросить можно ли
в разделе "Как подключить" рассмотреть в
будущем подключение MMC/SD ?
RSS feed for comments to this post