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

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

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

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

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

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

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

Содержимое подпрограммы - это ее тело, то есть фрагмент программного кода, который выполняется при вызове или вызове подпрограммы.

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

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

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

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

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

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

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

Языки программирования высокого уровня обычно включают специальные конструкции для:

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

Некоторые языки программирования , такие как Паскаль , Фортран , Ada и многие диалекты в BASIC , различать функции или функции подпрограмм, которые обеспечивают явное возвращаемое значение вызывающей программы, и подпрограммы или процедуры, которые этого не делают. В этих языках вызовы функций обычно встраиваются в выражения (например, sqrtфункция может называться как y = z + sqrt(x)). Вызов процедур либо ведет себя синтаксически как операторы (например, printпроцедура может вызываться как if x > 0 then print(x)или вызывается явно с помощью оператора, такого как CALLили GOSUB(например, call print(x))). Другие языки, такие какC и Lisp не делают различий между функциями и подпрограммами.

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

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

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

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

Преимущества разбиения программы на подпрограммы:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В языках программирования C и C ++ подпрограммы называются функциями (далее классифицируются как функции-члены, если они связаны с классом , или как свободные функции [19], когда нет). Эти языки используют специальное ключевое слово, voidчтобы указать, что функция не возвращает никакого значения. Обратите внимание, что функции C / C ++ могут иметь побочные эффекты, включая изменение любых переменных, адреса которых передаются как параметры. Примеры:

void  Function1 ()  {  / * какой-то код * /  }

Функция не возвращает значение и должна вызываться как отдельная функция, например, Function1();

int  Function2 ()  {  возврат  5 ; }

Эта функция возвращает результат (число 5), и вызов может быть частью выражения, например, x + Function2()

char  Function3 ( целое  число )  {  char  selection []  =  { 'S' ,  'M' ,  'T' ,  'W' ,  'T' ,  'F' ,  'S' };  вернуть  выделение [ число ]; }

Эта функция преобразует число от 0 до 6 в начальную букву соответствующего дня недели, а именно 0 в «S», 1 в «M», ..., 6 в «S». Результат вызова его может быть присвоен переменной, например, num_day = Function3(number);.

void  Function4 ( int *  pointer_to_var )  {  ( * pointer_to_var ) ++ ; »; }

Эта функция не возвращает значение, а изменяет переменную, адрес которой передается в качестве параметра; это будет называться с Function4(&variable_to_increment);.

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

Example ()  'Вызывает подпрограммуSub  Example  '  Запускает подпрограмму TextWindow . WriteLine ( «Это пример подпрограммы в Microsoft Small Basic.» )  'Что делает подпрограмма EndSub  ' Завершает подпрограмму

В приведенном выше примере Example()вызывает подпрограмму. [20] Чтобы определить фактическую подпрограмму, Subнеобходимо использовать ключевое слово, за которым следует имя подпрограммы Sub. После того, как контент будет следовать, EndSubнеобходимо ввести его.

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

В Visual Basic 6 языка, называются подпрограммы функция или подводные лодки (или методы , когда связанный с классом). Visual Basic 6 использует различные термины, называемые типами, для определения того, что передается в качестве параметра. По умолчанию неуказанная переменная регистрируется как вариантный тип и может передаваться как ByRef (по умолчанию) или ByVal . Кроме того, когда функция или подпрограмма объявляется, ей дается общедоступное, частное или дружественное обозначение, которое определяет, можно ли получить к ней доступ вне модуля или проекта, в котором она была объявлена.

  • По значению [ByVal] - способ передачи значения аргумента в процедуру путем передачи копии значения вместо передачи адреса. В результате фактическое значение переменной не может быть изменено процедурой, в которую оно передается.
  • По ссылке [ByRef] - способ передачи значения аргумента в процедуру путем передачи адреса переменной вместо передачи копии ее значения. Это позволяет процедуре получить доступ к фактической переменной. В результате фактическое значение переменной может быть изменено процедурой, в которую оно передается. Если не указано иное, аргументы передаются по ссылке.
  • Public (необязательно) - указывает, что процедура функции доступна для всех других процедур во всех модулях. При использовании в модуле, содержащем Option Private, процедура недоступна вне проекта.
  • Private (необязательно) - указывает, что процедура функции доступна только другим процедурам в модуле, в котором она объявлена.
  • Друг (необязательно) - используется только в модуле класса. Указывает, что процедура Function видна во всем проекте, но не видна контроллеру экземпляра объекта.
