Почему вложенные функции не поддерживаются стандартом C? - PullRequest
12 голосов
/ 28 августа 2009

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

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

Ответы [ 9 ]

11 голосов
/ 28 августа 2009

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

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

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

10 голосов
/ 28 августа 2009

Я бы хотел процитировать что-то из BDFL (Гвидо ван Россум):

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

Акцент мой.

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

Другая его часть могла бы быть замыканиями .

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

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

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

5 голосов
/ 28 августа 2009

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

5 голосов
/ 28 августа 2009

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

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

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

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

3 голосов
/ 28 августа 2009

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

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

2 голосов
/ 13 августа 2013

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

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

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

2 голосов
/ 28 августа 2009

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

1 голос
/ 20 января 2013

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

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

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

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

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

0 голосов
/ 28 августа 2009

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

...