c стандартными и битовыми сдвигами - PullRequest
5 голосов
/ 31 октября 2011

Этот вопрос был впервые вдохновлен (неожиданными) результатами этого кода:

uint16_t   t16 = 0;
uint8_t     t8 = 0x80;
uint8_t t8_res;

t16    = (t8 << 1);
t8_res = (t8 << 1);

printf("t16: %x\n", t16);    // Expect 0, get 0x100
printf(" t8: %x\n", t8_res); // Expect 0, get 0

Но оказывается, что это имеет смысл:

6.5.7 Битовые операторы сдвига

Ограничения

2 Каждый из операндов должен иметь целочисленный тип

Таким образомизначально запутанная строка эквивалентна:

t16 = (uint16_t) (((int) t8) << 1);

Немного не интуитивно понятно ИМХО, но, по крайней мере, четко определено.

Хорошо, отлично, но тогда мы делаем:

{
uint64_t t64 = 1;
t64 <<= 31;
printf("t64: %lx\n", t64); // Expect 0x80000000, get 0x80000000
t64 <<= 31;
printf("t64: %lx\n", t64); // Expect 0x0, get 0x4000000000000000
}

// edit: после того же буквального аргумента, что и выше, следующее должно быть эквивалентно:

t64 = (uint64_t) (((int) t64) << 31);

// отсюда мое замешательство / ожидание [end_edit]

Теперь,мы получаем интуитивный результат, но не то, что получилось бы из моего (буквального) прочтения стандарта.Когда и как происходит это «дальнейшее автоматическое продвижение типов»?Или есть ограничение в другом месте, что тип никогда не может быть понижен в должности (что имело бы смысл?), В таком случае, как применяются правила продвижения для:

uint32_t << uint64_t

Поскольку стандарт говорит, что оба аргументаповышен до Int;должны ли оба аргумента быть переведены в один и тот же тип здесь?

// edit:

Более конкретно, что должно быть результатом:

uint32_t t32 = 1;
uint64_t t64_one = 1;
uint64_t t64_res;

t64_res = t32 << t64_one;

// end edit

Ответ на поставленный выше вопрос разрешается, когда мы понимаем, что спецификация не требует повышения до int, а точнее до integer type, которое uint64_t квалифицируется как.

//РЕДАКТИРОВАТЬ УТОЧНЕНИЕ:

Хорошо, но теперь я снова запутался.В частности, если uint8_t является целочисленным типом, то почему его вообще повышают до int?Кажется, она не связана с константой int 1, как показывает следующее упражнение:

{
uint16_t t16 = 0;
uint8_t t8 = 0x80;
uint8_t t8_one = 1;
uint8_t t8_res;

t16 = (t8 << t8_one);
t8_res = (t8 << t8_one);

printf("t16: %x\n", t16);
printf(" t8: %x\n", t8_res);
}

t16: 100
 t8: 0

Почему повышается выражение (t8 << t8_one), если uint8_t является целочисленным типом? </p>

-

Для справки, я работаю с ISO / IEC 9899: TC9, WG14 / N1124 6 мая 2005 г. Если это устарело, и кто-то может также предоставить ссылку на более свежую версиюкопия, это также будет оценено.

Ответы [ 3 ]

7 голосов
/ 31 октября 2011

Я думаю, что источником вашей путаницы может быть то, что следующие два утверждения не эквивалентны:

  • Каждый из операндов должен иметь целочисленный тип
  • Каждый из операндов должен иметь int тип

uint64_t - целочисленный тип.

5 голосов
/ 31 октября 2011

Ограничение в §6.5.7, что "Каждый из операндов должен иметь целочисленный тип." - это ограничение, которое означает, что вы не можете использовать операторы побитового сдвига для нецелых типов, таких как значения с плавающей запятой илиуказатели.Это не вызывает эффекта, который вы отмечаете.

Часть, которая вызывает , вызывает эффект в следующем параграфе:

3. Целочисленные продвижениявыполняется на каждом из операндов.Тип результата - тип повышенного левого операнда.

Целочисленные продвижения описаны в §6.3.1.1:

2.Следующее может использоваться в выражении везде, где могут использоваться int или unsigned int:

  • Объект или выражение с целочисленным типом, чей ранг целочисленного преобразования меньше или равен рангуint и unsigned int.
  • Битовое поле типа _Bool, int, signed int или unsigned int.

Если int может представлять все значения исходного типа, значение преобразуется в int;в противном случае он преобразуется в unsigned int.Они называются целочисленными акциями .Все остальные типы не изменяются целочисленными повышениями.

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

Правила ранжирования являются сложными, но они гарантируют, что тип с более высоким рангом не может иметьменьшая точность.По сути, это означает, что типы не могут быть "понижены" до типа с меньшей точностью с помощью целочисленных повышений (возможно повышение uint64_t до int или unsigned int, но только * если диапазон типа по крайней мере равен uint64_t).

В случае uint32_t << uint64_t правило, которое срабатывает, равно "Тип результата - это типповышен левый операнд ".Таким образом, у нас есть несколько возможностей:

  • Если int равен как минимум 33 битам, тогда uint32_t будет повышен до int, а результат будет int;
  • Если int меньше 33 бит, а unsigned int не менее 32 бит, то uint32_t будет повышен до unsigned int, а результат будет unsigned int;
  • Если unsigned int меньше 32 бит, тогда uint32_t не изменится, и результат будет uint32_t.

В сегодняшних распространенных реализациях для настольных компьютеров и серверов int и unsigned int обычно 32 бити поэтому появится вторая возможность (uint32_t повышен до unsigned int).Раньше для int / unsigned int обычно было 16 битов, и возникала третья возможность (uint32_t оставлено без поддержки).

Результат вашего примера:

uint32_t t32 = 1;
uint64_t t64_one = 1;
uint64_t t64_res;

t64_res = t32 << t64_one;

Будет значением 2, сохраненным в t64_res.Обратите внимание, что на это не влияет тот факт, что результат выражения не uint64_t - и пример выражения, на которое будет повлиять:

uint32_t t32 = 0xFF000;
uint64_t t64_shift = 16;
uint64_t t64_res;

t64_res = t32 << t64_shift;

РезультатВот 0xf0000000.

Обратите внимание, что, хотя детали довольно сложны, вы можете свести все это к довольно простому правилу, которое вы должны иметь в виду:

В Cарифметика никогда не выполняется в типах, более узких, чем int / unsigned int.

3 голосов
/ 31 октября 2011

Вы нашли неправильное правило в стандарте :( Уместно что-то вроде «применяются обычные целочисленные промо-акции». Это то, что поражает вас в первом примере. Если целочисленный тип, такой как uint8_t, имеет ранг, равныйменьше чем int он повышен до int. uint64_t не имеет ранга меньше чем int или unsigned, поэтому повышение не выполняется и оператор << применяется к переменной uint64_t.

Редактировать: Все целочисленные типы, меньшие int, рекламируются для арифметики. Это просто факт жизни :) Независимо от того, повышен ли uint32_t, зависит от платформы,потому что он может иметь такой же ранг или выше, чем int (не повышен) или меньший ранг (повышен).

Относительно оператора << тип правого операнда на самом деле не важен, что считаетсядля количества битов остается левый (с вышеуказанными правилами).Более важным для правильного является его ценность.Оно не должно быть отрицательным или превышать ширину (повышенного) левого операнда.

...