vak: (Default)
[personal profile] vak
Сложить два целых числа, вернуть целое. Породить исключение в случае арифметического переполнения.
int add(int a, int b);
Удивительно, сколько народу обламывается на этой задачке.

Date: 2020-08-02 18:38 (UTC)
archaicos: Шарж (Default)
From: [personal profile] archaicos
UB возникает во время +. Потом на результат от этого + смотреть уже поздно.

Date: 2020-08-02 19:34 (UTC)
waqur: (Default)
From: [personal profile] waqur
Думаю, можно переделать

int sum = a + b;

в

int sum = (int)(((unsigned int)a) + ((unsigned int)b));

а всё остальное оставить как есть.

Date: 2020-08-02 20:30 (UTC)
archaicos: Шарж (Default)
From: [personal profile] archaicos
Заменяет undefined behavior на implementation-defined (в частности на сигнал).

Date: 2020-08-02 22:13 (UTC)
waqur: (Default)
From: [personal profile] waqur
Не понял, где будет сигнал? На касте или на сложении? Так каст между signed/unsigned int — это бесплатно, это просто реинтерпретация битов в регистре, а сложение двух unsigned int это всегда определённое поведение, даже при переполнении.

Date: 2020-08-02 22:25 (UTC)
archaicos: Шарж (Default)
From: [personal profile] archaicos
Сигнал может быть на преобразовании из unsigned в signed (что каст, что присвоение – один хер/каст). Оно может быть не бесплатное. Я уже упомянул implementation-defined.

Date: 2020-08-02 22:34 (UTC)
waqur: (Default)
From: [personal profile] waqur
Да, вижу в C99 возможность словить сигнал при целочисленном преобразовании типов (6.3.1.3). Никогда с таким не сталкивался, но очень интересно, спасибо.

Наверное, каст можно обойти с помощью union. Или с помощью memcpy.
Edited Date: 2020-08-02 22:44 (UTC)

Date: 2020-08-02 22:58 (UTC)
archaicos: Шарж (Default)
From: [personal profile] archaicos
Приведение типов через union можно в принципе (т.е. не всегда) делать только в C. В C++ этого делать нельзя никогда.
memcpy() – самый надёжный способ, но если только в типе нету зарезервированных значений, т.н. trap representation (в целых обычно нет, но могут быть). А то после memcpy() при использовании результата может бяка вылезти.

Date: 2020-08-03 08:20 (UTC)
waqur: (Default)
From: [personal profile] waqur
В принципе, рассуждения о платформах, где каст из unsigned int в int может вызвать сигнал — это такой же религиозный пуризм, как и рассуждения о платформах, где sizeof(intmax_t)=sizeof(char), где байт содержит не 8 бит, где бинарное представление nullptr не совпадает с бинарным представлением ((size_t)0U), или где отрицательные целые числа представлены не в виде двоичного дополнения, или о таких платформах, где каст целочисленных типов совпадающей размерности через union это UB. Очень интересно, но чистая философия, сродни теологическим дискуссиям о количестве ангелов на кончике иглы.

Поэтому на проде вместо извращений с memcpy() и надежд на оптимизатор, я оставил бы каст, т.к. если найдётся платформа, где этот каст сломается, то проблемы будут абсолютно у всех и абсолютно везде, а не только у меня.
Edited Date: 2020-08-03 08:29 (UTC)

Date: 2020-08-02 20:25 (UTC)
sobriquet9: (Default)
From: [personal profile] sobriquet9
Ну это наверное можно обойти приведением аргументов к unsigned до сложения, а потом результата обратно к int. Или спрятать сложение под if и добавить там смещение на полдиапазона, благо мы уже знаем, в какую сторону смещать.

Вопрос в том, как убедиться в отсутствии UB. Обложить testcase-ами не поможет, ибо UB как в приведённом примере может не отличаться от ожидаемого поведения.

Date: 2020-08-02 20:44 (UTC)
archaicos: Шарж (Default)
From: [personal profile] archaicos
В общем случае UB никак нельзя найти автоматически, и в этом заключается один из фундаментальных дефектов языка. Это как с сусликом, которого ты не видишь, а он есть.
Но в частности есть т.н. UndefinedBehaviorSanitizer. Есть ещё немножко таких.

Преобразование unsigned int в signed int в случае переполнения даёт или implementation-defined число или implementation-defined signal.
Ну и там ещё нужно не забывать про представление чисел со знаком, коих три варианта, т.е. возможны два разных диапазона в одном и том же числе битов.

Date: 2020-08-02 22:49 (UTC)
sobriquet9: (Default)
From: [personal profile] sobriquet9
clang++ с ключом -fsanitize=signed-integer-overflow молчит, с ключом -S выдаёт ассемблерную инструкцию addl %edi, %eax которая работает одинаково что с int, что с unsigned.

Поскольку компилятора стандарта C++ нет, это мне напоминает историю с фальшивой банкнотой. Если никто её не может отличить от настоящей, значит её можно считать настоящей.