Модель памяти в языке С. Функции в языке С. Функции в ассемблере

Содержание

Слайд 2

Краткое содержание предыдущей серии Как в ассемблере происходит сравнение? Как используется

Краткое содержание предыдущей серии

Как в ассемблере происходит сравнение?
Как используется результат сравнения?
В

чем отличие логических операций от битовых?
Какие вы помните логические операции?
А битовые?
Чем опасны сдвиги в С?
Слайд 3

Краткое содержание этой серии Модель памяти в языке С Функции в

Краткое содержание этой серии

Модель памяти в языке С
Функции в языке С
Функции

в ассемблере
Еще о командах перехода
Слайд 4

Модель оперативной памяти в языке С Статическая область – ее размер

Модель оперативной памяти в языке С

Статическая область – ее размер известен

при компиляции; там хранятся глобальные и статические (static) переменные, там могу хранится константы.
Куча – область, из которой выделяется динамическая память (malloc() в С, new в С++ ). Ее максимальный размер задается как-то (например, программист просто выбирает число).
Стек – его размер меняется при работе программы. Максимальный размер задается как-то.
Плохие ситуации: выход за границы стека, выход за границы кучи, встреча кучи и стека.
Слайд 5

Функции в языке С: объявление Объявление (прототип) – declaration: Что такое

Функции в языке С: объявление

Объявление (прототип) – declaration:
Что такое объявление?
Это

«обещание», что где-то написано тело функции.
Пример: char foo(int a);
Где должно располагаться объявление?
До вызова. Т.е. выше по тексту.
В заголовочном файле (.h), если это глобальная функция.
В том же файле .с, если это функция static.
Объявление может быть совмещено с телом функции.
Ошибки линкера «undefined symbol имяФункции» означают, что есть объявление, но нет тела.
Слайд 6

Функции в языке С: объявление void * foo(int a); void *

Функции в языке С: объявление

void * foo(int a);
void * - тип

возвращаемого значения
foo – имя функции
int a – тип и имя параметра (аргумента)
аргументов может быть много, они разделяются запятой
аргументов может быть переменное количество
; - обязательный элемент синтаксиса, если дальше нет тела функции
Слайд 7

Функции в языке С: определение Что такое определение (тело) – definition?

Функции в языке С: определение

Что такое определение (тело) – definition?
Это сам

код функции, который будет выполняться при вызове.
char foo(int a)
{
...
}
Слайд 8

