Почему в C и C++ нет встроенных способов проверки целочисленного переполнения?

Почему C и C++ не предоставляют набор операций, предоставляемых реализацией, для выполнения каждой из основных целочисленных операций с предусмотренной проверкой переполнения (например, bool safeAdd(int *out, int a, int b)).

Насколько я понимаю, в большинстве наборов инструкций есть способы определить, переполнились ли операции (например, флаги переполнения x86 и переноса), а также определить, произойдет ли это в случае целых чисел со знаком.

Таким образом, не должны ли компиляторы выполнять работу намного лучше, создавая более простые и быстрые операции, чем то, что можно закодировать на C и C++?


person Fire Lancer    schedule 22.08.2012    source источник
comment
Я не думаю, что добавление проверок ускорит работу.   -  person Alexander Chertov    schedule 22.08.2012
comment
быстрее, чем пытаться решить перед операцией, может ли он переполниться или нет. например Добавление, подписанное IIRC x86, вы можете просто проверить флаг OF с условным переходом   -  person Fire Lancer    schedule 22.08.2012
comment
кстати, есть boost::numeric_cast для безопасных числовых преобразований.   -  person mirt    schedule 22.08.2012
comment
Я знаю, как это сделать, но собирается ли оптимизирующий компилятор выполнять такие проверки и превращать ваше if(additionIsSafe(inta,intb)) в ADD a, b\n JO overflow?   -  person Fire Lancer    schedule 22.08.2012
comment
Это философский вопрос, почему этого нет в языке, но вот ответы о том, как это сделать в любом случае: stackoverflow.com/questions/199333/.   -  person Lubo Antonov    schedule 22.08.2012
comment
Взгляните на этот материал, чтобы обсудить обработку переполнения. В нем не упоминается, почему это не рассматривалось в C, а просто говорится, что стандарт допускает модулярность целочисленной арифметики, поэтому переполнение в такой ситуации считается желаемым и ожидаемым поведением. Немного глупо, но и немного поздно что-то с этим делать сейчас.   -  person Rook    schedule 22.08.2012
comment
@FireLancer В случае с x86 для этого есть (или была) специальная инструкция (INTO). Объявленное намерение Intel заключалось в том, чтобы компиляторы использовали его. Насколько я знаю, никто никогда этого не делал (включая собственные компиляторы Intel); исходный компилятор PL/M, который я использовал на 8086, имел возможность использовать его, но на самом деле он не был реализован.   -  person James Kanze    schedule 22.08.2012
comment
@FireLancer Хороший компилятор, вероятно, мог бы поднять многие проверки на более высокий уровень, как это делает хороший программист. Разница в том, что хороший компилятор не забудет исправить поднятые проверки при изменении выражений на нижних уровнях.   -  person James Kanze    schedule 22.08.2012
comment
Если была проверка на переполнение, что должен делать код, когда это происходит? Выбрасывать исключение нельзя: исключения выбрасываются явно, и это критическое понятие для рассуждений о правильности. Прервать? Действительно?   -  person Pete Becker    schedule 22.08.2012
comment
Я не думал об общей проверке всего, а скорее о наборе явных функций, которые возвращают true, если все в порядке, или возвращают false, если нет, и оставляют значение *out в покое.   -  person Fire Lancer    schedule 22.08.2012
comment
@PeteBecker Это хорошая вещь в неопределенном поведении. Разработчик может сделать все, что лучше для его клиентов. (В большинстве случаев я бы остановился на этом. Если до этого дойдет, значит, в исходном коде есть ошибка, и вы не знаете, что не так во всем остальном.)   -  person James Kanze    schedule 22.08.2012


Ответы (6)


C и C++ следуют основному принципу «Вы не платите за то, что вам не нужно». Таким образом, арифметические операции по умолчанию не будут отклоняться от единственной инструкции базовой архитектуры для арифметических операций.

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

В языке программирования C переполнение целого числа со знаком приводит к неопределенному поведению.

Учитывая, что существует несколько способов реализации целого числа со знаком (дополнение до единицы, дополнение до двух и т. д.), и когда был создан C, все эти архитектуры были распространены, понятно, почему это не определено. Было бы сложно реализовать «безопасную*» функцию чистого C без большого количества информации о базовой платформе. Это можно было бы сделать, зная процессор за процессором.

