Jump to content

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

(Перенаправлено со встроенной функции )

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

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

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

Терминология

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

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

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

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

Идея вызываемого модуля была первоначально придумана Джоном Мочли и Кэтлин Антонелли во время их работы над 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    (Calls subroutine MYSUB.)
BB    ...          (Will return here after MYSUB is done.)

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

MYSUB NOP          (Storage for MYSUB's return address.)
AA    ...          (Start of MYSUB's body.)
...
JMP MYSUB,I  (Returns to the calling program.)

Инструкция 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.
по ссылке Передается ссылка на аргумент; обычно его адрес Доступен для выбора в большинстве языков, подобных Алголу после Algol 60 , таких как Algol 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;
  }
  return Fib(n - 1) + Fib(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 которые принимают разные типы:

// returns the area of a rectangle defined by height and width
double Area(double h, double w) { return h * w; }

// returns the area of a circle defined by radius
double Area(double r) { return r * r * 3.14; }

int main() {
  double rectangle_area = Area(3, 4);
  double circle_area = Area(5);
}

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

DECLARE gen_name GENERIC(
    name  WHEN(FIXED BINARY),
    flame  WHEN(FLOAT),
    pathname OTHERWISE);

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

Закрытие

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

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

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

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

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

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

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

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

           BAL  14, SUBRTN01    go to a subroutine, storing return address in R14
           B    TABLE(15)      use returned value in reg 15 to index the branch table,
*                              branching to the appropriate branch instr.
TABLE      B    OK             return code =00   GOOD                  }
           B    BAD            return code =04   Invalid input         } Branch table
           B    ERROR          return code =08   Unexpected condition  }

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

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

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

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

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

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

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

Некоторые оптимизации для минимизации накладных расходов на вызовы могут показаться простыми, но их нельзя использовать, если вызываемый объект имеет побочные эффекты. Например, в выражении (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 INPUT GIVE ME A NUMBER; N
110 PRINT THE SQUARE ROOT OF; N; 
120 PRINT IS; SQRT(N)
130 RETURN

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

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

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

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

Sub SayHello
    TextWindow.WriteLine("Hello!")
EndSub

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

Визуальный Бейсик

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

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

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

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

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

Sub DoSomething()
    ' Some Code Here
End Sub

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

Function GiveMeFive() as Integer
    GiveMeFive= 5
End Function

Это возвращает значение 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() {
    /* some code */
}

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

int giveMeFive() {
    return 5;
}

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

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

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

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

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

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

change_sign: procedure(array);
  declare array(*,*) float;
  array = -array;
end change_sign;

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

/* first array bounds from -5 to +10 and 3 to 9 */
declare array1 (-5:10, 3:9)float;
/* second array bounds from 1 to 16 and 1 to 16 */
declare array2 (16,16) float;
call change_sign(array1);
call change_sign(array2);

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

def format_greeting(name):
    return "Welcome " + name
def greet_martin():
    print(format_greeting("Martin"))

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

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

A :- B

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

A if B

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

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

mother_child(elizabeth, charles).
father_child(charles, william).
father_child(charles, harry).
parent_child(X, Y) :- mother_child(X, Y).
parent_child(X, Y) :- father_child(X, Y).

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

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

?- parent_child(elizabeth, Y).
Y = charles.

?- parent_child(X, Y).
X = elizabeth,
Y = charles.

X = charles,
Y = harry.

X = charles,
Y = william.

?- parent_child(william, harry).
no.

?- parent_child(elizabeth, charles).
yes.

См. также

[ редактировать ]
  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» .
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 12c06e939a08a05d299854871c920e1f__1722528480
URL1:https://arc.ask3.ru/arc/aa/12/1f/12c06e939a08a05d299854871c920e1f.html
Заголовок, (Title) документа по адресу, URL1:
Function (computer programming) - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)