stringtranslate.com

Функция (компьютерное программирование)

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

Вызываемые модули представляют собой мощный инструмент программирования. [2] Основная цель — обеспечить разложение большой и/или сложной проблемы на фрагменты с относительно низкой когнитивной нагрузкой и присвоить этим фрагментам осмысленные имена (если они не анонимны). Разумное применение может снизить затраты на разработку и поддержку программного обеспечения, одновременно повышая его качество и надежность. [3]

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

История

Идея вызываемого модуля была первоначально задумана Джоном Мокли и Кэтлин Антонелли во время их работы над ENIAC [ 4] и записана на Гарвардском симпозиуме в январе 1947 года по теме «Подготовка задач для машин типа EDVAC ». [5] Морису Уилксу , Дэвиду Уилеру и Стэнли Гиллу обычно приписывают формальное изобретение этой концепции, которую они назвали закрытой подпрограммой , [6] [7] в отличие от открытой подпрограммы или макроса . [8] Однако Алан Тьюринг обсуждал подпрограммы в статье 1945 года о предложениях по проектированию NPL ACE , доходя до того, что изобрел концепцию стека адресов возврата . [9]

Идея подпрограммы возникла после того, как вычислительные машины уже существовали некоторое время. Инструкции арифметических действий и условного перехода были запланированы заранее и изменились сравнительно мало, но специальные инструкции, используемые для вызовов процедур, сильно изменились за прошедшие годы. Самые ранние компьютеры и микропроцессоры, такие как Manchester Baby и RCA 1802 , не имели ни одной инструкции вызова подпрограммы. Подпрограммы можно было реализовать, но для этого требовалось, чтобы программисты использовали последовательность вызовов — серию инструкций — на каждом месте вызова .

Подпрограммы были реализованы в Z4 Конрада Цузе в 1945 году.

В 1945 году Алан М. Тьюринг использовал термины «похоронить» и «вытащить из погребения» как средство вызова подпрограмм и возврата из них. [10] [11]

В январе 1947 года Джон Мочли представил общие заметки на «Симпозиуме крупномасштабных цифровых вычислительных машин», организованном совместно Гарвардским университетом и Управлением артиллерийских вооружений ВМС США. Здесь он обсуждает последовательную и параллельную работу, предполагая

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

Другими словами, можно обозначить подпрограмму A как деление, подпрограмму B как комплексное умножение и подпрограмму C как вычисление стандартной ошибки последовательности чисел и так далее по списку подпрограмм, необходимых для конкретной задачи. ... Все эти подпрограммы потом будут храниться в машине, и останется только сделать краткую ссылку на них по номеру, как они указаны в кодировке. [5]

Кей МакНалти тесно сотрудничала с Джоном Мочли в команде ENIAC и разработала идею подпрограмм для компьютера ENIAC , который она программировала во время Второй мировой войны. [12] Она и другие программисты ENIAC использовали подпрограммы для расчета траекторий ракет. [12]

Гольдстайн и фон Нейман написали статью от 16 августа 1948 года, в которой обсуждается использование подпрограмм. [13]

Некоторые очень ранние компьютеры и микропроцессоры, такие как IBM 1620 , Intel 4004 и Intel 8008 , а также микроконтроллеры PIC , имеют вызов подпрограммы с одной командой, который использует выделенный аппаратный стек для хранения адресов возврата — такое оборудование поддерживает только несколько уровней. вложенности подпрограмм, но может поддерживать рекурсивные подпрограммы. Машины до середины 1960-х годов, такие как UNIVAC I , PDP-1 и IBM 1130 , обычно используют соглашение о вызовах , при котором счетчик команд сохраняется в первой ячейке памяти вызываемой подпрограммы. Это допускает произвольно глубокие уровни вложенности подпрограмм, но не поддерживает рекурсивные подпрограммы. В IBM System/360 была инструкция вызова подпрограммы, которая помещала сохраненное значение счетчика команд в регистр общего назначения; это можно использовать для поддержки произвольно глубокой вложенности подпрограмм и рекурсивных подпрограмм. Burroughs B5000 [14] (1961 г.) — один из первых компьютеров, сохраняющих данные возврата из подпрограммы в стек.

