Виклик функції в сі

Нижче описано поведінку стека при виконанні функції в сі. Деталі відповідають дійсності для компілятора gcc на платформі intel pentium. Існує безліч способів і угод створити і заповнити фрейм: різні процесори, операційні системи і компілятори можуть робити це по-різному.

Виклик функції

Якщо викликана функція працює з регістрами EAX, ECX і EDX, то викликає функція повинна їх зберегти якимось чином на стеці перед викликом підпрограми. З іншого боку, що викликається функція повинна відновити значення цих регістрів. Якщо викликається, змінює стан регістрів EAX, ECX і EDX, то вона повинна спочатку зберегти попередній стан на стеку, а перед виходом відновити їх колишні значення.

Параметри, що передаються функції foo кладуться на стек справа наліво: спочатку самий останній аргумент, в кінці найперший. Локальні змінні, також як і тимчасові, зберігаються на стеку.

Розглянемо покроково процес виклику і поспостерігаємо за зміною стека.

Дії викликає функції перед викликом

У нашому прикладі викликає функція main, а викликається foo. Перед викликом функції main використовує регістри ESP і EBP для роботи з власним фреймом.
1. Функція main кладе на стек вміст регістрів EAX, ECX і EDX. Це необов'язкове дію, яке відбувається тільки якщо стан регістрів повинно бути збережено.
2. Функція main кладе на стек аргументи, починаючи з останнього. Наприклад, якщо виклик має вигляд
a = foo (12, 15, 18);
на асемблері це може виглядати як:

3. main запитує виклик підпрограми call

Виклик функції в сі
Мал. 2 Стан фрейма після виклику call

На малюнку 2 показано вміст фрейма після того, як відпрацювала функція call. Червона лінія на цьому і наступних малюнках відокремлює вміст стека, яке було створено викликом функції від решти вмісту. Після виклику функції вершина стека знову опиниться на цій позиції.

Дії викликається функції після виклику

До оли функція foo отримує управління, вона повинна зробити 3 речі: встановити власний фрейм, виділити місце під локальне сховище, у міру потреби зберегти стан регістрів EBX, ESI і EDI.

Отже, foo спочатку встановлює власний фрейм. EBP ще вказує на положення фрейму функції main. Це значення має бути збережено, тому воно кладеться на стек. Вміст ESP переміщається в EBP. Це дозволить звертатися до аргументів функції як до зрушення щодо EBP і звільнить покажчик стека ESP для подальших дій. Тому, майже всі функції сі починаються з інструкцій

Виклик функції в сі
Мал. 3 Фрейм після виклику call: викликається, зберегла значення покажчика бази функції main

До локальних змінних і тимчасового сховища тепер можна звертатися за допомогою зсуву щодо EBP. На закінчення, foo повинна зберегти вміст регістрів EBX, ESI і EDI на стеку. якщо вони використовуються.

Виклик функції в сі
Мал. 4 Тепер функція foo може виконати своє тіло

Тепер може бути виконано тіло функції. При цьому нові дані можуть міститися і зніматися з стека. Тому покажчик стека ESP може змінюватися, але EBP залишається фіксованим, і до першого аргументу можна звернутися [EBP + 8] незалежно від того, скільки операцій push і pop було викликано. Виконання функції foo в свою чергу також може привести до виклику інших функцій або до рекурсивному викликом. Проте, оскільки EBP відновлюється після повернення з цих вкладених викликів, посилання на аргументи, локальні змінні і тимчасове сховище все також може бути зроблено за допомогою зсуву щодо EBP.

Дії викликається функції перед поверненням

П еред тим як повернути управління викликає функції, foo повинна визначитися з тим, як повертати значення, про що ми вже згадували. Результат буде поміщений в регістр EAX, або стане додатковим параметром функції, а сама функція не повертатиме значення.

По-друге, foo повинна відновити значення регістрів EBX, ESI і EDI. На початку виклику ми помістили значення регістрів на стек (так як наша функція їх змінювала), а тепер можемо зняти ці значення звідти, але тільки в тому випадку, якщо ESP зберігає вірне значення, тобто кількість операція push і pop має бути збалансовано.

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

Виклик функції в сі
Мал. 5 Управління повернуто функції main

Дії викликає функції після повернення

П віслюку того, як управління перейшло до визивающеё функції (у нашому прикладі це main), аргументи, передані функції зазвичай вже не потрібні. Ми можемо зняти зі стека все три значення відразу, просто додавши 12 (3 рази по 4 байта) до покажчика стека.

Зрештою, якщо вміст регістрів EAX, ECX і EDX було збережено на стеку, то воно може бути відновлено. Таким чином, стек знову знаходиться в тому стані, в якому був до виклику функції.

ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 [email protected] Stepan Sypachev students

Все ще не зрозуміло? - пиши питання на ящик