Полезно ли деление на ноль, сопровождаемое ошибкой времени выполнения, в C ++? - PullRequest
3 голосов
/ 17 октября 2011

Согласно стандарту C ++ (5/5) деление на ноль является неопределенным поведением. Теперь рассмотрим этот код (существует множество бесполезных операторов, которые мешают компилятору оптимизировать код):

int main()
{
    char buffer[1] = {};
    int len = strlen( buffer );
    if( len / 0 ) {
        rand();
    }
}

Visual C ++ компилирует if -это утверждение так:

sub         eax,edx 
cdq 
xor         ecx,ecx 
idiv        eax,ecx 
test        eax,eax 
je          wmain+2Ah (40102Ah) 
call        rand

Ясно, что компилятор видит, что код должен делиться на ноль - он использует шаблон xor x,x для обнуления ecx, который затем служит вторым операндом в целочисленном делении. Этот код определенно вызовет ошибку «деление на целое число ноль» во время выполнения.

IMO такие случаи (когда компилятор знает, что код всегда будет делиться на ноль) стоят ошибки времени компиляции - Стандарт не запрещает это. Это помогло бы диагностировать такие случаи во время компиляции, а не во время выполнения.

Однако я разговаривал с несколькими другими разработчиками, и они, похоже, не согласны - их возражение заключается в том, «что если автор хочет разделить на ноль до ... ммм ... тестовой обработки ошибок?"

Намеренно делить на ноль без понимания компилятора не так сложно - используя __declspec(noinline) Декоратор функций, специфичный для Visual C ++:

__declspec(noinline)
void divide( int what, int byWhat )
{
    if( what/byWhat ) {
       rand();
    }
}

void divideByZero()
{
    divide( 0, 0 );
}

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

Я что-то упустил? Нужно ли разрешать выдачу кода, который, как знает компилятор, делит на ноль?

Ответы [ 5 ]

4 голосов
/ 17 октября 2011

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

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

4 голосов
/ 17 октября 2011

Деление на 0 - неопределенное поведение, поскольку на некоторых платформах оно может вызвать аппаратное исключение. Мы все могли бы пожелать иметь более качественное аппаратное обеспечение, но поскольку никто никогда не считал целесообразным иметь целые числа со значениями -INF / + INF и NaN, это совершенно бесполезно.

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

int foo(char* buf, int i) {
  if (5 / i == 3) {
    return 1;
  }

  if (buf != buf + i) {
    return 2;
  }

  return 0;
}

Поскольку i используется в качестве делителя, то он не равен 0. Следовательно, вторая if тривиально верна и может быть оптимизирована.

Перед лицом таких преобразований любой, кто надеется на нормальное поведение деления на 0 ... будет сильно разочарован.

0 голосов
/ 17 октября 2011

Обнаружение деления на ноль во время компиляции - это то, что вы хотели бы иметь в качестве компилятора предупреждение . Это определенно хорошая идея.

Я не держу компанию с Microsoft Visual C ++, но G ++ 4.2.1 делает такую ​​проверку. Попробуйте скомпилировать:

#include <iostream>

int main() {
    int x = 1;
    int y = x / 0;
    std::cout << y;
    return 0;
}

И он скажет вам:

test.cpp: In function ‘int main()’:
test.cpp:5: warning: division by zero in ‘x / 0’

Но, учитывая это, ошибка - это скользкий уклон, который, как известно опытным людям, не тратит слишком много свободного времени на лазание. Подумайте, почему G ++ нечего сказать, когда я пишу:

int main() {
    while (true) {
    }
    return 0;
}

Как вы думаете, это должно скомпилировать или выдать ошибку? Должен ли он всегда предупреждать? Если вы думаете, что он должен вмешиваться во все такие случаи, я с нетерпением жду вашей копии написанного вами компилятора, который компилирует только те программы, которые гарантируют успешное завершение! : -)

0 голосов
/ 17 октября 2011

В случае целочисленных типов (int, short, long и т. Д.) Я не могу представить себе какое-либо использование для преднамеренного деления на ноль.

Однако для типов с плавающей запятой на IEEE-совместимом оборудовании явное деление на ноль чрезвычайно полезно. Вы можете использовать его для получения положительной и отрицательной бесконечности (+/- 1/0), а не числовых (NaN, 0/0) значений, что может быть очень полезно.

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

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

Несмотря на то, что вы можете получить доступ к этим значениям через макросы и std :: numeric_limits (в C ++), полезно иметь возможность создавать их самостоятельно (и позволяет вам избежать большого количества кода «особого случая»). Это также позволяет разработчикам стандартной библиотеки избегать хакерских атак (таких как ручная сборка правильной битовой последовательности FP) для предоставления этих значений.

0 голосов
/ 17 октября 2011

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

...