Тем не менее, это не делает это невозможным. Мне определенно было бы интересно, если бы кто-нибудь мог найти предложения органам по стандартизации C или C++ с более безопасными помощниками переполнения и смог бы понять, почему они были отклонены.

Несмотря на это, на практике существует множество способов обнаружения арифметических переполнений. и библиотеки в помощь.

person Doug T.    schedule 22.08.2012

Наверное, потому что на него нет спроса. Арифметическое переполнение — это неопределенное поведение, явно позволяющее реализациям выполнять такие проверки. Если бы производители компиляторов думали, что их выполнение позволит продать больше компиляторов, они бы так и сделали.

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

Тем не менее, известно, что программисты допускают ошибки, и легко забыть исправить валидацию при изменении вычислений позже. Я бы хотел видеть такую ​​функцию в компиляторе. Но, видимо, это не поможет продавать компиляторы, или, по крайней мере, производители считают, что не поможет, так что мы этого не понимаем.

person James Kanze    schedule 22.08.2012
comment
Интересно, какие (если вообще были) предложения когда-либо поступали в комитеты по стандартам C или C++ для более безопасных арифметических функций/библиотек. - person Doug T.; 22.08.2012
comment
@ДугТ. - ничего подобного в общем-то, отчасти потому, что непонятно, что есть надежнее. Atomics гарантирует, что переполнение и потеря значимости значений со знаком переносятся так, как если бы представление было 2s-дополнением. - person Pete Becker; 22.08.2012
comment
Существует динамическая проверка целочисленного переполнения для кода C и C++. - person caf; 22.08.2012
comment
@caf: я знаю, что Ричард Смит планирует реализовать в Clang режим, в котором как можно больше случаев неопределенного поведения будет прерывать программу, и что целочисленное переполнение является его частью (у Clang уже есть некоторые специальные режимы, но пока ничего всеобъемлющего). Я думаю, что статический анализатор потенциально мог бы ловить такие переполнения... но программы, вероятно, недостаточно аннотированы. - person Matthieu M.; 22.08.2012
comment
@MatthieuM. Это то, чего я давно хотел. (Еще одна вещь - виртуальная машина, которая имитирует очень странные среды, для проверки случайных зависимостей от вещей, определенных реализацией. Виртуальная машина, которая позволяет использовать 9-битные байты, захватывать представления в int и т. д. и т. д.) - person James Kanze; 22.08.2012

Вопрос всплывает регулярно.

Во-первых, помните, что C определяется как портативный и эффективный. Таким образом, он был разработан только для обеспечения операций, которые поддерживались множеством оборудования (вероятно, еще до того, как x86 увидел свет).

Во-вторых, ряд компиляторов предоставляют (или планируют предоставить) встроенные функции для таких операций, чтобы пользователи могли использовать типы классов, которые используют эти встроенные функции внутри. Качество реализации встроенных модулей не так важно (хотя и важно), как тот факт, что компилятор, осведомленный об их значении, может оптимизировать проверки, когда они доказуемо бесполезны.

Наконец, есть и другие способы проверки программ. Например, статический анализ или специальные режимы компиляции & unit тесты могут обнаружить эти недостатки на ранней стадии и избежать необходимости (более или менее полностью) встраивать эти проверки переполнения в выпускные сборки.

person Matthieu M.    schedule 22.08.2012

Потому что это редко когда нужно. Когда вам действительно нужно обнаруживать целочисленное переполнение? Почти во всех ситуациях, когда вам нужно проверить какой-то диапазон, обычно именно вы определяете фактический диапазон, потому что этот диапазон полностью зависит от приложения и алгоритма.

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

Целочисленное переполнение — это UB, потому что вы редко заботитесь об этом. Если мой unsigned char переполняется во время операций, возможно, я выбрал неправильный тип для накопления 10 миллионов чисел. Но знание о переполнении во время выполнения мне не поможет, так как мой дизайн все равно сломан.

