Простая задачка на Си++
2020-08-02 04:29Сложить два целых числа, вернуть целое. Породить исключение в случае арифметического переполнения.
Удивительно, сколько народу обламывается на этой задачке.int add(int a, int b);

no subject
Date: 2020-08-02 11:49 (UTC)https://en.cppreference.com/w/cpp/language/try_catch
— выше сил аппликантов?
no subject
Date: 2020-08-02 12:19 (UTC)no subject
Date: 2020-08-02 13:00 (UTC)Короткий гугл-поиск дает ответ (только gcc конечно):
no subject
Date: 2020-08-02 13:08 (UTC)А когда вставляют в код ответ не от той задачи, совсем весело.
no subject
Date: 2020-08-02 13:13 (UTC)no subject
Date: 2020-08-02 13:13 (UTC)Первая истина для разработчика -- не умножай сущностей,
то есть не выдумывай заново, то что уже придумано до тебя.
Если человек не нашел builtin'a в гугле, то он уже не квалифицирован для разработки.
И знает ли он о том что signed integer overflow это UB или не знает это уже совершенно неважно.
no subject
Date: 2020-08-02 13:16 (UTC)no subject
Date: 2020-08-02 13:21 (UTC)Это как если бы вы сейчас сказали: в сети столько плохо написанных научных статей,
что проще написать свою с нуля, чем изучать existing works.
no subject
Date: 2020-08-02 13:25 (UTC)no subject
Date: 2020-08-02 13:29 (UTC)Для беззнаковых типичный паттерн проверки на переполнение — это сложить и проверить, что результат сложения не меньше одного из операндов (любого). А для знаковых — не знаю, ветвиться по знаковости операндов нужно, наверное, на все четыре случая. А в ветках - сводить всё к беззнаковым и применять тот же приёмчик. Это в предположении, что просто перейти к типам большей размерности нельзя, например, такая платформа, что sizeof(int) == sizeof(intmax_t).
no subject
Date: 2020-08-02 13:45 (UTC)Internally, the overflow flag is usually generated by an exclusive or of the internal carry into and out of the sign bit.
Мило, но вряд-ли аппликант на работу додумается до этого на собеседовании, если не знает заранее.
Carry out of the sign bit можно вычислить, как я уже говорил, после кастинга обоих операндов к unsigned int, их сложения, и сравнения суммы с любым из них, например первым. Если сумма беззнаковых меньше первого беззнакового — значит бит Carry out равен 1, иначе 0.
Carry in можно вычислить, просто применив AND-маску ((unsigned int)(INT_MAX)) к обоим операндам в беззнаковой форме перед сложением. Нужный нам бит "Carry in of the sign bit" будет в старшем бите результата после сложения.
А далее просто XOR этих carry in и carry out, как учит Википедия. :)
Интересно, найдётся ли компилятор, который преобразует все эти извращения обратно в инструкцию чтения флага OF.
no subject
Date: 2020-08-02 14:22 (UTC)int add(int a, int b) { int sum = a + b; if (a > 0 && b > 0 && sum < -1) { throw std::overflow_error("result too positive"); } else if (a < 0 && b < 0 && sum > -1) { throw std::overflow_error("result too negative"); } return sum; }no subject
Date: 2020-08-02 15:08 (UTC)Довольно логично, и не так уж неэффективно.
no subject
Date: 2020-08-02 15:09 (UTC)Достаточно проверить равенство знаков, и пробовать сложить абсолютные величины.
no subject
Date: 2020-08-02 15:48 (UTC)Я в курсе, что в процессоре инструкция add одна для всех типов операндов (и знаковых, и беззнаковых, и их комбинаций), я интересуюсь исключительно в контексте того, не съедет ли компилятор с катушек и не вставит ли туда инструкцию ud2, не выбросит ли весь код как недостижимый и т.д.
no subject
Date: 2020-08-02 15:55 (UTC)Нет смысла спрашивать меня про стандарт. Я его не знаю (да и не знал никогда).
no subject
Date: 2020-08-02 16:17 (UTC)Короче, настоящий программист ломанётся в дебри и обязательно запутается, потому что не успеет продумать все ньюансы.
no subject
Date: 2020-08-02 16:38 (UTC)Я когда-то всю эту хрень на форте (т.е. на ассемблере, если кишки брать) программировал.
no subject
Date: 2020-08-02 17:51 (UTC)no subject
Date: 2020-08-02 18:07 (UTC)int add(int a, int b) { int sum = a + b; if (~(a ^ b) & (a ^ sum) & INT_MIN) throw overflow_error("arithmetic overflow"); return sum; }no subject
Date: 2020-08-02 18:22 (UTC)no subject
Date: 2020-08-02 18:37 (UTC)Т.е. там более длинных целочисленных типов просто нет.
no subject
Date: 2020-08-02 18:38 (UTC)int add(int a, int b) { if (~(a ^ b) & (a ^ ((a & ~INT_MIN) + (b & ~INT_MIN))) & INT_MIN) throw overflow_error("arithmetic overflow"); return a + b; }no subject
Date: 2020-08-02 18:38 (UTC)no subject
Date: 2020-08-02 18:48 (UTC)1. undefined behavior не определено никак
2. компилятор не обязан его детектировать (и во многих ситуациях принципиально не может это сделать во время компиляции)
3. компилятор его не детектирует, а просто считает, что его не происходит, что программист не ошибся
4. в данном конкретном случае компилятор посчитает, что переполнения не произойдёт (а если произойдёт, то это undefined behavior же, ну и хер с ним)
5. это ему может открыть пути для оптимизации, т.е. здесь он может выкинуть код, следующий за оператором сложения, который проверяет на переполнение (которого ведь не возникает, а если возникает, то это UB и проблема программиста-недоучки, а не компилятора)