Путаница в смещении битов
Я пытаюсь считать два отдельных сообщения по 3 байта через CAN, сложить их и отправить обратно через CAN на CANBED V1, который использует Leonardo.
Моя проблема в использовании битового сдвига.
Если я попытаюсь сдвинуть 1 на 1..14 бит, я, кажется, пойму,
uint32_t hdbs = 1 << (14) ;
Serial.println(hdbs, BIN);
Этот код выводит на дисплей 100000000000000
при печати двоичного числа или 16384 в десятичном формате.
Но если я попробую сдвинуть бит на 15 или больше
uint32_t hdbs = 1 << (15) ;
Serial.println(hdbs, BIN);
Теперь я в замешательстве, поскольку
11111111111111111110000000000000000 или 4294934528 в десятичной системе
Какую базовую концепцию я упускаю? Я пробовал использовать unsigned long, long и т. д.
Если я произведу простые вычисления и использую hdbs = 8 * 4095
, то получу правильный ответ 32760, но когда я попробую 8*4096
, то получу 4294934528, что должно быть 32768 (что, похоже, превышает 16-битную беззнаковую переменную, но я думал, что объявляю hdbs как 32-битную переменную.
@Greg Smith, 👍1
2 ответа
Это на самом деле больше вопрос по C++, но я не знаю, известно ли вам это. Грубо говоря, в C, C++ (и языке "Arduino") выражения вычисляются для типов параллельно со своими значениями изнутри наружу, следуя правилам приоритета операторов, как в обычной алгебре.
Вы должны убедиться, что тип в левой части <<
уже готов принять результирующее значение. Например:
uint32_t hdbs = static_cast<uint32_t>(1) << 15;
uint32_t hdbs = (uint32_t)1 << 15;
uint32_t hdbs = uint32_t(1) << 15;
uint32_t hdbs = uint32_t{1} << 15;
uint32_t hdbs = 1UL << 15;
1
имеет тип int
, который для Leonardo является 16-битным типом.
Обычно, когда бинарные операторы, такие как +
или /
, оцениваются для типа результата, обе стороны учитываются в том, что называется обычными арифметическими преобразованиями. Для <<
он просто смотрит на тип смещаемой вещи и берет его. Таким образом, ваш результат 1 << 15
оценивается в int
, который не может хранить это значение в AVR. Тот факт, что вы позже используете это при инициализации uint32_t hdbs
, не имеет значения при вычислении самого 1 << 15
.
Я бы предположил, что вы получите 0x8000, что при преобразовании в 32-битное значение с расширением знака дало бы 11111111 1111111 10000000 00000000
. Но попытка переполнить знаковый целочисленный тип, как этот, не определена. Нельзя ожидать, что надежность приведет к какому-либо определенному поведению, не говоря уже о значении.
Если бы вы использовали 32-битный чип, такой как ARM или ESP32/ESP8266, это бы просто сработало, потому что тип int
там 32-битный.
Вот как работает приведение типа C.
Выражения C по умолчанию имеют знаковый тип. Если тип выражения имеет слишком малую длину в битах, то он может стать также отрицательным из-за переполнения (в старший бит, который также является знаковым битом).
Всякий раз, когда знаковое выражение должно вписаться в большее количество бит, оно сначала расширяется до более длинного знакового значения (что фактически то же самое, что дублирование старшего бита столько раз, сколько необходимо). И только после этого оно будет преобразовано в беззнаковое (что по сути является пустой операцией).
Так что, чтобы преодолеть эту проблему, вам нужно изменить порядок приведения, сделав 1U
или 1UL
вместо просто 1
. Тогда вы никогда не столкнетесь со знаковыми значениями и, следовательно, без расширения знакового бита.