Частная  функция  Function1 ()  'Некоторый код Здесь End  Function

Функция не возвращает значение и должна вызываться как отдельная функция, например, Function1

Частная  функция  Function2 ()  как  целочисленная  функция2  =  5 Конечная  функция

Эта функция возвращает результат (число 5), и вызов может быть частью выражения, например, x + Function2()

Частная  функция  Function3 ( ByVal  intValue  как  Integer )  как  String  Dim  strArray ( 6 )  as  String  strArray  =  Array ( «M» ,  «T» ,  «W» ,  «T» ,  «F» ,  «S» ,  «S» )  Function3  =  strArray ( intValue ) Конечная  функция

Эта функция преобразует число от 0 до 6 в начальную букву соответствующего дня недели, а именно 0 в «M», 1 в «T», ..., 6 в «S». Результат вызова его может быть присвоен переменной, например, num_day = Function3(number).

Частная  функция  Function4 ( ByRef  intValue  как  целое число )  intValue  =  intValue  +  1 Конечная  функция

Эта функция не возвращает значение, а изменяет переменную, адрес которой передается в качестве параметра; он будет называться с " Function4(variable_to_increment)".

Пример PL / I [ править ]

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

 change_sign: процедура (массив); объявить array (*, *) float; массив = -массив; конец change_sign;

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

 / * границы первого массива от -5 до +10 и от 3 до 9 * / объявить array1 (-5: 10, 3: 9) float; / * границы второго массива от 1 до 16 и от 1 до 16 * / объявить array2 (16,16) float; вызов change_sign (array1); вызов change_sign (array2);

Локальные переменные, рекурсия и повторный вход [ править ]

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

Подпрограмма может иметь любое количество и любой характер точек вызова. Если рекурсия поддерживается, подпрограмма может даже вызвать саму себя, в результате чего ее выполнение будет приостановлено, пока происходит другое вложенное выполнение той же подпрограммы. Рекурсия - полезное средство для упрощения некоторых сложных алгоритмов и решения сложных проблем. Рекурсивные языки обычно предоставляют новую копию локальных переменных при каждом вызове. Если программист желает, чтобы значение локальных переменных оставалось неизменным между вызовами, они могут быть объявлены статическими на некоторых языках или могут использоваться глобальные значения или общие области. Вот пример рекурсивной подпрограммы на C / C ++ для поиска чисел Фибоначчи :

int  Fib ( int  n )  {  если  ( n  <=  1 )  {  return  n ;  }  return  Fib ( n  -  1 )  +  Fib ( n  -  2 ); }

Ранние языки, такие как Fortran , изначально не поддерживали рекурсию, потому что переменные были статически распределены, а также местоположение для адреса возврата. Большинство компьютеров до конца 1960-х, таких как PDP-8 , не поддерживали регистры аппаратного стека. [ необходима цитата ]

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

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

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

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

Перегрузка [ править ]

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

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

Вот пример перегрузки подпрограммы в C ++ :

#include  <iostream>двойная  площадь ( двойная  h ,  двойная  w )  {  return  h  *  w ;  }двойная  площадь ( двойная  r )  {  return  r  *  r  *  3.14 ;  }int  main ()  {  двойной  rectangle_area  =  Площадь ( 3 ,  4 );  double  circle_area  =  Площадь ( 5 ); std :: cout  <<  "Площадь прямоугольника равна"  <<  rectangle_area  <<  std :: endl ;  std :: cout  <<  "Площадь круга равна"  <<  circle_area  <<  std :: endl ; }

В этом коде есть две функции с одинаковым именем, но у них разные параметры.

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

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

 ОБЪЯВИТЬ gen_name GENERIC ( имя КОГДА (ФИКСИРОВАННЫЙ ДВОИЧНЫЙ), пламя КОГДА (ПЛАВАЕТ), имя пути ИНАЧЕ  );

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

Закрытия [ править ]

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

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

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

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

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

Коды возврата [ править ]

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

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

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

Оптимизация вызовов подпрограмм [ править ]

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

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

