Будучи программистом, вам не нужно полностью знать ISO/ANSI C стандарт. Можно создавать довольно сложные приложения на Си, не зная всех деталей. Однако, стандарт – лучший помощник, когда вы разбираетесь в менее очевидных частях языка и исследуете его закоулки.
   Когда вы читаете стандарт, некоторые моменты выглядят совершенно ясными на бумаге, но когда вы компилирует свой код, он так переворачивается, что делает все, кроме того, что вы хотели. Некоторые части стандарта требуют анализа, прежде чем вы полностью поймете их поведение и сможете использовать в своих интересах.
   Несколько примеров возможного запутанного поведения описаны ниже и это далеко не то, что бы вы назвали закоулками языка Си, выражения подобные этим можно найти в реальном приложении. Но прежде чем погрузиться в поведение, которое может показаться запутанным, давайте сначала разберемся, что стандарт говорит относительно целочисленного расширения и целочисленных констант.

Целочисленное расширение

   Си стандарт 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.

Интересная на мой взгляд статья. Перевел как мог. 

 
 

Добавить комментарий

При добавлении в комментарии Сишного кода заключайте его между тегами
[code] [/code]

Защитный код
Обновить