Почему 255 + 1 даёт 0 в микроконтроллере?
Обновлено Редакция GAW
Переменная типа uint8_t или byte хранит 8 бит, то есть числа от 0 до 255. Сумма 255 + 1 = 256 в двоичном виде занимает 9 бит:
В регистр помещаются только младшие 8 бит — нули. Девятый бит не пропадает бесследно: процессор кладёт его во флаг переноса C. По описанию команды ADD в справочнике AVR флаг C выставляется при переносе из старшего бита, а флаг Z — когда результат равен нулю. У Cortex-M то же самое описывает функция AddWithCarry в архитектурном руководстве Arm, только регистры там 32-битные.
Беззнаковые и знаковые числа
Один и тот же байт можно читать двумя способами, и переполняется он в разных местах:
| Тип | Диапазон | Переход через границу | Флаг |
|---|---|---|---|
| uint8_t, byte | 0…255 | 255 + 1 → 0 | C (перенос) |
| int8_t | −128…127 | 127 + 1 → −128 | V (переполнение) |
| uint16_t | 0…65 535 | 65 535 + 1 → 0 | C |
Для знаковых чисел байт 1000 0000₂ — это −128 в дополнительном коде, поэтому 127 + 1 превращается в −128. Чем флаг переноса отличается от флага переполнения, разбирают отдельные термины.
В C ноль появляется при записи
В программе на C или Arduino байт в выражении сначала расширяется до int — это правило целочисленного повышения языка. Поэтому при byte a = 255 условие a + 1 == 0 ложно: в выражении a + 1 равно 256. Ноль получается, только когда результат записывают обратно в байт, например a = a + 1 или a++. Так же 127 + 1 для int8_t сначала даёт 128 в int, а −128 появляется при присваивании.
Где это встречается
- Цикл for (byte i = 0; i < 300; i++) никогда не закончится: после 255 переменная i снова станет 0, и условие i < 300 всегда истинно.
- Сумма нескольких показаний АЦП не помещается в uint8_t — накопитель берут 16- или 32-битным.
- Команда INC у AVR флаг C не трогает: так её удобно использовать как счётчик цикла в многобайтных вычислениях, но перенос после 255 + 1 по флагу C не поймать — только по Z.
Сложение, вычитание и флаги для своих чисел можно проверить в калькуляторе двоичной арифметики, а перевести числа между системами счисления — в конвертере систем счисления.
Похожие вопросы
Как сделать, чтобы счётчик не переполнялся?
Взять тип шире — uint16_t считает до 65 535, uint32_t до 4 294 967 295 — или проверять значение перед прибавлением. В ассемблере перенос виден во флаге C: его прибавляют к старшему байту командой ADC, так складывают числа длиннее одного регистра.
Почему 0 − 1 в байте даёт 255?
Это то же переполнение в обратную сторону: вычитание берёт заём из несуществующего девятого бита, и в регистре остаётся 1111 1111₂ = 255. По справочнику AVR команда SUB при этом выставляет флаг C — он означает, что вычитаемое было больше уменьшаемого.
Какие числа помещаются в int16_t и uint16_t?
В 16 бит помещаются 65 536 значений: у uint16_t это 0…65 535, у int16_t в дополнительном коде — от −32 768 до 32 767. Переполнение наступает на тех же границах, что и у байта, только дальше.
Источники
- Microchip. AVR Instruction Set Manual, DS40002198B, 2021 — 6.2 ADD: флаг Z — результат равен 0x00, флаг C — перенос из старшего бита результата; V — переполнение в дополнительном коде; 6.61 INC: прибавляет единицу, флаг C не меняет, чтобы INC можно было использовать как счётчик цикла в многобайтной арифметике; V выставляется, если до операции в регистре было 0x7F; для беззнаковых чисел надёжны только переходы BREQ и BRNE; 6.119 SUB: флаг C выставляется, если вычитаемое по модулю больше уменьшаемого (заём)
- Arm. Armv7-M Architecture Reference Manual, ARM DDI 0403E.e — A2.2.1 — функция AddWithCarry(): результат — младшие N бит суммы, carry_out = 1, если беззнаковая сумма не помещается в N бит, overflow — если не помещается знаковая; разрядность регистров Cortex-M — 32 бита
Нашли ошибку?