Кожного разу, коли ви виділяєте локальні змінні в області дії C (наприклад, функції), вони не мають коду ініціалізації за замовчуванням (наприклад, конструктори C ++). І оскільки вони не розподіляються динамічно (це просто неініціалізовані вказівники), ніяких додаткових (і потенційно дорогих) функцій не потрібно викликати (наприклад,malloc для їх підготовки / розподілу не ).
Завдяки тому, як працює стек , виділення змінної стека просто означає зменшення покажчика стека (тобто збільшення розміру стека, оскільки на більшості архітектур він зростає вниз), щоб звільнити місце для нього. З точки зору центрального процесора, це означає виконання простої інструкції SUB:SUB rsp, 4 (у випадку, якщо ваша змінна має 4 байти - наприклад, звичайне 32-бітове ціле число).
Більше того, коли ви оголошуєте кілька змінних, ваш компілятор досить розумний, щоб насправді згрупувати їх в одну велику SUB rsp, XXінструкцію, де XXє загальний розмір локальних змінних області. Теоретично. На практиці трапляється щось трохи інше.
У таких ситуаціях я вважаю, що GCC Explorer є безцінним інструментом, коли справа доходить до виявлення (з надзвичайною легкістю) того, що відбувається "під капотом" компілятора.
Тож давайте подивимось, що відбувається, коли ви насправді пишете таку функцію: посилання GCC Explorer .
Код С
int function(int a, int b) {
int x, y, z, t;
if(a == 2) { return 15; }
x = 1;
y = 2;
z = 3;
t = 4;
return x + y + z + t + a + b;
}
У результаті складання
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
cmp DWORD PTR [rbp-20], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
-- snip --
.L3:
pop rbp
ret
Як виявляється, GCC ще розумніший за це. Він навіть не виконує інструкцію SUB для розподілу локальних змінних. Він просто (внутрішньо) припускає, що простір "зайнятий", але не додає жодних інструкцій щодо оновлення вказівника стека (наприклад SUB rsp, XX). Це означає, що покажчик стека не оновлюється, але, оскільки в цьому випадку після використання простору стека більше PUSHне виконуються інструкції (і відсутні rspвідносні пошуки), проблем немає.
Ось приклад, коли не оголошуються додаткові змінні: http://goo.gl/3TV4hE
Код С
int function(int a, int b) {
if(a == 2) { return 15; }
return a + b;
}
У результаті складання
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
.L3:
pop rbp
ret
Якщо ви подивитесь на код перед передчасним поверненням ( jmp .L3який переходить до коду очищення та повернення), ніяких додаткових інструкцій для "підготовки" змінних стека не буде використано. Єдина відмінність полягає в тому, що параметри функції a і b, які зберігаються в регістрах ediand esi, завантажуються в стек за вищою адресою, ніж у першому прикладі ( [rbp-4]і[rbp - 8] ). Це пов’язано з тим, що додатковий простір не виділено для локальних змінних, як у першому прикладі. Отже, як бачите, єдиною "накладними витратами" для додавання цих локальних змінних є зміна терміна віднімання (тобто навіть не додавання додаткової операції віднімання).
Отже, у вашому випадку фактично відсутні витрати на просто оголошення змінних стека.