person Christian Rau    schedule 22.08.2012
comment
Есть много ситуаций, когда единственной верхней границей размера чисел, которые программа может принять на вход, будет функция целочисленного переполнения. Когда происходит целочисленное переполнение, программа не всегда может что-то с этим поделать, но запуск какой-либо ловушки может быть лучше, чем перезапись программой неверных данных, которые были действительными данными, и может гарантировать, что кто-то получит уведомление о переполнении. проблему, прежде чем она приведет к более серьезным проблемам в будущем. - person supercat; 11.01.2014

Лучше задать вопрос: почему целочисленное переполнение не определено? На практике 99,9% всех процессоров используют дополнение до двух и бит переноса/переполнения. Таким образом, в реальном мире, на уровне ассемблера/кода операции, целочисленные переполнения всегда четко определены. На самом деле большая часть ассемблера или аппаратного языка C в значительной степени зависит от четко определенных целочисленных переполнений (в частности, драйверов для оборудования таймера).

Исходный язык C до стандартизации, вероятно, не рассматривал подобные вещи подробно. Но когда C был стандартизирован ANSI и ISO, они должны были следовать определенным правилам стандартизации. Стандарты ISO не позволяют предвзято относиться к определенной технологии и тем самым давать определенной компании преимущество в конкурентной борьбе.

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

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

Разумным решением этой проблемы было бы не изобретать какие-то проверки безопасного диапазона, а скорее заявить, что все целые числа со знаком в языке C должны иметь формат дополнения до двух, конец истории. Тогда беззнаковый символ всегда будет от 0 до 127, а переполнение до -128, и все будет четко определено. Но искусственная стандартная бюрократия мешает стандарту стать разумным.

В стандарте C есть много подобных проблем. Выравнивание/заполнение, endianess и т. д.

person Lundin    schedule 22.08.2012
comment
Проблема, с которой я столкнулся там, заключается в том, что даже если целевая машина совместима с двумя, если вы пишете проверку, так что вы выполняете, скажем, подписанное добавление, а затем проверяете, обернуто ли значение, компилятор может удалить его как невозможный код ветвь (gcc, например IIRC), и независимо от реализации, bool addWithOverflowCheck(int*,int,int) или как бы вы ни называли, кажется, что компилятор может сделать это более оптимально почти на любой платформе - person Fire Lancer; 22.08.2012
comment
Причина, по которой целочисленное переполнение не определено, заключается в том, что разработчики могут определить его любым способом, который лучше всего подходит для их клиентов. Цель состоит в том, чтобы некоторые разработчики реализовывали проверки. - person James Kanze; 22.08.2012
comment
hardware-related C, relies heavily on well-defined integer overflows Значит ли это, что они компилируются с отключенной оптимизацией? LLVM, например, выбрасывает код, который переполняется - person Cubbi; 22.08.2012
comment
@Cubbi Нет, скорее всего, это означает, что они напрямую обращаются к аппаратным регистрам таймера, объявленным с помощью volatile, так что оптимизатор не может делать никаких предположений. - person Lundin; 22.08.2012
comment
Если бы речь шла только о целочисленном типе (дополнение 2 против дополнения 1 против величины знака), то это было бы оставлено определяемым реализацией, а не полностью неопределенным поведением. Неопределенное поведение больше связано с реализациями, которые перехватывают переполнение. - person caf; 22.08.2012
comment
@caf Да, это правильно, строго говоря, какая из 2, 1 и т. д. используется, определяется реализацией. Но использование различных операторов в реализациях с отрицательным нулем или без него и т. д. не указано/не определено. В разделе 6.2.6.2 стандарта перечислены все комбинации этих странностей. - person Lundin; 22.08.2012

Почему? Ну, потому что их не было в C, когда с этого начался C++, и потому что с тех пор никто не предлагал такие функции и не смог убедить создателей компиляторов и членов комитета в том, что они достаточно полезны, чтобы их предоставлять.

Обратите внимание, что компиляторы делают обеспечивают такие встроенные функции, так что дело не в том, что они против них.

Также обратите внимание, что есть предложения стандартизировать такие вещи, как Арифметика с фиксированной точкой и неограниченный- Прецизионные целочисленные типы.

Так что, наверное, просто не хватает интереса.

person AProgrammer    schedule 22.08.2012