DEC PDP-6 [15] (1964 г.) является одной из первых машин на базе аккумулятора, в которой имеется инструкция вызова подпрограммы, которая сохраняет адрес возврата в стеке, адресуемом аккумулятором или индексным регистром. Более поздние линейки PDP-10 (1966 г.), PDP-11 (1970 г.) и VAX-11 (1976 г.) последовали этому примеру; эта функция также поддерживает как произвольно глубокую вложенность подпрограмм, так и рекурсивные подпрограммы. [16]

Языковая поддержка

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

Одним из первых языков программирования, поддерживающих написанные пользователем подпрограммы и функции, был FORTRAN II . Компилятор IBM FORTRAN II был выпущен в 1958 году. АЛГОЛ 58 и другие ранние языки программирования также поддерживали процедурное программирование.

Библиотеки

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

Многие ранние компьютеры загружали инструкции программы в память с перфоленты . Затем каждая подпрограмма может быть представлена ​​отдельным куском ленты, загруженным или склеенным до или после основной программы (или «основной линии» [17] ); и одна и та же лента с подпрограммами может затем использоваться многими разными программами. Похожий подход использовался в компьютерах, загружавших программные инструкции с перфокарт . Название « библиотека подпрограмм» первоначально означало библиотеку в буквальном смысле, в которой хранились индексированные коллекции лент или колод карт для коллективного использования.

Возвращение непрямым прыжком

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

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

Перейти к подпрограмме

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

В IBM System/360 , например, инструкции ветвления BAL или BALR, предназначенные для вызова процедур, сохраняли адрес возврата в регистре процессора, указанном в инструкции, по соглашению, регистре 14. Для возврата подпрограмме нужно было только выполнить инструкция косвенного ветвления (BR) через этот регистр. Если подпрограмме нужен этот регистр для какой-то другой цели (например, для вызова другой подпрограммы), она сохранит содержимое регистра в отдельной области памяти или в стеке регистров .

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

...JSB MYSUB (вызывает подпрограмму MYSUB.)BB ... (Вернусь сюда после завершения MYSUB.)

для вызова подпрограммы MYSUB из основной программы. Подпрограмма будет закодирована как

MYSUB NOP (Хранилище обратного адреса MYSUB.)АА... (Начало тела MYSUB.)...JMP MYSUB,I (возврат в вызывающую программу.)

Инструкция JSB поместила адрес инструкции NEXT (а именно, BB) в ячейку, указанную в качестве ее операнда (а именно, MYSUB), а затем после этого разветвилась к ячейке NEXT (а именно, AA = MYSUB + 1). Затем подпрограмма может вернуться в основную программу, выполнив непрямой переход JMP MYSUB, I, который перейдет к ячейке, хранящейся в ячейке MYSUB.

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

Кстати, аналогичный метод использовался Lotus 1-2-3 в начале 1980-х годов для обнаружения зависимостей пересчета в электронной таблице. А именно, в каждой ячейке было зарезервировано место для хранения обратного адреса. Поскольку циклические ссылки не допускаются для естественного порядка пересчета, это позволяет обход дерева без резервирования места для стека в памяти, что было очень ограничено на небольших компьютерах, таких как IBM PC .

Стек вызовов

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

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

Стек вызовов обычно реализуется как непрерывная область памяти. Это произвольный выбор конструкции, является ли нижняя часть стека самым низким или самым высоким адресом в этой области, так что стек может расти в памяти вперед или назад; однако многие архитектуры выбрали последнее. [ нужна цитата ]

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

Когда впервые были представлены вызовы процедур на основе стека, важной мотивацией была экономия драгоценной памяти. [ нужна цитация ] При такой схеме компилятору не нужно резервировать отдельное пространство в памяти для личных данных (параметров, адреса возврата и локальных переменных) каждой процедуры. В любой момент стек содержит только частные данные активных в данный момент вызовов (а именно тех, которые были вызваны, но еще не вернулись). Из-за того, как программы обычно собирались из библиотек, нередко можно было найти программы, включающие в себя тысячи функций, из которых в любой момент времени активны лишь несколько. [ нужна цитация ] Для таких программ механизм стека вызовов может сэкономить значительные объемы памяти. Действительно, механизм стека вызовов можно рассматривать как самый ранний и простой метод автоматического управления памятью .

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

