Извлечь верхнее и нижнее слово 32-битного целого числа без знака

Чтобы извлечь верхнее и нижнее слово 32-битного целого числа без знака и сохранить каждое из них в отдельных переменных uint16_t, я делаю следующее (nbr — 32-битное целое число без знака):

uint16_t lower_word = (uint16_t) nbr & 0x0000FFFF;  // mask desired word
uint16_t upper_word = (uint16_t) ((nbr & 0xFFFF0000) >> 16); // right-shift after masking

Является ли явное преобразование в uint16_t ненужным? Какие другие более эффективные способы, если они есть, вы рекомендуете для получения желаемых результатов вместо этого подхода?


person Abdel Aleem    schedule 21.12.2018    source источник


Ответы (4)


uint16_t lower_word = (uint16_t) nbr;
uint16_t upper_word = (uint16_t) (nbr  >> 16);

маски бесполезны

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

{отредактировать, чтобы учесть замечание Лундина/Эрика Постпишила}

например gcc -Wconversion выдает предупреждение без приведения

person bruno    schedule 21.12.2018
comment
Я вижу вашу логику. Маскировка кажется ненужной. - person Abdel Aleem; 21.12.2018
comment
Присваивания не нарушают никаких ограничений стандарта C, поэтому от компилятора не требуется выдавать предупреждение, и в большинстве случаев это не требуется по умолчанию, даже когда запрашиваются «все» обычные предупреждения. GCC, например, предупреждает только тогда, когда явно запрашивается «-Wconversion»; это предупреждение не входит в набор предупреждений «-Wall». - person Eric Postpischil; 21.12.2018
comment
@EricPostpischil да, Лундин предупредил ( :) ) меня об этом в комментарии к следующему ответу, но спасибо - person bruno; 21.12.2018

Система типа C одновременно тонкая и опасная. Явное преобразование может быть необходимым, а может и не быть. В частности, в случае (uint16_t) nbr & 0x0000FFFF приведение не является правильным, предполагая 32-битный процессор.

Вы произносите перед операцией. Это означает, что операнд nbr будет явно преобразован путем приведения, а затем немедленно неявно преобразован до int путем неявного целочисленного преобразования. Результат будет иметь тип int, который является подписанным. Безвреден в этом случае, но может вызвать проблемы в других случаях. Используя неправильный состав, вы сделали signed int из uint32_t, что не было целью.

В целом вам необходимо знать правила продвижения неявного типа.

Хотя существует неявное преобразование lvalue при присвоении обратно uint16_t, что в большинстве случаев спасает положение.

Также обратите внимание, что 0x0000FFFF — опасный стиль. Шестнадцатеричные литералы относятся к типу, в котором значение будет соответствовать, независимо от того, сколько нулей вы поставили перед значением. В данном случае это int, который подписан. В 16-битной системе 0x0000FFFF даст int, а 0x00008000 даст unsigned int. (Проверьте, например, эту странную ошибку: Почему 0 ‹ -0x80000000?)

Лучшей практикой, надежным, переносимым, совместимым с MISRA-C кодом является код, который вообще не содержит никаких неявных преобразований:

uint32_t nbr = ...;
uint16_t lower_word = (uint16_t) (nbr & 0xFFFFUL);
uint16_t upper_word = (uint16_t) ((nbr >> 16) & 0xFFFFUL);

Это предполагает, что nbr известно как uint32_t, в противном случае рекомендуется привести этот операнд к uint32_t перед приведением.

В данном конкретном случае маски на самом деле не нужны, но в общем случае, например, при маскировании 4 байтов из uint32_t.

person Lundin    schedule 21.12.2018
comment
@EricPostpischil Как было сказано изначально: предполагается 32-битный процессор. Очевидно, что если int или long являются 64-битными на каком-то гаражном компиляторе, замените UL на U. Серьезно, у тебя нет ничего лучше, чем следить за каждым моим постом и приходить с замечаниями языкового юриста, которые выходят далеко за рамки того, что нужно знать любому новичку? Этот ответ уже слишком подробен, чтобы быть педагогическим, и не охватывает переносимость для неясных гаражных компиляторов. - person Lundin; 21.12.2018

