Подтвердить что ты не робот

Почему вложенные функции не поддерживаются стандартом C?

Не похоже, что в сборке было бы слишком сложно реализовать.

gcc также имеет флаг (-fnested-functions), чтобы включить их использование.

4b9b3361

Ответ 1

Оказывается, на самом деле их не так просто реализовать правильно.

Если внутренняя функция имеет доступ к содержащимся переменным области видимости? Если нет, нет смысла вкладывать в нее; просто сделайте его статическим (чтобы ограничить видимость для единицы перевода в нем) и добавьте комментарий "Это вспомогательная функция, используемая только myfunc()".

Если вы хотите получить доступ к содержащимся переменным области видимости, вы в основном вынуждаете его создавать замыкания (альтернатива ограничивает то, что вы можете делать с вложенными функциями, достаточными, чтобы сделать их бесполезными). Я думаю, что GCC на самом деле справляется с этим, создавая (во время выполнения) уникальный thunk для каждого вызова содержащейся функции, который устанавливает указатель контекста, а затем вызывает вложенную функцию. Это в конечном итоге является скорее хаксом Icky, и что-то, что некоторые вполне разумные реализации не могут сделать (например, в системе, которая запрещает выполнение записываемой памяти - что многие современные ОС делают по соображениям безопасности). Единственный разумный способ заставить его работать в целом - заставить все указатели функций нести вокруг скрытого контекстного аргумента и все функции, чтобы принять его (потому что в общем случае вы не знаете, когда вы называете это закрытием или незамкнутая функция). Это нецелесообразно требовать в C как по техническим, так и по культурным причинам, поэтому мы придерживаемся возможности использования явных указателей контекста для подделки закрытия вместо функций вложенности или использования языка более высокого уровня, который имеет инфраструктуру, необходимую для сделайте это правильно.

Ответ 2

Я хотел бы процитировать что-то от BDFL (Guido van Rossum):

Это связано с тем, что определения вложенных функций не имеют доступа к локальные переменные окружающего блока - только для глобальных содержащий модуль. Это делается для того, чтобы поиск глобальных должны пройти цепочку словарей - как в C, есть только два вложенные области: локальные и глобальные (и вне этого, встроенные). Поэтому вложенные функции имеют ограниченное применение. Это было намеренное решение, основанное на опыте с языками, позволяющими такие как Pascal и оба Algols - код тоже многие вложенные области примерно так же читабельны, как и код со слишком большим количеством GOTO.

Акцент мой.

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

Другой частью этого может быть закрытие.

Если у вас есть такая функция, как этот код типа C:

(*int()) foo() {
    int x = 5;
    int bar() {
        x = x + 1;
        return x;
    }
    return &bar;
}

Если вы используете bar в некотором обратном вызове, что происходит с x? Это хорошо определено на многих более новых языках более высокого уровня, но AFAIK не имеет четко определенного способа отслеживать, что x в C - does bar возвращает 6 каждый раз, или выполняет последовательные вызовы на bar return увеличивать значения? Это могло бы потенциально добавить совершенно новый уровень сложности к C относительно простому определению.

Ответ 3

См. C FAQ 20.24 и руководство GCC для потенциальные проблемы:

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

Это не является более серьезным, чем некоторые другие проблемные части стандарта C, поэтому я бы сказал, что причины в основном исторические (C99 на самом деле не отличается от K & R C).

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

Ответ 4

Вложенные функции - очень деликатная вещь. Вы сделаете их закрытием? Если нет, то они не имеют преимуществ для регулярных функций, поскольку они не могут получить доступ к локальным переменным. Если да, то что вы делаете с переменными, выделенными стекем? Вы должны поместить их в другое место, чтобы, если вы позже вызовете вложенную функцию, переменная все еще существует. Это означает, что они возьмут память, поэтому вам нужно выделить место для них в куче. Без GC это означает, что программист теперь отвечает за очистку функций. Etc... С# делает это, но у них есть GC, и это значительно более новый язык, чем C.

Ответ 5

Также было бы не слишком сложно добавлять функции-члены в структуры, но они также не соответствуют стандарту.

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

Ответ 6

ANSI C существует уже 20 лет. Возможно, в период с 1983 по 1989 год комитет, возможно, обсуждал это с учетом состояния технологии компилятора в то время, но если они сделали свое рассуждение потерянным в тусклом и отдаленном прошлом.

Ответ 7

Я не согласен с Дейвом Вандервисом.

Определение вложенной функции является гораздо лучшим стилем кодирования, чем определение его в глобальной области видимости, что делает его статическим и добавляет комментарий: "Это вспомогательная функция, используемая только myfunc()".

Что, если вам нужна вспомогательная функция для этой вспомогательной функции? Вы добавили бы комментарий "Это вспомогательная функция для первой вспомогательной функции, используемой только myfunc"? Где вы берете имена из необходимых для всех этих функций без полного загрязнения пространства имен?

Как запутанный код может быть написан?

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

Ответ 8

Еще одна причина: не совсем ясно, что вложенные функции ценны. Двадцать с лишним лет назад я использовал крупномасштабное программирование и обслуживание в (VAX) Pascal. У нас было много старого кода, который сильно использовал вложенные функции. Сначала я подумал, что это было здорово (по сравнению с K & R C, с которым я работал раньше) и начал делать это сам. Через некоторое время я решил, что это катастрофа, и остановился.

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

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

Ответ 9

Либо вы не позволяете ссылаться на локальные переменные содержащейся функции в содержащем, а вложенность - это просто функция определения области охвата без особого использования, или вы это делаете. Если вы это делаете, это не такая простая функция: вы должны иметь возможность вызывать вложенную функцию из другой при доступе к правильным данным, а также учитывать рекурсивные вызовы. Это невозможно: методы хорошо известны и хорошо освоены при разработке C (у Algol 60 уже была функция). Но это усложняет организацию времени выполнения и компилятор и предотвращает простое сопоставление на языке ассемблера (указатель на функцию должен содержать информацию об этом, а также есть альтернативы, такие как использование одного gcc). Он был недоступен для языка C реализации системы.

Ответ 10

Ваш вопрос и это ответ образуют тавтологию... Почему? Потому что!

Я мог бы также спросить, почему C не имеет, о, я не знаю, передаю по ссылке вместо передачи по значению по умолчанию. Или что-то еще, чего у него нет.

Если вы хотите вложенные функции, хорошо на ya! Найдите и используйте язык, который их поддерживает.