В многопоточной среде обычно имеется более одного стека. [18] Среда, которая полностью поддерживает сопрограммы или отложенные вычисления, может использовать структуры данных, отличные от стеков, для хранения записей активации.

Отложенная укладка

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

Эти накладные расходы наиболее очевидны и нежелательны в конечных процедурах или конечных функциях , которые возвращаются без выполнения каких-либо вызовов процедур. [19] [20] [21] Чтобы уменьшить эти накладные расходы, многие современные компиляторы пытаются отложить использование стека вызовов до тех пор, пока он действительно не понадобится. [ нужна цитация ] Например, вызов процедуры P может сохранять адрес возврата и параметры вызываемой процедуры в определенных регистрах процессора и передавать управление телу процедуры простым переходом. Если процедура P возвращается без каких-либо других вызовов, стек вызовов вообще не используется. Если P необходимо вызвать другую процедуру Q , он будет использовать стек вызовов для сохранения содержимого любых регистров (например, адреса возврата), которые понадобятся после возврата Q.

Функции

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

Реализации

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

Общие характеристики

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

Именование

Некоторые языки, такие как Pascal , Fortran , Ada и многие диалекты BASIC , используют другое имя для вызываемой единицы, которая возвращает значение ( функция или подпрограмма ), а не для той , которая этого не делает ( подпрограмма или процедура ). Другие языки, такие как C , C++ , C# и Lisp , используют только одно имя для вызываемой единицы — function . В языках семейства C это ключевое слово используется для обозначения отсутствия возвращаемого значения.void

Синтаксис вызова

Если объявлено, что он возвращает значение, вызов можно внедрить в выражение , чтобы использовать возвращаемое значение. Например, вызываемая единица измерения квадратного корня может называться как y = sqrt(x).

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

В некоторых старых языках требуется ключевое слово для вызовов, которые не принимают возвращаемое значение, например CALL print("hello").

Параметры

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

Возвращаемое значение

В некоторых языках, таких как BASIC, вызываемый объект имеет другой синтаксис (т.е. ключевое слово) для вызываемого объекта, который возвращает значение, и для вызываемого объекта, который его не возвращает. В других языках синтаксис одинаков. В некоторых из этих языков используется дополнительное ключевое слово для объявления отсутствия возвращаемого значения; например, voidв C, C++ и C#. В некоторых языках, таких как Python, разница заключается в том, содержит ли тело оператор возврата со значением, и конкретный вызываемый объект может возвращать значение со значением или без него в зависимости от потока управления.

Побочные эффекты

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

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

Локальные переменные

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

Вложенный вызов – рекурсия

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

Языки, восходящие к ALGOL , PL/I и C , а также современные языки, почти всегда используют стек вызовов, обычно поддерживаемый наборами команд, для обеспечения записи активации для каждого вызова. Таким образом, вложенный вызов может изменять свои локальные переменные, не затрагивая ни одну из переменных приостановленных вызовов.

Рекурсия позволяет напрямую реализовать функциональность, определенную математической индукцией и рекурсивными алгоритмами «разделяй и властвуй» . Вот пример рекурсивной функции на C/C++ для поиска чисел Фибоначчи :

int Fib ( int n ) { if ( n <= 1 ) { return n ; } вернуть Фиб ( n - 1 ) + Фиб ( n - 2 ); }                   

Ранние языки, такие как Фортран, изначально не поддерживали рекурсию, поскольку для каждого вызываемого объекта выделялся только один набор переменных и адрес возврата. [22] Ранние компьютерные наборы команд затрудняли хранение адресов возврата и переменных в стеке. Машины с индексными регистрами или регистрами общего назначения , например, серии CDC 6000 , PDP-6 , GE 635 , System/360 , серии UNIVAC 1100 , могут использовать один из этих регистров в качестве указателя стека .

Вложенная область

