2012-01-06 4 views
8

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

int badFun() { return badFun(); }

Это, очевидно, вызвало переполнение стека даже на языке высокого уровня, я работал с (4Test в SilkTest). Нет никакого способа, чтобы этот код можно было считать полезным. Первыми признаками проблем были предупреждения, увиденные после завершения скрипта, но не ошибки компиляции или предупреждения. Любопытно, что я пробовал писать программы на C++, C# и Python с одинаковой структурой, и все они были скомпилированы/интерпретированы без синтаксических ошибок или предупреждений, даже несмотря на ошибки во время выполнения во всех случаях. В любом из этих случаев я даже не видел никаких предупреждений. Почему по умолчанию эта проблема не рассматривается как возможная проблема?

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

+4

Ваш вопрос о C#, C++ * или * Python? Пример кода выглядит так же, как C#. – BoltClock

+10

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

+0

В MSVC (C++) он выдаст вам предупреждение, если вы вызовете функцию: 'warning C4717: 'badFun': рекурсивный на всех путях управления, функция вызовет переполнение стека во время выполнения' – Mysticial

ответ

39

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

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

Это верно для всех функций. У предупреждений есть особые дополнительные проблемы. Предупреждение должно быть о коде, который имеет следующие характеристики:

  • Legal. Очевидно, что код должен быть законным; если это не законно, то это не предупреждение, в первую очередь, его ошибка.
  • Почти наверняка неправильно. Предупреждение, предупреждающее вас о правильном, желательном коде, является плохим предупреждением. (Кроме того, если код соответствует, то должен быть способ записи кода таким образом, чтобы предупреждение уходило.)
  • Inobvious. Предупреждения должны сообщать вам о ошибках, которые являются тонкими, а не очевидными.
  • Подходит для анализа. Некоторые предупреждения просто невозможны; предупреждение, которое требует от компилятора решения проблемы с остановкой, например, не произойдет, поскольку это невозможно.
  • Вряд ли можно поймать другие формы тестирования.

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

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

+2

+1 Отличный ответ и действительно получает то, о чем я просил. Я знаю, что компиляторы не должны мешать вам писать ужасный код, но хорошие компиляторы должны хотя бы бросить «эй, что там делать?» иногда. – joshin4colours

+2

Существует связанная с этим проблема с 'int BadProperty {get {return BadProperty;}}' и ее эквивалентом setter, где ошибку очень легко сделать, и не так легко заметить. Но эту проблему явно легко обнаружить, когда вы действительно запускаете код, что значительно снижает преимущество этой функции. – CodesInChaos

+2

@CodeInChaos: Действительно, это одно место, где предупреждение может быть оправдано. –

1

Потому что компилятор не будет проверять этот материал.

Если вы установите анализатор кода, например Resharper, в Visual Studio, он выдает предупреждение о бесконечном рекурсивном вызове или sth, как если бы вы включили опцию анализа кода.

20

Почему это не рассматривается как проблема по умолчанию?

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

void evil() { 
    if (somethingThatTurnsOutToAlwaysBeTrue) 
     evil(); 
} 

Для того, чтобы определить, что является ли проблема, компилятор должен попытаться выяснить, будет ли условие всегда истинным или нет. В общем случае я не думаю, что это более сложно вычислить, чем определить, будет ли программа в конечном итоге остановлена ​​(т. Е. Это provably not computable).

+17

+1. Компиляторы не беспокоят, потому что проблема с остановкой в общем случае невыполним. – Patrick87

+1

По той же логике компилятор не стал бы предупреждать о недостижимом коде. И все же он делает - в определенном подмножестве случаев. – harold

+0

@harold Как хорошо объяснил Эрик Липперт, он сводится к решение о стоимости и преимуществах предупреждения. То, что я пытаюсь сказать здесь, состоит в том, что определение тривиального случая представляется не очень полезным, и нетривиальные случаи, когда предупреждение было бы полезным, вероятно, будут трудными или невозможными – Caleb

1

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

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

2

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

Даже если компилятор проверил это, где вы рисуете линию? Что делать, если у вас была рекурсивная функция, которая рассчитывала факториал навсегда?

+1

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

3

Ни один компилятор любого языка программирования не имеет представления о семантике кода, который он компилирует. Это действительный код, хотя и глупый, поэтому он будет скомпилирован.

+0

Компилятор много разбирается в семантике кода. Было бы довольно легко реализовать это предупреждение на разумном уровне. Но я не знаю, стоит ли его указывать, внедрять, документировать и поддерживать эту функцию. – CodesInChaos

0

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

Во время исполнения

, когда стек переполняется он выдает ошибку из-за переполнения стека * не из кода *.

Рекурсивная функция является действительной, но снова в реализации нам нужно поставить условие проверки для возврата значений до заполнения стека.

Смежные вопросы