Вложенные функции - это плохо в gcc? - PullRequest
20 голосов
/ 28 мая 2010

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

Это плохо? Если да, не могли бы вы показать мне несколько неприятных примеров? Каков статус вложенных функций в gcc? Они собираются быть удалены?

Ответы [ 11 ]

17 голосов
/ 28 мая 2010

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

Небольшая история, чтобы проиллюстрировать это - я работал на политехникум из Великобритании, который в основном использовал коробки DEC - в частности, DEC-10 и некоторые VAXen. Весь инженерный факультет использовал в своем коде множество расширений DEC для FORTRAN - они были уверены, что мы останемся магазином DEC навсегда. А затем мы заменили DEC-10 на мэйнфрейм IBM, компилятор FORTRAN которого не поддерживает ни одно из расширений. В тот день было много воплей и скрежетов зубов, могу вам сказать. Мой собственный код FORTRAN (симулятор 8080) был перенесен в IBM за пару часов (почти все занялись изучением того, как управлять компилятором IBM), потому что я написал его на болотном стандарте FORTRAN-77.

7 голосов
/ 19 августа 2010

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

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

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

6 голосов
/ 19 августа 2011

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

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

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

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

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

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

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

Переносимость важна, но gcc доступен во многих средах, и по крайней мере еще одно семейство компиляторов поддерживает вложенные функции - IBM xlc, доступная в AIX, Linux в PowerPC, Linux в BlueGene, Linux on Cell и z / OS. Увидеть http://publib.boulder.ibm.com/infocenter/comphelp/v8v101index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fnested_functions.htm

Вложенные функции доступны в некоторых новых (например, Python) и многих других традиционных языках, включая Ada, Pascal, Fortran, PL / I, PL / IX, Algol и COBOL. В C ++ даже есть две ограниченные версии - методы в локальном классе могут обращаться к статическим (но не автоматически) переменным, содержащимся в его функции, а методы в любом классе могут обращаться к членам и методам данных статического класса. В будущем стандарте C ++ есть функции lamda, которые являются действительно анонимными вложенными функциями. Так что мир программирования имеет большой опыт за и против с ними.

Вложенные функции полезны, но будьте осторожны. Всегда используйте любые функции и инструменты там, где они помогают, а не там, где им больно.

5 голосов
/ 28 мая 2010

Как вы сказали, они плохие в том смысле, что они не являются частью стандарта C, и, как таковые, не реализуются многими (какими-либо?) Другими компиляторами C.

Также помните, что g ++ не реализует вложенные функции, поэтому вам нужно будет удалить их, если вам когда-нибудь понадобится взять часть этого кода и выгрузить его в программу на C ++.

3 голосов
/ 17 июня 2017

Поздно на вечеринку, но я не согласен с утверждением принятого ответа, что

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

В частности:

TL; DR: вложенные функции могут снизить использование стека во встроенных средах

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

void do_something(my_obj *obj) {
    double times2() {
        return obj->value * 2.0;
    }
    double times4() {
        return times2() * times2();
    }
    ...
}

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

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

3 голосов
/ 27 октября 2015

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

  • Используются GCC и вложенные функции

  • используется указатель на вложенную функцию

  • вложенная функция обращается к переменным из родительской функции

  • архитектура предлагает защиту битов NX (без выполнения), например, 64-битный linux.

Когда вышеуказанные условия будут выполнены, GCC создаст батут https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html. Для поддержки батутов стек будет помечен как исполняемый. см .: https://www.win.tue.nl/~aeb/linux/hh/protection.html

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

2 голосов
/ 29 октября 2011

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

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

Но GCC, являющийся бесплатным (как в речи) компилятором, имеет определенное значение ... А некоторые расширения GCC, как правило, становятся стандартами де-факто и реализуются другими компиляторами.

Еще одним расширением GCC, которое я считаю очень полезным, является вычисленное значение goto, т.е. помечает как значения . При кодировании автоматов или интерпретаторов байт-кода это очень удобно.

1 голос
/ 24 октября 2011

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

С другой стороны, они не переносимы на другие компиляторы. (Обратите внимание на компиляторы, а не на устройства. Не так много мест, где gcc не запускается).

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

0 голосов
/ 14 августа 2018

Мне нужны вложенные функции, позволяющие использовать служебный код вне объекта.

У меня есть объекты, которые присматривают за различными аппаратными устройствами. Это структуры, которые передаются по указателю в качестве параметров функциям-членам, а не так, как это происходит автоматически в c ++.

Так что я мог бы иметь

    static int ThisDeviceTestBram( ThisDeviceType *pdev )
    {
        int read( int addr ) { return( ThisDevice->read( pdev, addr ); }
        void write( int addr, int data ) ( ThisDevice->write( pdev, addr, data ); }
        GenericTestBram( read, write, pdev->BramSize( pdev ) );
    }

GenericTestBram не знает и не может знать об ThisDevice, который имеет несколько экземпляров. Но все, что ему нужно, это средства для чтения и письма и размер. ThisDevice-> read (...) и ThisDevice-> Write (...) нуждаются в указателе на ThisDeviceType для получения информации о том, как читать и записывать память блока (Bram) этого конкретного экземпляра. Указатель pdev не может иметь глобальную область действия, поскольку существует несколько экземпляров, и они могут выполняться одновременно. Поскольку доступ осуществляется через интерфейс FPGA, это не простой вопрос передачи адреса, и он варьируется от устройства к устройству.

Код GenericTestBram - это служебная функция:

    int GenericTestBram( int ( * read )( int addr ), void ( * write )( int addr, int data ), int size )
    {
        // Do the test
    }

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

Даже с GCC, однако, вы не можете этого сделать. Проблема в том, что указатель находится вне области видимости, и эта проблема должна быть решена. Единственный способ, которым я знаю, чтобы f (x, ...) неявно осознавал своего родителя, - это передать параметр со значением вне диапазона:

     static int f( int x ) 
     {
         static ThisType *p = NULL;
         if ( x < 0 ) {
             p = ( ThisType* -x );
         }
         else
         {
             return( p->field );
         }
    }
    return( whatever );

Функция f может быть инициализирована чем-то, что имеет указатель, а затем вызываться из любого места. Хотя не идеально.

0 голосов
/ 18 августа 2012

Вложенные функции НЕОБХОДИМЫ на любом серьезном языке программирования.

Без них фактическое представление о функциях невозможно.

Это называется лексическим ограничением.

...