Некоторые языки, такие как Pascal , PL/I и Ada , поддерживают вложенную область видимости для вызываемого объекта, то есть вложенную функцию , так что внутренний вызываемый объект может быть вызван только внешним вызываемым объектом. Внутренний вызываемый объект может иметь доступ к локальным переменным внешнего.

Реентерабельность

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

Перегрузка

Некоторые языки поддерживают перегрузку — разрешают несколько вызываемых объектов с одинаковым именем в одной области, но работающих с разными типами ввода. Рассмотрим функцию квадратного корня, примененную к действительному числу, комплексному числу и входной матрице. Алгоритм для каждого типа ввода различен, и возвращаемое значение может иметь другой тип. Написав три отдельных вызываемых объекта с одинаковым именем. т.е. sqrt , результирующий код может быть проще писать и поддерживать, поскольку каждый из них имеет имя, которое относительно легко понять и запомнить, вместо того чтобы давать более длинные и сложные имена, такие как sqrt_real , sqrt_complex , qrt_matrix .

Перегрузка поддерживается во многих языках, поддерживающих строгую типизацию . Часто компилятор выбирает перегрузку для вызова в зависимости от типа входных аргументов, или компилятор терпит неудачу, если входные аргументы не выбирают перегрузку. Старые и слабо типизированные языки обычно не поддерживают перегрузку.

Вот пример перегрузки в C++ двух функций Area, принимающих разные типы:

// возвращает площадь прямоугольника, определенную высотой и шириной. double Area ( double h , double w ) { return h * w ; }          // возвращает площадь круга, определенную радиусом double Area ( double r ) { return r * r * 3.14 ; }          int main () { двойной прямоугольник_область = Площадь ( 3 , 4 ); двойной круг_площадь = Площадь ( 5 ); }           

PL/I имеет GENERICатрибут для определения общего имени для набора ссылок на записи, вызываемых с различными типами аргументов. Пример:

ОБЪЯВИТЬ имя_гена GENERIC( имя КОГДА(ФИКСИРОВАННЫЙ ДВОИЧНЫЙ), пламя КОГДА(FLOAT), путь ИНАЧЕ);

Для каждой записи можно указать несколько определений аргументов. Вызов «gen_name» приведет к вызову «name», когда аргумент FIXED BINARY, «flame», когда FLOAT и т. д. Если аргумент не соответствует ни одному из вариантов, будет вызван «pathname».

Закрытие

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

Отчеты об исключениях

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

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

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

В IBM System/360 , где от подпрограммы ожидался код возврата, возвращаемое значение часто проектировалось так, чтобы оно было кратно 4, чтобы его можно было использовать в качестве прямого индекса таблицы перехода в таблицу перехода, часто расположенную сразу после вызвать инструкцию, чтобы избежать дополнительных условных проверок, что еще больше повышает эффективность. На языке ассемблера System/360 можно было бы написать, например:

 BAL 14, SUBRTN01 перейти к подпрограмме, сохраняющей адрес возврата в R14. B TABLE(15) использует возвращаемое значение в регистре 15 для индексации таблицы ветвей,* переход на соответствующую ветку инстр.ТАБЛИЦА Б. Код возврата ОК =00 ХОРОШО } B BAD код возврата =04 Неверный ввод } Таблица ответвлений B Код возврата ОШИБКА =08 Непредвиденное состояние }

Накладные расходы на вызов

Вызов имеет накладные расходы во время выполнения , которые могут включать, помимо прочего:

Для минимизации стоимости вызовов во время выполнения используются различные методы.

Оптимизация компилятора

Некоторые оптимизации для минимизации накладных расходов на вызовы могут показаться простыми, но их нельзя использовать, если вызываемый объект имеет побочные эффекты. Например, в выражении (f(x)-1)/(f(x)+1)функцию fнельзя вызвать только один раз, а ее значение использовать два раза, поскольку два вызова могут возвращать разные результаты. Более того, в тех немногих языках, которые определяют порядок вычисления операндов оператора деления, значение xдолжно быть получено снова перед вторым вызовом, поскольку первый вызов мог его изменить. Определить, имеет ли вызываемый объект побочный эффект, сложно – более того, неразрешимо в силу теоремы Райса . Таким образом, хотя эта оптимизация безопасна для чисто функционального языка программирования, компилятор для языка, не ограничивающегося функциональным, обычно предполагает худший случай, когда каждый вызываемый объект может иметь побочные эффекты.

