Эквивалентно ли арифметическое переполнение операции по модулю?

Мне нужно выполнить арифметику по модулю 256 на C. Итак, могу ли я просто сделать

unsigned char i;
i++;

вместо

int i;
i=(i+1)%256;

person avmohan    schedule 06.02.2014    source источник
comment
ну, поскольку C99 есть stdint.h, и вы можете просто использовать uintXX_t cplusplus.com/reference/cstdint   -  person user2485710    schedule 06.02.2014
comment
Хотя это работает, оно попадает в категорию изощренных уловок, которые, вероятно, будут удалены другим разработчиком, который сочтет ваш трюк ошибкой. Явное указание ваших намерений по модулю предотвращает это.   -  person Mgetz    schedule 06.02.2014
comment
Если вам не нужно полагаться на кого-то еще, поддерживающего ваш код, тогда да. Я просто не понимаю, зачем вам это. Дополнительная работа по поддержанию запутанного кода не стоит того.   -  person Peter Abolins    schedule 06.02.2014
comment
Помните, что % - это остаток, а не по модулю! хотя по модулю == остаток для положительных чисел. Обе части кода не эквивалентны, потому что первая дает вам модуль, тогда как вторая дает остаток (ваш i равен int, а не unsigned int).   -  person Grijesh Chauhan    schedule 06.02.2014
comment
@Mgetz Других разработчиков нет, так как это лабораторное задание, где я должен реализовать RC4. Я все равно решил этого не делать, потому что лучше быть явным, чем неявным, и мне лучше не прибегать к эзотерическим уловкам, особенно когда кто-то ставит оценку ...   -  person avmohan    schedule 06.02.2014
comment
@ v3ga - хорошо, что спросили; это привело к интересной дискуссии и к пониманию того, что не каждый байт равен 8 битам, что было для меня новостью. Вы правы в том, что вам лучше использовать явный код, который делает очевидным то, что вы делаете, - а хороший компилятор знает быстрые трюки для выполнения %256, поэтому вам не нужно выполнять микрооптимизацию. О написании понятного кода можно сказать МНОГОЕ - особенно, если это лабораторная работа, и она получает оценки! Спасибо за интересное обсуждение.   -  person Floris    schedule 06.02.2014
comment
Для меня это тоже было новостью (на самом деле я получаю много такого из большинства вопросов по C).   -  person avmohan    schedule 06.02.2014
comment
@ v3ga Я порекомендую вам, если вы используете первую версию (или любую из предложенных Александром К.). Вы всегда можете добавить в код комментарий сортировки, объясняющий, что вы оцениваете мод 256, и это будет нормально. Просто добавьте //trick to eval mod 256. Хорошая программа - это тот, кто знает более одного пути. код вроде iitians :)   -  person Grijesh Chauhan    schedule 07.02.2014


Ответы (7)


Нет. Нет ничего, что могло бы гарантировать, что unsigned char имеет восемь бит. Используйте uint8_t из <stdint.h>, и все будет в порядке. Для этого требуется реализация, поддерживающая stdint.h: любой компилятор, совместимый с C99, поддерживает, но старые компиляторы могут не предоставлять этого.

Примечание: беззнаковая арифметика никогда не переполняется и ведет себя как «по модулю 2 ^ n». Подписанные арифметические переполнения с неопределенным поведением.

person Alexandre C.    schedule 06.02.2014
comment
@ user2485710: Даже MSVC теперь имеет stdint.h, когда они пытаются реализовать C ++ 11. - person Alexandre C.; 06.02.2014
comment
См. stackoverflow.com/a/2215454/1967396 для справки, в которой говорится, что char всегда является одним байтом в C99. - person Floris; 06.02.2014
comment
@ Флорис: Да, но один байт C не обязательно 8 бит. Он определен как размер одного char. - person Alexandre C.; 06.02.2014
comment
да, но это всегда требования, и чаще всего я вижу какие-либо требования от людей, использующих C, действительно действительно старые реализации, например, довольно популярны во встраиваемом мире. - person user2485710; 06.02.2014
comment
@AlexandreC. Понятно - вы правы. Это делает ваш ответ лучше, чем мой. - person Floris; 06.02.2014
comment
@ user2485710: Вы правы насчет встроенного мира. На рабочем столе это больше не проблема. - person Alexandre C.; 06.02.2014
comment
И в OP даже не упоминается тип машины, на которую он нацелен, так что вы просто не знаете и должны указать это в своем ответе; эта информация может понадобиться и другим людям. - person user2485710; 06.02.2014
comment
@ user2485710: Готово. Поскольку байтовые уловки довольно распространены в таких средах, действительно необходимо сделать предостережение. - person Alexandre C.; 06.02.2014
comment
Даже с компилятором, совместимым с C99, все типы intN_t и uintN_t являются необязательными, и их доступность не гарантируется. В любой системе, в которой нет 8-битных байтов (что, по общему признанию, вероятно, необычно), они, скорее всего, будут недоступны. - person Crowman; 06.02.2014
comment
@PaulGriffiths: Один из известных мне примеров - это конкретная 16-битная встраиваемая платформа Texas Instruments DSP, которая поддерживает только адресацию слов, поэтому все как минимум 16-битное. sizeof(char) == sizeof(short) == sizef(int) == 1 == 16 bits. Предположение, предложенное в этом ответе, там не сработает. - person Jason R; 07.02.2014

Да, поведение обоих ваших примеров одинаково. См. C99 6.2.5 §9:

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

person LihO    schedule 06.02.2014

unsigned char c = UCHAR_MAX;
c++;

