Я хотел убедиться, что я понял, что происходит в следующем коде:
#include <stdio.h>
#include <stdint.h>
int main (void)
{
uint8_t a = 255;
a = a + 5;
printf("%d\n", a);
}
Будет ли напечатанное значение равным 4, потому что, когда a достигает максимального значения, которое оно может считать до (255), оно сбрасывается обратно в 0? Итак, если я хочу продолжить отсчет после (255), я могу создать переменную int и добавить к ней? Например, int b = a + 5;
, который напечатает 260.
@YvesDaoust Во многих ситуациях это полезно при программировании, связанном с аппаратным обеспечением. Предположим, например, что у меня есть 8-битный процессор, который предпочитает 8-битные переменные из соображений производительности. Затем каждый второй раз, когда я вызываю функцию, я хочу, чтобы что-то произошло: void func (void) { static uint8_t x; if (x & 1) { do_something(); } x++; }
Здесь переменная в конечном итоге будет перенесена, но нам не нужно знать или заботиться о том, какое значение она содержит.
@Lundin: конечно, но здесь ОП явно упоминает счет выше 255.
@YvesDaoust Речь идет больше о понимании того, что происходит, чем о том, что было бы разумнее сделать.
Во многом правильно. Вам просто нужно понимать, что в выражении a + 5
unsigned char
неявно преобразуется в тип 5
, которым является int
, благодаря правилу продвижения, называемому «обычные арифметические преобразования». Таким образом, сложение на самом деле выполняется по типу int
, и в итоге вы получаете 260 во временном int
. И поэтому вы действительно можете сделать int b = a + 5;
и получить 260, потому что часть a + 5
равна 260.
Во время присвоения a
в a = a + 5
вы принудительно выполняете еще одно преобразование обратно в unsigned char
, и оно выполняется «как будто по модулю» на единицу больше, чем максимальное число, которое оно может содержать. 260 % 256 = 4, и в итоге у вас получится 4
. Это то же самое, что происходит, когда вокруг появляется unsigned char
.
(Переполнение/недополнение целого числа со знаком, что является неопределенным поведением. Целые числа без знака переносятся вокруг, что является четко определенным поведением.)
Если бы вы вместо этого написали a++; a++; a++; a++; a++;
, вы бы получили зацикливание.
Обычные арифметические преобразования не являются правилом продвижения. Промоакции – это разновидность конверсий, а не наоборот. Внутри обычных арифметических преобразований существуют правила продвижения. Продвижение никогда не меняет ценности. Некоторые преобразования выполняются, включая некоторые обычные арифметические преобразования.
@lundin Подробности: unsigned char
становится частью int
. Тип +
изначально не имеет значения для этого первого шага.
Да на оба вопроса.
Учитывая, что a
— это uint8_t
, a + 5
эквивалентно (int)a + 5
и порождает int
.
При выполнении арифметических операций над парами значений выполняются обычные арифметические преобразования. Если типом обоих значений являются целочисленные типы, то это относится к целочисленному преобразованию обоих операндов, а также к дополнительным шагам, если расширенные значения по-прежнему относятся к разным типам.
Целочисленное продвижение означает преобразование значения с типом меньшего ранга, чем int
, в int
(если весь их диапазон может уместиться в int
) или unsigned int
. uint8_t
гарантированно будет типом меньшего ранга, чем int
, и его диапазон гарантированно будет соответствовать int
, поэтому значение этого типа будет повышено до int
.
Итак, в результате обычных арифметических преобразований a + 5
преобразует a
в int
, добавляет к нему int
5
, создавая int
260
.
Пока перелива нет. Но затем мы присваиваем его uint8_t
. Преобразование слишком большого значения в беззнаковый целочисленный тип четко определено. Для 8-битного беззнакового целого числа вы фактически получаете результат по модулю 256, то есть uint8_t
4
.
Не было бы переполнения, если бы вместо этого вы сохранили int
260
в int
, так что это действительно создаст int
260
.
Зачем вам нужен байтовый аккумулятор, если вы знаете, что он переполнится?