Встраивание

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

Совместное использование

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

Взаимодействие

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

Встроенные функции

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

Программирование

Компромиссы

Преимущества

Преимущества разбиения программы на функции включают в себя:

Недостатки

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

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

Конвенции

В отношении вызываемых объектов было разработано множество соглашений по программированию.

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

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

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

Примеры

Ранний БЕЙСИК

Ранние варианты BASIC требуют, чтобы каждая строка имела уникальный номер ( номер строки ), который упорядочивает строки для выполнения, не обеспечивает разделения вызываемого кода, не имеет механизма передачи аргументов или возврата значения, а все переменные являются глобальными. Он предоставляет команду GOSUB, где sub — это сокращение от sub процедуры , подпроцедуры или подпрограммы . Управление переходит к указанному номеру строки, а затем при возврате переходит к следующей строке.

10 REM A BASIC PROGRAM 20 GOSUB 100 30 GOTO 20 100 ВВОД « ДАЙТЕ МНЕ НОМЕР » ; N 110 ПЕЧАТЬ « КВАДРАТНЫЙ КОРЕНЬ » ; _ _ Н ; 120 ПЕЧАТЬ « Есть » ; SQRT ( Н ) 130 ВОЗВРАТ                      

Этот код неоднократно просит пользователя ввести число и сообщает квадратный корень из значения. Строки 100-130 являются вызываемыми.

Маленький базовый

В Microsoft Small Basic , предназначенном для студентов, впервые изучающих программирование на текстовом языке, вызываемая единица называется подпрограммой . Ключевое Subслово обозначает начало подпрограммы, за ним следует идентификатор имени. Последующие строки представляют собой тело, которое заканчивается ключевым EndSubсловом. [25]

Sub SayHello TextWindow . WriteLine ( «Привет!» ) EndSub  

Это можно назвать так SayHello().[26]

Visual Basic

В более поздних версиях Visual Basic (VB), включая последнюю линейку продуктов и VB6 , термин процедура используется для обозначения концепции вызываемого модуля. Ключевое слово Subиспользуется для возврата значения и Functionдля возврата значения. При использовании в контексте класса процедура является методом.[27]

У каждого параметра есть тип данных , который можно указать, но в противном случае по умолчанию используется Objectдля более поздних версий на основе .NET и варианта для VB6 . [28]

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

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

Sub DoSomething () 'Здесь некоторый код End Sub   

Не возвращает значение и его следует называть автономным, напримерDoSomething

Функция GiveMeFive () как целое число GiveMeFive = 5 Конечная функция      

Это возвращает значение 5, и вызов может быть частью выражения, напримерy = x + GiveMeFive()

Sub AddTwo ( ByRef intValue as Integer ) intValue = intValue + 2 End Sub          

Это имеет побочный эффект — изменяет переменную, передаваемую по ссылке, и ее можно вызывать для переменной vтипа AddTwo(v). Если перед вызовом v равно 5, после вызова оно будет равно 7.

С и С++

В C и C++ вызываемая единица называется функцией . Определение функции начинается с имени типа значения, которое она возвращает, или voidс указания того, что она не возвращает значение. За ним следует имя функции, формальные аргументы в круглых скобках и строки тела в фигурных скобках.

В C++ функция, объявленная в классе (как нестатическая), называется функцией-членом или методом . Функцию вне класса можно назвать свободной функцией , чтобы отличить ее от функции-члена.[29]

void doSomething () { /* некоторый код */ }   

Эта функция не возвращает значение и всегда называется автономной, напримерdoSomething()

int GiveMeFive () { возвращение 5 ; }    

Эта функция возвращает целочисленное значение 5. Вызов может быть автономным или в виде выражения типаy = x + giveMeFive()

void addTwo ( int * pi ) { * pi += 2 ; }      

