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

Из Википедии, бесплатной энциклопедии
(Перенаправлено из Подпрограммы )

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

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

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

Терминология [ править ]

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

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

  • Подпрограмма использовалась давно, но на сегодняшний день устарела.
  • Термины «подпрограмма» и «подпрограмма» (оба устаревшие) описывают одно и то же, но подчеркивают подчиненные отношения вызова, которые аналогичны тому, как подкаталог структурно подчинен каталогу : подкаталог является каталогом, а (за исключением корня) каталог является подкаталог.
  • Некоторые считают, что функция подразумевает математическую функцию; никаких побочных эффектов нет, но во многих контекстах (языках) функция не подразумевает, что
  • В контексте Visual Basic , Sub, сокращение от subroutine или subprocedure , — это имя вызываемого объекта, который не возвращает значение, тогда как Function возвращает значение
  • В C и C++ есть функции , но родственные языки C# и Java слова используют метод для обозначения того, что по сути является функцией, которая является членом объекта .

История [ править ]

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

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

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

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

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

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

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

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

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

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

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

Языковая поддержка [ править ]

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

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

Библиотеки [ править ]

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

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

Возврат непрямым прыжком [ править ]

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

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

Перейти к подпрограмме [ править ]

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

В 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-х годов, включают в себя специальные инструкции для эта цель.

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

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

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

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

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

Отложенная укладка [ править ]

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

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

Особенности [ править ]

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

Реализации [ править ]

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

Общие характеристики [ править ]

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

Именование [ править ]

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

Синтаксис вызова [ править ]

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

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

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

Параметры [ править ]

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

Соглашение Описание Используется в
по стоимости Копия аргумента передается По умолчанию в большинстве языков, подобных Алголу, после Алгола 60 , таких как Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada и многих других, включая C, C++ и Java.
по ссылке Передается ссылка на аргумент; обычно его адрес Доступен для выбора в большинстве алгоритмоподобных языков после алгоритма 60 , таких как алгоритм 68, Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada и многих других, включая C++, Fortran, PL/I.
по результату Значение, вычисленное во время вызова, копируется в аргумент при возврате. Выходные параметры Ады
по значению-результату Передается копия аргумента, и значение, вычисленное во время вызова, копируется в аргумент при возврате. Алгол, Swift входные параметры
по имени Как макрос — замените параметры невычисленными выражениями аргументов, затем оцените аргумент в контексте вызывающего объекта каждый раз, когда вызываемый объект использует параметр. Алголь, Скала
по постоянному значению Аналогично по значению, за исключением того, что параметр рассматривается как константа. НЕНАзначаемые параметры PL/I, параметры Ada IN

Возвращаемое значение [ править ]

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

Побочные эффекты [ править ]

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

Побочные эффекты считает нежелательными Роберт К. Мартин , известный своей пропагандой принципов проектирования. Мартин утверждает, что побочные эффекты могут привести к временной связи или зависимости от порядка. [21]

В строго функциональных языках программирования, таких как 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 , могут использовать один из этих регистров в качестве указателя стека .

Вложенная область действия [ править ]

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

Реентерабельность [ править ]

Если вызываемый объект может быть выполнен правильно, даже когда другое выполнение того же вызываемого объекта уже выполняется, этот вызываемый объект называется реентерабельным . Повторно входящий вызов также полезен в многопоточных ситуациях, поскольку несколько потоков могут вызывать один и тот же вызываемый объект, не опасаясь мешать друг другу. В 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должен быть получен еще раз перед вторым вызовом, поскольку первый вызов мог изменить его. Определить, имеет ли вызываемый объект побочный эффект, сложно – более того, неразрешимо в силу теоремы Райса . Таким образом, хотя эта оптимизация безопасна для чисто функционального языка программирования, компилятор для языка, не ограничивающегося функциональным, обычно предполагает худший случай, когда каждый вызываемый объект может иметь побочные эффекты.

Inlining[editвстраивание

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

Делюсь [ править ]

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

Взаимодействие [ править ]

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

Встроенные функции [ править ]

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

Программирование [ править ]

Компромиссы [ править ]

Преимущества [ править ]

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

  • Разложение сложной задачи программирования на более простые шаги: это один из двух основных инструментов структурного программирования , наряду со структурами данных.
  • Уменьшение дублирования кода в программе
  • Включение повторного использования кода в нескольких программах
  • Разделение большой задачи программирования между различными программистами или различными этапами проекта.
  • Скрытие деталей реализации от пользователей функции
  • Улучшение читаемости кода путем замены блока кода вызовом функции, где описательное имя функции служит для описания блока кода. Это делает вызывающий код кратким и читабельным, даже если функция не предназначена для повторного использования.
  • Улучшение отслеживаемости (т.е. большинство языков предлагают способы получения трассировки вызовов, которая включает имена задействованных функций и, возможно, даже дополнительную информацию, такую ​​​​как имена файлов и номера строк); если не разложить код на функции, отладка будет серьезно затруднена.

Недостатки [ править ]

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

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

Соглашения [ править ]

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

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

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

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

Примеры [ править ]

Ранний БЕЙСИК [ править ]

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

10   A BASIC PROGRAM 
 20   GOSUB   100 
 30   GOTO   20 
 100   ВВОД   «  ДАЙТЕ   МНЕ   НОМЕР   »  ;  REM    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++ [ править ]

В 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() чтобы написать «Добро пожаловать, Мартин» на консоль.

Пролог [ править ]

В процедурной интерпретации логических программ логические импликации ведут себя как процедуры редукции цели. Правило ( или предложение ) вида:

A :- B

который имеет логическое чтение:

A if B

ведет себя как процедура, сводящая к минимуму цели, которые объединяются с A к подцелям, которые являются примерами B.

Рассмотрим, например, программу на Прологе:

мать_ребёнка  (  Элизабет  ,   Чарльз  ). 
  отец_ребёнок  (  Чарльз  ,   Уильям  ). 
  отец_ребёнок  (  Чарльз  ,   Гарри  ). 
  родитель_ребенок  (  X  ,   Y  )   : —   мать_ребенок  (  X  ,   Y  ). 
  родитель_ребенок  (  X  ,   Y  )   : —   отец_ребенок  (  X  ,   Y  ). 

Обратите внимание, что функция материнства X = mother(Y)представлено отношением, как в реляционной базе данных . Однако отношения в Прологе функционируют как вызываемые единицы.

Например, вызов процедуры ?- parent_child(X, charles) производит вывод X = elizabeth. Но ту же процедуру можно вызвать и с другими шаблонами ввода-вывода. Например:

?-   родитель_ребенок  (  Элизабет  ,   Y  ). 
  Y   =   Чарльз  . 

  ?-   родитель_ребенок  (  X  ,   Y  ). 
  X   =   Элизабет  , 
 Y   =   Чарльз  . 

  X   =   Чарльз  , 
 Y   =   Гарри  . 

  X   =   Чарльз  , 
 Y   =   Уильям  . 

  ?-   родитель_ребенок  (  Уильям  ,   Гарри  ). 
  нет  . 

  ?-   родитель_ребенок  (  Элизабет  ,   Чарльз  ). 
  да  . 

См. также [ править ]

Ссылки [ править ]

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