Встраивание [ править ]

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

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

  • Функция (математика)
  • Метод (компьютерное программирование)
  • Встроенная функция
  • Стратегия оценки
  • Модульное программирование
  • Включение
  • Перегрузка оператора
  • Защищенная процедура
  • Функциональное программирование
  • Разделение команд и запросов (CQS)
  • Сопрограммы , подпрограммы, которые вызывают друг друга, как если бы обе были главными программами
  • Обработчик событий , подпрограмма, которая вызывается в ответ на событие ввода или прерывание.
  • Асинхронный вызов процедуры , подпрограмма, которая вызывается после того, как ее параметры установлены другими действиями.

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

  1. ^ Комиссия по содействию выборам США (2007). «Определения слов со специальными значениями» . Рекомендации по системе добровольного голосования . Архивировано из оригинала на 2012-12-08 . Проверено 14 января 2013 .
  2. ^ Subrata Дасгупт (7 января 2014). Это началось с Бэббиджа: генезис компьютерных наук . Издательство Оксфордского университета. С. 155–. ISBN 978-0-19-930943-6.
  3. ^ а б Дж. Мочли, "Подготовка задач для машин типа EDVAC" (1947), в Брайане Рэнделле (ред.), Истоки цифровых компьютеров, Springer, 1982.
  4. ^ Уиллер, DJ (1952). «Использование подпрограмм в программах» (PDF) . Материалы национального собрания ACM 1952 г. (Питтсбург) - ACM '52 . п. 235. DOI : 10,1145 / 609784,609816 .
  5. ^ Уилкс, М. В.; Уиллер, диджей; Гилл, С. (1951). Подготовка программ для ЭЦМ . Эддисон-Уэсли.
  6. ^ Дайнит, Джон (2004). " " открытая подпрограмма. "Компьютерный словарь" . Encyclopedia.com . Проверено 14 января 2013 года .
  7. ^ Тьюринг, Алан М. (1945), Отчет д-ра AM Тьюринга о предложениях по разработке Автоматического вычислительного двигателя (ACE): Представлено Исполнительному комитету NPL в феврале 1946 г.перепечатано в Copeland, BJ , ed. (2005). Автоматическая вычислительная машина Алана Тьюринга . Оксфорд: Издательство Оксфордского университета. п. 383. ISBN. 0-19-856593-3.
  8. ^ Дональд Э. Кнут (1997). Искусство программирования, Том I: Основные алгоритмы . Эддисон-Уэсли. ISBN 0-201-89683-4.
  9. ^ O.-J. Даль; EW Dijkstra; CAR Hoare (1972). Структурированное программирование . Академическая пресса. ISBN 0-12-200550-3.
  10. ^ Тьюринг, Алан Матисон (1946-03-19) [1945], Предложения по развитию в математическом отделе автоматизированного вычислительного двигателя (ACE) (NB. Представлено 1946-03-19 перед Исполнительным комитетом Национальной физической лаборатории (Великобритания).)
  11. ^ Карпентер, Брайан Эдвард ; Доран, Роберт Уильям (1977-01-01) [октябрь 1975]. «Другая машина Тьюринга» . Компьютерный журнал . 20 (3): 269–279. DOI : 10.1093 / comjnl / 20.3.269 . (11 страниц)
  12. ^ a b Исааксон, Уолтер (18 сентября 2014 г.). "Уолтер Айзексон о женщинах ENIAC" . Удача . Архивировано из оригинала 12 декабря 2018 года . Проверено 14 декабря 2018 .
  13. ^ Планирование и кодирование проблем для электронного вычислительного прибора, Pt 2, Vol. 3 https://library.ias.edu/files/pdfs/ecp/planningcodingof0103inst.pdf (см. Стр. 163 pdf для соответствующей страницы)
  14. Гай Льюис Стил-младший. Записка AI 443. «Разоблачение мифа о« дорогом вызове процедуры »; или реализации вызова процедуры, признанные вредными » . Раздел« C. Почему вызовы процедур имеют плохую репутацию ».
  15. ^ Франк, Томас С. (1983). Введение в PDP-11 и его язык ассемблера . Серия программного обеспечения Prentice-Hall. Прентис-Холл. п. 195. ISBN 9780134917047. Проверено 6 июля 2016 . Мы могли бы снабдить нашего специалиста по сборке копиями исходного кода для всех наших полезных подпрограмм, а затем, представляя ему основную программу для сборки, сказать ему, какие подпрограммы будут вызываться в основной линии [...]
  16. ^ "Информационный центр ARM" . Infocenter.arm.com . Проверено 29 сентября 2013 .
  17. ^ "Использование стека x64" . Документы Microsoft . Microsoft . Дата обращения 5 августа 2019 .
  18. ^ «Типы функций» . Msdn.microsoft.com . Проверено 29 сентября 2013 .
  19. ^ "Что подразумевается под бесплатной функцией" .
  20. ^ «Microsoft Small Basic» . www.smallbasic.com .