Эта функция имеет побочный эффект — изменяет значение, передаваемое по адресу, на входное значение плюс 2. Ее можно вызывать для переменной, vнапример addTwo(&v), когда амперсанд (&) сообщает компилятору передать адрес переменной. Если перед вызовом v равно 5, после вызова оно будет равно 7.

void addTwo ( int & я ) { я += 2 ; }      

Для этой функции требуется C++ — она не будет компилироваться как C. Она ведет себя так же, как и предыдущий пример, но передает фактический параметр по ссылке, а не передает его адрес. Такой вызов addTwo(v)не включает амперсанд, поскольку компилятор обрабатывает передачу по ссылке без синтаксиса в вызове.

ПЛ/Я

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

Change_sign: процедура (массив); объявить массив(*,*) с плавающей запятой; массив = -массив;конец изменения_знака;

Это можно вызвать с различными массивами следующим образом:

/* первый массив ограничен от -5 до +10 и от 3 до 9 */объявить массив1 (-5:10, 3:9)float;/* второй массив ограничен значениями от 1 до 16 и от 1 до 16 */объявить массив2 (16,16) с плавающей запятой;вызов Change_sign(массив1);вызовите Change_sign(массив2);

Питон

В Python ключевое слово defобозначает начало определения функции. Операторы тела функции следуют с отступом в последующих строках и заканчиваются на строке, отступ которой совпадает с отступом первой строки или конца файла. [30]

def  format_greeting ( имя ):  вернуть  «Добро пожаловать»  +  имя def  greeting_martin ():  напечатать ( format_greeting ( «Мартин» ))

Первая функция возвращает текст приветствия, включающий имя, переданное вызывающим абонентом. Вторая функция вызывает первую и вызывается так же, как greet_martin()для написания на консоли «Добро пожаловать, Мартин».

Смотрите также