Нет, вам не нужен typecast, и я бы не рекомендовал его использовать. Это связано с тем, что он имеет более высокий приоритет, чем оператор &, поэтому nbr сначала преобразуется в uint16_t, а затем маскируется. По этой же причине вторая строка не будет работать без дополнительных скобок.

Кроме того, код просто прекрасен, и нет никакой реальной причины использовать другой подход. Вы также можете сначала выполнить сдвиг, а затем замаскировать значение, но результирующий ассемблерный код должен быть точно таким же.

person Felix G    schedule 21.12.2018
comment
@AbdelAleem Пожалуйста, не исправьте вопрос после публикации ответов. Вместо этого исправьте фактический код :) Я отменю ваши изменения, так как это делает опубликованные ответы неактуальными. - person Lundin; 21.12.2018
comment
не согласен, typecast нужен потому что он хочет результат в 16 бит и естественно код должен компилироваться без предупреждений - person bruno; 21.12.2018
comment
Код компилируется без предупреждений, по крайней мере, на gcc 8.2, даже если вы используете -Wall -Wpedantic -Wextra. Тем не менее, приведения тоже не повредят, по крайней мере, если соблюдается приоритет оператора (поэтому всегда следует использовать дополнительные скобки) - person Felix G; 21.12.2018
comment
@FelixG -Wall, по крайней мере, обязателен для компиляции для меня, компилятор не выдает предупреждение/ошибку, потому что это не мягко, а потому, что это помогает, и программист должен их учитывать :) - person bruno; 21.12.2018
comment
@bruno, я полностью согласен с вами в этом, и я действительно использую -Wall -Wextra во всем своем коде. Вот почему я сделал быстрый тест и обнаружил, что gcc не жалуется, если пропущены приведения, даже если я добавляю -Wpedantic - person Felix G; 21.12.2018
comment
@FelixG о да, так странно! - person bruno; 21.12.2018
comment
В gcc есть отдельное предупреждение о потенциально опасных преобразованиях типов под названием -Wconversion. Он не включен в -Wall и -Wextra, вероятно, потому, что он дает большое количество ложных срабатываний вместе с обнаружением реальных ошибок (как статический анализатор). -pedantic влияет только на соответствие стандарту, а uint16_t u16 = u32 >> 16; подходит только для стандарта. - person Lundin; 21.12.2018

Если вам нужно повторить эту операцию несколько раз в коде, есть другой способ сделать это с помощью union :

typedef union _uplow                                                                                     
{                                                                                                        
  struct _reg {                                                                                          
    uint32_t low : 16;                                                                                   
    uint32_t up  : 16;                                                                                   
  } reg;                                                                                                 
  uint32_t word;                                                                                         
} uplow;

Объявите свою переменную следующим образом:

uplow my_var;
my_var.word = nbr;

Используйте это так:

printf ("Word : 0x%x\n Low : 0x%x\n Up  : 0x%x\n", my_var.word, my_var.reg.low, my_var.reg.up);

Вывод :

Word : 0xaaaabbbb
 Low : 0xbbbb
 Up  : 0xaaaa
person K.Hacene    schedule 21.12.2018
comment
Это ужасно непереносимо, как для компилятора, так и для процессора. В то время как код ОП на 100% переносим и намного лучше этого. Вы можете использовать союз между uint16_t[2] и uint32_t, чтобы сделать его менее плохим, но у вас все равно будет ненужная зависимость от порядка байтов. - person Lundin; 21.12.2018
comment
@Lundin да, и в то же время такой сложный. Я проголосовал против этого ответа, как и в случае с удаленным ответом, используя указатель, эти два отрицательных голоса - единственные, которые я сделал на S.O. но трудно не сделать :( - person bruno; 21.12.2018