В SICP говорится, что в C потребление памяти увеличивается, даже когда рекурсивные вызовы на самом деле являются итеративными. Зачем?

Оригинальные слова

Одна из причин, по которой различие между процессом и процедурой может сбивать с толку, заключается в том, что большинство реализаций распространенных языков (включая Ada, Pascal и C) спроектированы таким образом, что интерпретация любой рекурсивной процедуры потребляет объем памяти, который растет по мере увеличения объема памяти. количество вызовов процедур, даже если описываемый процесс в принципе является итеративным. Как следствие, эти языки могут описывать итерационные процессы, только прибегая к специальным «циклическим конструкциям», таким как do, repeat, until, for и while.

Я не знаком с языком C, как насчет Java или C#? Это верно и для них? И почему?

Примечание. Я думал, что автор говорит о возможностях разных языков. Но на самом деле речь идет просто о разных реализациях компиляторов.


person Cui Pengfei 崔鹏飞    schedule 21.07.2012    source источник
comment
На самом деле, это не говорит, что они повторяющиеся. Там написано ...in principle, iterative. Итак, они по-прежнему рекурсивны и требуют дополнительных вызовов процедур.   -  person Dave    schedule 21.07.2012


Ответы (4)


По сути, он пытается сказать, что компиляторы для этих языков не могут устранить хвостовую рекурсию. Короче говоря, он ошибается (или в любом случае чрезмерно обобщает - я думаю, что «большинство реализаций» достаточно ласковой формулировки, чтобы технически это могло быть в некотором роде правильным, хотя в лучшем случае вводящим в заблуждение). Хотя я, конечно, не могу гарантировать, что каждый компилятор для любого другого языка устраняет хвостовую рекурсию, нет никаких сомнений в том, что по крайней мере некоторые компиляторы C и C++ (например, Intel C++, gcc/g++) могут и будут это делать. Я не проверял, но, учитывая, что GNAT (компилятор Gnu Ada) использует тот же оптимизатор, что и их компиляторы C и C++, я сразу предполагаю, что он также может устранять хвостовую рекурсию.

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

person Jerry Coffin    schedule 21.07.2012
comment
То есть автор говорит именно о разных реализациях компиляторов, а не о возможностях разных языков, верно? - person Cui Pengfei 崔鹏飞; 21.07.2012
comment
@CuiPengFei: Да, в значительной степени. Я бы посмотрел на вещи, противоположные тому, что он сделал: поскольку Scheme (и Lisps в целом) так сильно зависит от рекурсии, устранение хвостовой рекурсии необходимо для всех реализаций. Поскольку Pascal, Ada, C и т. д. могут выражать итерацию напрямую, для них это гораздо менее важно. - person Jerry Coffin; 21.07.2012
comment
@JerryCoffin: Common Lisp не гарантирует устранение хвостовой рекурсии (даже если, конечно, многие компиляторы CL действительно это делают). Оптимизация хвостовых вызовов имеет основополагающее значение для схемы только потому, что нет другого способа создавать циклы. - person 6502; 21.07.2012

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

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

person Oliver Charlesworth    schedule 21.07.2012
comment
У лиспа и схемы нет стека вызовов? - person Cui Pengfei 崔鹏飞; 21.07.2012
comment
@CuiPengFei: я мало что знаю о реализации Lisp; но я, кажется, припоминаю, что читал, что язык требует, чтобы интерпретатор выполнял оптимизацию хвостового вызова. - person Oliver Charlesworth; 21.07.2012
comment
То есть автор говорит именно о разных реализациях компиляторов, а не о возможностях разных языков, верно? - person Cui Pengfei 崔鹏飞; 21.07.2012
comment
@CuiPengFei: Возможно. Я предполагаю, что когда SICP был впервые написан (начало 1980-х?), компиляторы C/C++ были значительно менее умными, чем сейчас. - person Oliver Charlesworth; 21.07.2012

Вы спросили: а как же C#?

C #, как и Java, но в отличие от большинства реализаций C / C ++ или ADA, компилируется в промежуточный язык, который затем компилируется, как правило, во время выполнения «компилятором точно в срок».

На момент написания компилятор C# не будет выполнять устранение хвостовой рекурсии, но JIT может.

Подробную информацию см. в разделе http://blogs.msdn.com/b/clrcodegeneration/archive/2009/05/11/tail-call-improvements-in-net-framework-4.aspx

person Kris Vandermotten    schedule 21.07.2012

Это не имеет ничего общего с реализациями компиляторов.

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

person user207421    schedule 22.07.2012