Рекомендации

  1. ^ «Терминологический словарь». nist.gov . НИСТ . Проверено 9 февраля 2024 г. Вызываемая единица: (программного обеспечения или логического проекта) Функция, метод, операция, подпрограмма, процедура или аналогичная структурная единица, которая появляется внутри модуля.
  2. ^ Дональд Э. Кнут (1997). Искусство компьютерного программирования, Том I: Фундаментальные алгоритмы . Аддисон-Уэсли. ISBN 0-201-89683-4.
  3. ^ О.-Дж. Даль; Э. В. Дейкстра; АВТОМОБИЛЬ Хоара (1972). Структурное программирование . Академическая пресса. ISBN 0-12-200550-3.
  4. Субрата Дасгупта (7 января 2014 г.). Все началось с «Бэббиджа: генезис компьютерных наук». Издательство Оксфордского университета. стр. 155–. ISBN 978-0-19-930943-6.
  5. ^ аб Мочли, JW (1982). «Подготовка задач для машин типа EDVAC». В Рэнделле, Брайан (ред.). Происхождение цифровых компьютеров . Спрингер. стр. 393–397. дои : 10.1007/978-3-642-61812-3_31. ISBN 978-3-642-61814-7.
  6. ^ Уилер, ди-джей (1952). «Использование подпрограмм в программах» (PDF) . Материалы национального собрания ACM 1952 года (Питтсбург) на тему - ACM '52 . п. 235. дои : 10.1145/609784.609816 .
  7. ^ Уилкс, М.В.; Уилер, диджей; Гилл, С. (1951). Подготовка программ для электронной цифровой вычислительной машины . Аддисон-Уэсли.
  8. ^ Дайнит, Джон (2004). «Открытая подпрограмма». Компьютерный словарь». Энциклопедия.com . Проверено 14 января 2013 г.
  9. ^ Тьюринг, Алан М. (1945), Отчет доктора А. М. Тьюринга о предложениях по разработке автоматической вычислительной машины (ACE): представлен Исполнительному комитету НПЛ в феврале 1946 г.перепечатано в Copeland, BJ , изд. (2005). Автоматическая вычислительная машина Алана Тьюринга . Оксфорд: Издательство Оксфордского университета. п. 383. ИСБН 0-19-856593-3.
  10. ^ Тьюринг, Алан Мэтисон (19 марта 1946) [1945], Предложения по разработке в математическом отделе автоматической вычислительной машины (ACE)(Примечание. Представлено 19 марта 1946 г. Исполнительному комитету Национальной физической лаборатории (Великобритания).)
  11. ^ Карпентер, Брайан Эдвард ; Доран, Роберт Уильям (1 января 1977 г.) [октябрь 1975 г.]. «Другая машина Тьюринга». Компьютерный журнал . 20 (3): 269–279. дои : 10.1093/comjnl/20.3.269 .(11 страниц)
  12. ^ аб Исааксон, Уолтер (18 сентября 2014 г.). «Уолтер Айзексон о женщинах ENIAC». Удача . Архивировано из оригинала 12 декабря 2018 года . Проверено 14 декабря 2018 г.
  13. ^ Герман Х. Голдстайн; Джон фон Нейман (1947). «Часть II, том I-3, Планирование и кодирование задач для электронного вычислительного прибора» (PDF) . Отчет о математических и логических аспектах электронного вычислительного прибора (Технический отчет).(см. стр. 163 pdf-файла соответствующей страницы)
  14. ^ Эксплуатационные характеристики процессоров Burroughs B5000 (PDF) . Редакция А. Берроуз Корпорейшн . 1963. 5000-21005 . Проверено 8 февраля 2024 г.
  15. ^ «Инструкции по опусканию» (PDF) . Программируемый процессор данных 6 — Справочник (PDF) . п. 37 . Проверено 8 февраля 2024 г.
  16. ^ Гай Льюис Стил-младший, AI Memo 443. «Разоблачение мифа о «дорогом вызове процедур»; или реализации вызова процедур считаются вредными». Раздел «C. Почему вызовы процедур имеют плохую репутацию».
  17. ^ Франк, Томас С. (1983). Введение в PDP-11 и его язык ассемблера. Серия программного обеспечения Prentice-Hall. Прентис-Холл. п. 195. ИСБН 9780134917047. Проверено 6 июля 2016 г. Мы могли бы предоставить нашему сборщику копии исходного кода для всех наших полезных подпрограмм, а затем, предоставляя ему основную программу для сборки, сказать ему, какие подпрограммы будут вызываться в основной [...]
  18. ^ Баттлар, Дик; Фаррелл, Жаклин; Николс, Брэдфорд (1996). Программирование PThreads: стандарт POSIX для улучшения многопроцессорной обработки. «О'Рейли Медиа, Инк.». стр. 2–5. ISBN 978-1-4493-6475-5. ОСЛК  1036778036.
  19. ^ «Информационный центр АРМ». Infocenter.arm.com . Проверено 29 сентября 2013 г.
  20. ^ «Использование стека x64» . Документы Майкрософт . Майкрософт . Проверено 5 августа 2019 г.
  21. ^ «Типы функций». Msdn.microsoft.com . Проверено 29 сентября 2013 г.
  22. ^ Верховефф, Том (2018). «Мастер-класс по рекурсии». В Бёкенхауэре — Ханс-Иоахим; Комм, Деннис; Унгер, Уолтер (ред.). Приключения между нижними пределами и большими высотами: очерки, посвященные Юрай Хромковичу к его 60-летию . Спрингер. п. 616. ИСБН 978-3-319-98355-4. ОСЛК  1050567095.
  23. ^ «Встроенные функции». IBM.com . Проверено 25 декабря 2023 г.
  24. ^ Учебные материалы Python. Апрель 2023. с. 87 . Проверено 25 декабря 2023 г.
  25. ^ «Маленький базовый». Малый базовый . Проверено 8 февраля 2024 г.
  26. ^ «Небольшое базовое руководство по началу работы: Глава 9: Подпрограммы» . Майкрософт.
  27. ^ «Процедуры в Visual Basic». Microsoft Learn . Проверено 8 февраля 2024 г.
  28. ^ «Оператор Dim (Visual Basic)» . Microsoft Learn . Проверено 8 февраля 2024 г.
  29. ^ «Что подразумевается под свободной функцией» .
  30. ^ «4. Дополнительные инструменты потока управления — документация Python 3.9.7» .