В основном да, переполнения нет, но не потому, что c имеет беззнаковый тип. Здесь есть скрытое преобразование c в int и целочисленное преобразование из int в unsigned char, и это прекрасно определено.

Например,

 signed char c = SCHAR_MAX;
 c++;

также не является неопределенным поведением, поскольку фактически эквивалентно:

c = (int) c + 1;

и преобразование из int в signed char определяется здесь реализацией (см. c99, 6.3.1.3p3 о преобразованиях целых чисел). Для упрощения предполагается CHAR_BIT == 8.

Для получения дополнительной информации о приведенном выше примере я предлагаю прочитать этот пост:

"Маленькая функция C из ада"

http://blog.regehr.org/archives/482

person ouah    schedule 06.02.2014
comment
Спасибо за ссылку на маленькую функцию C из ада. Я видел это раньше, но тогда я забыл добавить его в закладки. Поскольку я не мог вспомнить точное название, поискать его в Google было невозможно. - person Tonny; 07.02.2014
comment
Сумма двух значений unsigned char не будет переполняться, но на машинах, где sizeof (int) равно двум, произведение двух значений unsigned char может переполниться. Исторически тот факт, что стандарт позволял компилятору на 16-битной машине делать с unsigned char x=255; x*=255; все, что ему заблагорассудится, не рассматривался как дефект, потому что на практике даже компиляторы, которые использовали 16-битный int, вели себя разумно. Тот факт, что такой код приводит к неопределенному поведению, рассматривался как теоретическая проблема, а не как возможность для компиляторов отрицать законы времени и причинности. - person supercat; 25.07.2015

Очень вероятно, что да, но причины этого в данном случае на самом деле довольно сложные.

unsigned char i = 255;
i++;

i++ эквивалентно i = i + 1.

(Ну, почти. i++ дает значение i до, которое было увеличено, так что оно действительно эквивалентно (tmp=i; i = i + 1; tmp). Но поскольку в этом случае результат отбрасывается, это не вызывает никаких дополнительных проблем.)

Поскольку unsigned char является узким типом, операнд unsigned char оператора + повышается до int (при условии, что int может содержать все возможные значения в диапазоне unsigned char). Таким образом, если i == 255 и UCHAR_MAX == 255, то результатом сложения будет 256 и имеет тип (подписанный) int.

Присваивание неявно преобразует значение 256 из int обратно в unsigned char. Преобразование в беззнаковый тип четко определено; результат уменьшается по модулю MAX+1, где MAX - максимальное значение целевого беззнакового типа.

Если i был объявлен как unsigned int:

unsigned int i = UINT_MAX;
i++;

преобразования типов не будет, но семантика оператора + для беззнаковых типов также определяет модуль сокращения MAX+1.

Имейте в виду, что значение, присвоенное i, математически эквивалентно (i+1) % UCHAR_MAX. UCHAR_MAX обычно 255 и гарантированно не менее 255, но по закону может быть больше.

Может быть экзотическая система, в которой UCHAR_MAX тоже нельзя хранить в подписанном int объекте. Для этого потребуется UCHAR_MAX > INT_MAX, что означает, что в системе должно быть не менее 16-битных байтов. В такой системе продвижение будет с unsigned char до unsigned int. Окончательный результат будет таким же. Вы вряд ли встретите такую ​​систему. Я думаю, что есть реализации C для некоторых DSP с байтами больше 8 бит. Количество битов в байте определяется параметром CHAR_BIT, определенным в <limits.h>.

CHAR_BIT > 8 не обязательно означает UCHAR_MAX > INT_MAX. Например, у вас могут быть CHAR_BIT == 16 и sizeof (int) == 2, т.е. 16-битные байты и 32-битные ints).

person Keith Thompson    schedule 06.02.2014
comment
Технически i++ эквивалентно созданию копии i перед его приращением и использованию старой копии в выражении, в котором произошло пост-приращение. ++i будет истинным эквивалентом _4 _ / _ 5_. - person JAB; 06.02.2014

Есть еще одна альтернатива, о которой не упоминалось, если вы не хотите использовать другой тип данных.

unsigned int i;
// ...
i = (i+1) & 0xFF; // 0xFF == 255

Это работает, потому что элемент modulo == 2^n, что означает, что диапазон будет [0, 2^n-1], и, таким образом, битовая маска легко сохранит значение в желаемом диапазоне. Возможно, этот метод будет не намного или менее эффективен, чем версия _4 _ / _ 5_, в зависимости от того, какую магию делает ваш компилятор за кулисами и как целевая система обрабатывает загрузки без слов (например, некоторые архитектуры RISC требуют дополнительных операции для загрузки значений, не превышающих размер слова). Это также предполагает, что ваш компилятор не обнаружит использование арифметики по модулю степени двойки для значений без знака и, конечно же, не заменит вам битовую маску, поскольку в подобных случаях использование модуля по модулю будет иметь большее семантическое значение (хотя с использованием этого как основа для вашего решения конечно не совсем портативная).

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

i = (i+1) & 0x1FF; // i %= 512
i = (i+1) & 0x3FF; // i %= 1024
// etc.
person JAB    schedule 06.02.2014

Это должно работать нормально, потому что оно должно просто переполниться обратно до 0. Как было указано в комментарии к другому ответу, вы должны делать это только тогда, когда значение беззнаковое, так как вы можете получить неопределенное поведение со значением со знаком.

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

person Gavin    schedule 06.02.2014

Он будет работать, если количество бит, которые вы используете для представления числа, равно количеству бит в двоичном (беззнаковом) представлении (100000000) делителя -1, что в данном случае: 9-1 = 8 (char)

person Wajahat    schedule 06.02.2014