Несколько вопросов по битшифтингам!

c++

Итак, int в Arduino составляет 2 байта, что технически может содержать значение 65 535. Однако старший бит используется как знаковый бит, так что теперь у нас есть от -32 768 до 32 767. Итак, это целое число со знаком, простая вещь. Пока я возился с битшифтингом, я наткнулся на кое-что интересное. Если я сделаю следующий код:

int a = 1023 << 6;  // 1023 = 0b0000001111111111
Serial.println(a, BIN);

он возвращает нелепое число 11111111111111111111111111000000, что составляет 4 байта. Я знаю, что это какая-то ошибка со сдвигом бита в бит со знаком, потому что если я это сделаю

int a = 1023 << 5;  // равно 111111111100000, как и ожидалось

Все работает отлично. Вопрос 1. Что здесь происходит? Откуда взялось это число?

Поэтому я поумнел (так я думал) и понял, что если мне нужно больше байтов, я просто использую long. Поэтому я попробовал следующий код:

long a = 1023 << 6;
Serial.println(a, BIN);

Я рассчитывал, что у меня будет 3 "дополнительных" байтов, чтобы переместить мой номер в. Однако возвращается тот же номер. Вопрос 2: Что происходит?

Вопрос 3. Каков порядок операций при битовом сдвиге?

Wire.write(CMD_VDR | PD_NPD | voltageLevel >> 6);

Этот код, который я написал, дает правильный результат. Однако каков порядок действий? Это просто слева направо? Помещение voltageLevel >> 6 в скобках не меняет вывод. Он работает нормально, но я хочу знать почему!

Спасибо!

, 👍-1

Обсуждение

Ваша третья часть была добавлена после того, как я начал отвечать, и она в значительной степени не связана с остальной частью. Вероятно, вы можете разобраться, посмотрев [это](https://en.cppreference.com/w/cpp/language/operator_precedence)., @timemage

исследование расширение знака, @jsotola

Пожалуйста, не размещайте несколько несвязанных вопросов в одном сообщении., @the busybee


2 ответа


Лучший ответ:

3

timemage предоставил отличный ответ на ваш первый вопрос (проголосовал за!). Попробую ответить на следующие:

long a = 1023 << 6;

Здесь вы оцениваете 1023 << 6, затем присваивая результат длинный. Важно понимать, что правила C++ для вычисления выражение не зависит от того, что вы собираетесь делать позже с результат. Учитывая, что 1023 является константой int, выражение имеет вид введите int и оценивается как таковой. Результат тогда не определен поведение, которое просто происходит, потому что вам не повезло, дать ожидаемое значение, то есть −64.

Присвоение значения -64 long приводит к преобразованию типа с сохранением значения. Это расширение подписи в действии. Как пояснил timemage, println(long, int) приводит значение к unsigned long, поэтому результат вы видите.

Какой порядок операций
[в выражении CMD_VDR | ПД_НПД | Уровень напряжения >> 6]?

Сдвиг имеет более высокий приоритет, чем побитовое ИЛИ. Выражение таким образом эквивалентно

CMD_VDR | PD_NPD | (voltageLevel >> 6)

См. Приоритет оператора C++.

,

Спасибо, что поделились списком приоритетов! Это очень полезно., @HavocRC

Ну, технически я попытался ответить на второй. Просто это было не так ясно, как ты выразился. Память, о которой я говорил, была промежуточным результатом правой части, которая применима к *обоим* их выражениям. Но, поскольку вы написали это, я просто проголосую за вас, а не попытаюсь прояснить, что я имел в виду., @timemage


2

Итак, int в Arduino составляет 2 байта

В общем случае это неверно, как бы там ни было. Это верно для Arduino на основе AVR, которые, как я предполагаю, используются ниже.

Строго говоря, когда вы делаете int a = 1023 << 6; с 16-битным целым числом все ставки сняты, потому что вы уже находитесь в неопределенном поведении. 1023 — это тип int, правая часть должна иметь тип int, и попытка сохранить что-то, что на самом деле не помещается в целое число со знаком, не определена.

Но на данный момент, давайте просто скажем, что он делает примерно то, что вы ожидаете от битового шаблона, как и для беззнакового типа. Вы создали битовое представление для 16-битного целого числа со знаком со значением -64.

Вы вызываете println(int, int) здесь. Он, в свою очередь, вызывает println(long, int) здесь. Он обрабатывает только signedness для основания 10. Итак, вы просто перенаправляете свое значение -64 в printNumber здесь, которые обрабатывают значения unsigned long (которые могут иметь префикс - для базы 10).

Итак, вы конвертируете -64 в unsigned long. Преобразование в типы без знака уменьшает их максимальное значение по модулю плюс 1. Максимальное значение 32-битного unsigned long равно 0xFFFFFFFF, с добавлением единицы вы получите 0x100000000. Другими словами, вы в конечном итоге печатаете 0x100000000 - 64, то есть число, которое вы видите.

1023 << 5 не переполняет 16-битное целое число со знаком. Не приводит к отрицательному числу (опять же, если мы снисходительно относимся к неопределенному поведению), поэтому вы видите другой результат.

,

«Однако это верно для Arduino на основе AVR», — (не призывая вас к этому, но) для полноты стоит упомянуть, что int - это 2 байта на AVR Arduino _с компилятором gcc_, потому что это то, что разработчики того, что компилятор выбрал для этого. Другой компилятор C/C++ на том же оборудовании может работать по-другому. Другими словами, ширина int зависит не от аппаратного обеспечения, а от компилятора. Ширина int обычно выбирается как собственный размер слова этого оборудования, но на AVR это будет 8 бит, что не так полезно, как 16., @JRobert

@JRobert Да, я не знал, как далеко они хотели зайти. На самом деле было бы несовместимо иметь 8-битный тип int, который не соответствует минимальному требуемому диапазону, хотя gcc позволит вам сделать это с помощью -mint8. Но да, совместимый компилятор может ориентироваться на AVR и делать множество подобных вещей. Например, вы можете сделать все целочисленные типы 64-битными, даже тип char, и задать CHAR_BIT равным 64. Опять же, трудно понять, на чем остановиться., @timemage

Я мог бы написать что-то вроде *"Действительный диапазон типа int определяется компилятором, а не аппаратным обеспечением. Он подчиняется требованиям стандартов в той мере, в какой компилятор пытается придерживаться стандарта, который частично находится под контроль над тем, какие параметры были указаны для компилятора. Обычно намерение состоит в том, чтобы он следовал стандарту и, где это возможно, следовал некоторому понятию естественного целочисленного размера базовой архитектуры. Подробности, в конечном счете, именно компилятор определяет размер типа int."*, @timemage

Спасибо за подробный ответ, @HavocRC

@timemage: Достаточно честно! Я склонен видеть детали...., @JRobert