Функции в языке С: вызов Как происходит вызов функции: char foo(int

Функции в языке С: вызов

Как происходит вызов функции:
char foo(int a); //определение

должно быть до вызова
char result = foo(2);
2 – параметр, передаваемый в функцию
result – переменная, в которую запишется возвращаемое значение
Слайд 9

Функции в языке С: вызов char foo(int a); ... char result

Функции в языке С: вызов

char foo(int a);
...
char result = foo(2);


После этой строки управление «мистическим» образом передается на первую строку тела функции, причем параметр a будет равным 2.
Параметры функции внутри нее – просто локальные переменные.
Слайд 10

Функции и процедуры В настоящее время различие минимально: процедуры не возвращают

Функции и процедуры

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

функции – могут возвращать (но не обязательно).
Слайд 11

Функции в ассемблере Вызов функции в ассемблере: команды BL address BLX

Функции в ассемблере

Вызов функции в ассемблере: команды
BL address
BLX register
Переход с

сохранением адреса возврата в регистре R14 (Link Register, LR).
Адрес возврата – адрес следующей команды после BL.
А по какому адресу нужно перейти, чтобы попасть в функцию?
По адресу первой инструкции в ее теле.
Слайд 12

Функции в ассемблере Что нужно сделать, чтобы вызвать функцию? Как-то передать

Функции в ассемблере

Что нужно сделать, чтобы вызвать функцию?
Как-то передать параметры
Как-то передать

управление
Как-то вернуться к месту вызова
Как-то вернуть значение
При этом:
Внутри функции тоже могут вызвать функцию
Может быть даже ту же самую (рекурсия)
Ничего не должно сломаться!
Слайд 13

Функции в ассемблере: что может сломаться? Весь код в ассемблере использует

Функции в ассемблере: что может сломаться?

Весь код в ассемблере использует регистры.
Код

в функции тоже использует регистры.
int a = 1 + sin(3.14);
MOV r0, 1 ; собираюсь складывать 1 и синус
(вызов sin) ; вызываю sin
.. а если функция sin тоже использовала r0?
Что же делать?
Слайд 14

Функции в ассемблере: что может сломаться? Содержимое регистров может быть испорчено

Функции в ассемблере: что может сломаться?

Содержимое регистров может быть испорчено при

вызове функций. Что делать?
Содержимое регистров нужно куда-то сохранять до вызова и восстанавливать после.
Куда сохранять?
Слайд 15

Функции в ассемблере: состояние регистров Куда сохранять состояние регистров? В специальную

Функции в ассемблере: состояние регистров

Куда сохранять состояние регистров?
В специальную статическую область

памяти (архитектура 8051)
Аппаратно в теневые регистры
В стек
Число регистров неизменно – всегда известно, сколько памяти нужно для сохранения.
Слайд 16

Функции в ассемблере А не нужно ли сохранять что-нибудь еще? Состояние

Функции в ассемблере

А не нужно ли сохранять что-нибудь еще?
Состояние LR для

текущей функции (но это регистр)
Локальные переменные текущей функции?
Кстати, а где хранятся локальные переменные?
Локальные переменные хранятся:
В регистрах
В специальной статической области памяти
В стеке
Поэтому их не надо сохранять, но нужно не задеть случайно.
Слайд 17

Функции в ассемблере А как передавать параметры? На регистрах (их ведь

Функции в ассемблере

А как передавать параметры?
На регистрах (их ведь все равно

сохраняем)
В специальной статической области памяти
В куче
В стеке
А как возвращать значение?
См. выше
Слайд 18

Стек Доступ к стеку: спец. команды push и pop через SP

Стек

Доступ к стеку:
спец. команды push и pop
через SP (регистр – указатель

стека)
или через еще какой-нибудь регистр
Т.е. к стеку можно обращаться через косвенно-регистровую адресацию.
Словно это обычный массив.
Собственно, в ARM стек и есть массив.
Слайд 19

Функции в ассемблере: контекст int foo(int a, int b) { 1:

Функции в ассемблере: контекст

int foo(int a, int b)
{
1: b--;
2: bar(50);
2.1: push r0-lr
2.2: push

50
2.3: BL bar;
3: a++;
4: return a;
}

Упрощенный вид:

Слайд 20

Функции в ассемблере: контекст int bar(int c) { 1: buzz(77); 1.1:

Функции в ассемблере: контекст

int bar(int c)
{
1: buzz(77);
1.1: push r0-lr
1.2: push 77
1.3: BL

buzz;
3: return 0;
}

Упрощенный вид:

Слайд 21

Функции: соглашение о вызове Как передаются параметры? Как возвращается результат? Кто

Функции: соглашение о вызове

Как передаются параметры?
Как возвращается результат?
Кто сохраняет контекст?
Кто восстанавливает

контекст?
Все это называется «соглашение о вызове».
Соглашение о вызове может быть разным в зависимости от процессора, ОС, языка, желания левой пятки (fastcall, stdcall...)
Слайд 22

Соглашение о вызове В ARM – ARM Procedure Call Standard (call

Соглашение о вызове

В ARM – ARM Procedure Call Standard
(call convention) (очень

упрощенно):
До четырех параметров передаются на регистрах (r0-r3); остальные через стек
Контекст сохраняет тот, кого вызвали
Восстанавливает контекст тот, кого вызвали
Возвращаемое значение передается через r0 (r0 и r1 для long long и double)
Т.е. слайды 18-19 были только для примера!
Слайд 23

Функции (в ARM): краткий итог Параметры и локальные переменные хранятся в

Функции (в ARM): краткий итог

Параметры и локальные переменные хранятся в регистрах

или в стеке
Доступ к переменным в стеке осуществляется через косвенно-регистровую адресацию (например, через SP)
Перед вызовом функции нужно сохранить контекст, после вызова – восстановить
Возвращаемое значение передается через регистр r0 (и r1)
Слайд 24

Суперскалярная архитектура (конвейеризация) Идея: Каждая инструкция ассемблера для процессора – многостадийный

Суперскалярная архитектура (конвейеризация)

Идея:
Каждая инструкция ассемблера для процессора – многостадийный процесс.
Разные

стадии часто не связаны.
Если их выполнять параллельно, можно получить прирост производительности!
Слайд 25

Простой трехстадийный конвейер ARM Стадии выполнения одной команды:

Простой трехстадийный конвейер ARM

Стадии выполнения одной команды:

Слайд 26

Конвеер Плюсы: Процессор загружен равномерно Инструкции выполняются параллельно Минусы: Инструкции могут

Конвеер

Плюсы:
Процессор загружен равномерно
Инструкции выполняются параллельно
Минусы:
Инструкции могут быть зависимы (конфликты)
Команды перехода срывают

конвеер и вызывают простой
Долгие команды вызывают простой
Слайд 27

Как борются с минусами конвейера? Зависимые инструкции: Команда NOP (no operation)

Как борются с минусами конвейера?

Зависимые инструкции:
Команда NOP (no operation)
Долгие инструкции:
Внеочередное исполнение
И

команда NOP
Срывы из-за переходов:
Размотка циклов
Условное выполнение вместо условных переходов
Inlining функций вместо перехода
Предсказание переходов
И еще много всего
Слайд 28

Функции Плюсы: Повторное использование кода Краткость кода Функции – это интерфейс

Функции

Плюсы:
Повторное использование кода
Краткость кода
Функции – это интерфейс к чужому коду
Минусы:
Срыв конвейера
Кэш-промах
Накладные

расходы на передачу параметров, переключение контекста и возврат
Слайд 29

Минусы функций: что же делать? Чего НЕ НАДО делать: оптимизировать раньше

Минусы функций: что же делать?

Чего НЕ НАДО делать:
оптимизировать раньше времени
использовать глобальные

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

Чистые функции Чистая функция (pure) зависит только от своих параметров и

Чистые функции

Чистая функция (pure) зависит только от своих параметров и не

меняет глобальное состояние.
Ее результат постоянен.
Например: синус, косинус и т.д.
Чистые функции это хорошо!