Эта статья — перевод этой статьи. Некоторые детали в ней устарели, некоторые являются специфичными для окружения, но в целом полно излагают поведение при вызове функции. Данный материал позволит глубже понять механизм вызова, специфичные ошибки, особенности вызова функций с переменным числом параметров и возможные методы оптимизации. Кроме того, она будет полезна, если вы захотите вызвать функцию или написать собственную на ассемблере.
Ниже описано поведение стека при вызове функции в си. Детали соответствуют действительности для компилятора gcc на платформе intel pentium. Существует множество способов и соглашений создать и заполнить фрейм: разные процессоры, операционные системы и компиляторы могут делать это по-разному.
Вызов функции
Р ассмотрим функцию со следующим прототипом
int foo (int arg1, int arg2, int arg3);
Функция имеет 2 локальных переменных. Далее будем считать, что sizeof(int) равен 4 байтам. Пусть функция main вызывает функцию foo. Регистр ESP является указателем стека (хранит адрес вершины стека), регистр EBP — указателем базы. Регистр EBP используется для хранения адреса предыдущего фрейма.
#40. Объявление и вызов функций | Язык C для начинающих
Аргументы, которые main передаёт foo, а также локальные переменные могут быть получены путём сдвига относительно указателя базы.
Если вызываемая функция работает с регистрами EAX, ECX и EDX, то вызывающая функция должна их сохранить каким-то образом на стеке перед вызовом подпрограммы. С другой стороны, вызываемая функция должна восстановить значения этих регистров. Если вызываемая функция изменяет состояние регистров EAX, ECX и EDX, то она должна сначала сохранить предыдущее состояние на стеке, а перед выходом восстановить их прежние значения.
Параметры, передаваемые функции foo кладутся на стек справа налево: сначала самый последний аргумент, в конце самый первый. Локальные переменные, также как и временные, хранятся на стеке.
Возвращаемое значение, если оно меньше 4 байт, сохраняется в регистре EAX. Если возвращаемое значение больше четырёх байт, то вызывающая функция передаёт дополнительный первый параметр вызываемой функции. Этот параметр — адрес, по которому должно быть сохранено возвращаемое значение. На языке си вызов
x = foo(a, b, c);
будет трансформирован в
foo(
Заметьте, это произойдёт только если возвращаемое значение больше 4 байт.
Рассмотрим пошагово процесс вызова и понаблюдаем за изменением стека.
Действия вызывающей функции перед вызовом
В нашем примере вызывающая функция main, а вызываемая foo. Перед вызовом функции main использует регистры ESP и EBP для работы с собственным фреймом.
1. Функция main кладёт на стек содержимое регистров EAX, ECX и EDX. Это необязательное действие, которое происходит только если состояние регистров должно быть сохранено.
2. Вызов функций по расписанию
2. Функция main кладёт на стек аргументы, начиная с последнего. Например, если вызов имеет вид
a = foo(12, 15, 18);
на ассемблере это может выглядеть как:
push dword 18 push dword 15 push dword 12
3. main запрашивает вызов подпрограммы call
call foo

Во время исполнения инструкции call, на стек кладётся содержимое регистра EIP (Istruction pointer). Смысл вот в чём: так как EIP указывает на следующую инструкцию после в main, то на вершине стека окажется адрес возврата. После вызова call следующий цикл выполнения начнётся с метки foo.
На рисунке 2 показано содержимое фрейма после того, как отработала функция call. Красная линия на этом и следующих рисунках отделяет содержимое стека, которое было создано вызовом функции от остального содержимого. После вызова функции вершина стека вновь окажется на этой позиции.
Действия вызываемой функции после вызова
К огда функция foo получает управление, она должна сделать 3 вещи: установить собственный фрейм, выделить место под локальное хранилище, по мере надобности сохранить состояние регистров EBX, ESI и EDI.
Итак, foo сначала устанавливает собственный фрейм. EBP ещё указывает на положение фрейма функции main. Это значение должно быть сохранено, поэтому оно кладётся на стек. Содержимое ESP перемещается в EBP. Это позволит обращаться к аргументам функции как к сдвигу относительно EBP и освободит указатель стека ESP для дальнейших действий. Поэтому, почти все функции си начинаются с инструкций
push ebp mov ebp, esp
На рисунке 3 изображён стек после этих действий. Обратите внимание, что первый аргумент имеет адрес EBP + 8, так как адрес возврата и EBP функии main каждый занимают по 4 байта.
На следующем шаге функция foo должна выделить на стеке место под локальные переменные. Также она должна выделить место под временное хранилище. Дело в том, что функция может иметь сложные выражения, которые будут сохранять промежуточные значения для дальнейшего использования в других сложных выражениях. Пусть, к примеру, кроме двух локальных переменных типа int нашей функции требуется ещё 12 байт для временного хранилища. Необходимые 20 байт могут быть выделены просто вычитанием из EBP двадцати (напомню, вершина стека растёт с убыванием адреса)
sub esp, 20
К локальным переменным и временному хранилищу теперь можно обращаться с помощью сдвига относительно EBP. В заключение, foo должна сохранить содержимое регистров EBX, ESI и EDI на стеке , если они используются.
Теперь может быть выполнено тело функции. При этом новые данные могут помещаться и сниматься со стека. Поэтому указатель стека ESP может изменяться, но EBP остаётся фиксированным, и к первому аргументу можно обратиться [EBP + 8] независимо от того, сколько операций push и pop было вызвано. Выполнение функции foo в свою очередь также может привести к вызову других функций или к рекурсивному вызову. Тем не менее, поскольку EBP восстанавливается после возвращения из этих вложенных вызовов, ссылки на аргументы, локальные переменные и временное хранилище всё также может быть произведено с помощью сдвига относительно EBP.
Действия вызываемой функции перед возвратом
П еред тем как вернуть управление вызывающей функции, foo должна определиться с тем, как возвращать значение, о чём мы уже упоминали. Результат будет помещён в регистр EAX, либо станет дополнительным параметром функции, а сама функция не будет возвращать значения.
Во-вторых, foo должна восстановить значение регистров EBX, ESI и EDI. В начале вызова мы поместили значения регистров на стек (так как наша функция их изменяла), а теперь можем снять эти значения оттуда, но только в том случае, если ESP хранит верное значение, то есть количество операция push и pop должно быть сбалансировано.
После этих двух шагов локальные переменные и временное хранилище больше не нужны и фрейм может быть снят с помощью инструкций
mov esp, ebp pop ebp

После этого адрес возврата может быть снят со стека и помещён в регистр EIP. Так как эти действия часто встречаются в программах на си, то процессоры i386 имеют специальные инструкции, которые делают то же самое, что и приведённые выше
leave ret
Действия вызывающей функции после возвращения
П осле того, как управление перешло к вызывающеё функции (в нашем примере это main), аргументы, переданные функции обычно уже не нужны. Мы можем снять со стека все три значения сразу, просто прибавив 12 (3 раза по 4 байта) к указателю стека.
add esp, 12
Функция main после этого должна сохранить возвращаемое значение, которое хранится в регистре EAX в предназначенном для этого месте. Например, если возвращаемое значение присваивается переменной, то содержимое регистра EAX может быть размещено по адресу этой переменной.
В конце концов, если содержимое регистров EAX, ECX и EDX было сохранено на стеке, то оно может быть восстановлено. Таким образом, стек опять находится в том состоянии, в котором был до вызова функции.

Всё ещё не понятно? – пиши вопросы на ящик
Источник: learnc.info
Вызов функции
Вызов функции может быть оформлен в виде отдельного оператора, а также содержаться в любом месте программы, где по смыслу предполагается некоторое значение (за исключением оговоренных случаев). Формат и правила исполнения вызова функции в равной мере распространяются на стандартные (встроенные) и пользовательские функции.
Формат вызова функции
Вызов функции состоит из названия функции и списка передаваемых параметров, обрамлённого круглыми скобками:
Название_функции (Список_параметров ) // Собственно вызов функции
Название, указанное в вызове функции, должно совпадать с названием функции, вызываемой для исполнения. Список параметров указывается через запятую. Количество параметров, передаваемых в функцию, ограничено и не может превышать 64.
В качестве параметров в вызове функции могут использоваться константы, переменные и вызовы других функций. Количество, тип и порядок упоминания передаваемых параметров в вызове функции должны совпадать с количеством, типом и порядком упоминания формальных параметров, указанных в описании функции (исключение составляет вызов функции, имеющей умолчательные параметры).
My_function (Alf, Bet) // Пример вызова функции
// Здесь:
My_function // Название вызываемой функции
Alf // Первый передаваемый параметр
Bet // Второй передаваемый параметр
Если вызываемая функция не предусматривает передаваемых параметров, то список параметров указывается пустым, наличие круглых скобок при этом обязательно.
My_function () // Пример вызова функции
// Здесь:
My_function // Название вызываемой функции
// Передаваемые параметры отсутствуют
При вызове функции, имеющей умолчательные параметры, список передаваемых параметров может быть ограничен (указан не полностью). Список параметров можно ограничить, начиная с первого умолчательного параметра. В данном примере локальные переменные b, c и d имеют умолчательные значения.
// Для функции с описанием:
int My_function (int a, bool b=true, int c=1, double d=0.5)
Операторы >
// .. допустимы следующие вызовы:
My_function (Alf, Bet, Ham, Del) // Допустимый вызов функции
My_function (Alf ) // Допустимый вызов функции
My_function (3) // Допустимый вызов функции
My_function (Alf, 0) // Допустимый вызов функции
My_function (3, Tet) // Допустимый вызов функции
My_function (17, Bet, 3) // Допустимый вызов функции
My_function (17, Bet, 3, 0.5) // Допустимый вызов функции
Параметры, не имеющие значения по умолчанию, опускать нельзя. Если какой-либо из умолчательных параметров опущен, то все последующие умолчательные параметры также не указываются.
// Для функции с описанием:
int My_function (int a, bool b=true, int c=1, double d=0.5)
Операторы
>
// ..следующие вызовы являются ошибочными:
My_function () // Недопустимый вызов функции: неумолчательные..
// ..параметры опускать нельзя (первый)
My_function (17, Bet, , 0.5) // Недопустимый вызов функции: пропущен..
// ..умолчательный параметр (третий)
Вызываемые функции делятся на две группы: возвращающие некоторое значение заранее определённого типа и не возвращающие никакого значения.
Формат вызова функции, не возвращающей значение
Вызов функции, не возвращающей значение, может быть оформлен только в виде отдельного оператора. Оператор вызова функции заканчивается знаком ; (точка с запятой):
Название_функции (Список_параметров ); // Оператор вызова функции,не возвращающей знач
Func_no_ret (Alfa, Betta, Gamma); // Пример оператора вызова функции.
//.. не возвращающей значение
Иного формата (способа) для вызова функций, не возвращающих значение, не предусмотрено.
Формат вызова функции, возвращающей значение
Вызов функции, возвращающей значение, может быть оформлен в виде отдельного оператора, а также использоваться в коде программы — в том месте, где по смыслу предполагается значение известного типа.
В случае оформления в виде отдельного оператора вызов функции заканчивается знаком ; (точка с запятой):
Название_функции (Список_параметров ); // Оператор вызова функции, возвращающей знач
Func_yes_ret (Alfa, Betta, Delta); // Пример оператора вызова функции.
//.. возвращающей значение
Правила исполнения вызова функции
| Вызов функции вызывает для исполнения одноимённую функцию. Если вызов функции оформлен в виде отдельного оператора, то после исполнения функции управление передаётся оператору, следующему за вызовом функции. Если вызов функции используется в выражении, то после исполнения функции управление передаётся в то место выражения, где указан вызов функции, и дальнейшие вычисления в выражении производятся со значением, возвращённым функцией. |
Использование вызова функции в составе других операторов определяется форматом этих операторов.
| Задача 20. Составить программу, в которой реализуются следующие условия: — если текущее время больше 15:00, то выполнить 10 итераций в цикле for;- в остальных случаях выполнить 6 итераций. |
Ниже приведен пример скрипта callfunction.mq4, в котором используются: вызов функции в заголовке оператора for (в составе Выражения_1, согласно формату оператора for, см. Оператор цикла for), вызов стандартной функции в виде отдельного оператора, в правой части оператора присваивания (см. Оператор присваивания) и в заголовке оператора if-else (в Условии, согласно формату оператора if-else, см. Условный оператор if-else).
///———————————————————————
// callfunction.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//———————————————————————
int start() // Описание функции start()
// Начало тела ф-ии start()
int n; // Объявление переменной
int T=15; // Заданное время
for(int i=Func_yes_ret(T);i10;i++) // Использование функции в..
//.заголовке оператора цикла
// Начало тела цикла for
n=n+1; // Счётчик итераций
Alert («Итерация n=»,n,» i=»,i); // Оператор вызова функции
> // Конец тела цикла for
return; // Выход из функции start()
> // Конец тела ф-ии start()
//———————————————————————
int Func_yes_ret (int Times_in) // Описание пользоват. ф-ии
// Начало тела польз. ф-ии
datetime T_cur=TimeCurrent(); // Использование функции в..
// ..операторе присваивания
if(TimeHour(T_cur) > Times_in) // Использование функции в..
//..заголовке операт.if-else
return(1); // Возврат значения 1
return(5); // Возврат значения 5
> // Конец тела пользов. ф-ии
//———————————————————————
В приведенном примере использованы вызовы следующих функций с такими передаваемыми параметрами:
- в вызове функции Func_yes_ret(T) — переменная T;
- в вызове функции Alert () — строковые константы «Итерация n=» и » i=», переменные n и i;
- вызов функции TimeCurrent() не предусматривает передаваемых параметров;
- в вызове функции TimeHour(T_cur) — переменная T_cur.
В программе реализован очень простой алгоритм. В переменной Т задаётся время в часах, относительно которого производятся вычисления. В заголовке оператора for указан вызов пользовательской функции Func_yes_ret(), которая может возвращать одно из двух значений: 1 или 5. В зависимости от этого значения изменяется количество итераций в цикле: либо их будет 10 (i изменяется от 1 до 10), либо 6 (i изменяется от 5 до 10). Для наглядности в теле цикла используется счётчик итераций, каждое значение которого выводится на экран с помощью функции Alert().
В описании пользовательской функции сначала вычисляется время в секундах, прошедших после 00:00 1 января 1970 года (обращение к функции TimeCurrent()), а затем текущее время в часах (обращение к функции TimeHour()). Разветвление алгоритма осуществляется с помощью оператора if (вызов функции TimeHour() указан в его условии). Если текущее время оказывается больше, чем переданное в пользовательскую функцию (локальная переменная Times_in), то пользовательская функция возвращает 1, иначе — возвращает 5.
| В программе нет описаний стандартных функций и вызова специальной функции start(). |
Ниже показана функциональная схема скрипта callfunction.mq4:

Рис. 51. Функциональная схема программы, использующей обращение к функциям.
На функциональной схеме кружками обозначены вызовы функций (пользовательской и стандартной). Красными стрелками показана передача управления в функцию и обратно. Наглядно видно, что функция возвращает управление в то место, где указан вызов функции, причём на пути между вызовом функции и самой функцией не совершается каких-либо иных вычислений. В общем случае, если функция возвращает значение, это значение передаётся в вызывающий модуль (по красной стрелке в направлении вызова функции).
Специальные функции могут быть вызваны из любого места программы по общим правилам, наравне с другими функциями. Специальные функции также могут иметь параметры. Однако при вызове этих функций клиентским терминалом никакие параметры извне переданы не будут, а будут использованы умолчательные значения.
Использование параметров в специальных функциях будет иметь смысл только при вызове их из программы. Несмотря на то, что в языке MQL4 имеется техническая возможность вызова специальных функций из программы, делать это не рекомендуется. Программу, использующую обращение к специальным функциям, следует считать некорректной.
Источник: book.mql4.com
Определение и вызов функций в Go

Функция — это часть кода, которая после определения может многократно использоваться. Функции используются для упрощения понимания кода путем разделения его на небольшие понятные задачи, которые могут использоваться многократно в вашей программе.
Go поставляется с мощной стандартной библиотекой, где имеются самые разные предопределенные функции. С некоторыми из них вы уже знакомы, например с функциями пакета fmt:
- fmt.Println() , которая будет выводить объекты в стандартный вывод (скорее всего, это будет ваш терминал).
- fmt.Printf() , которая позволяет форматировать отображаемый результат.
Имена функций включают скобки и могут содержать параметры.
В этом обучающем руководстве мы узнаем, как определить ваши собственные функции для вашего проекта.
Определение функции
Давайте начнем с превращения классического примера “Hello, World!” из программы в функцию.