Что касается Стандарта, то здесь нет понятия стека и кучи.Тем не менее, все известные мне реализации C ++ отображают понятия «продолжительность автоматического хранения» и «динамическое хранение» в стек (*) и кучу соответственно.
(*) Как отмечено @MooingDuck
, этоверно только для переменных функций.Глобальные и статические переменные (вероятно) имеют автоматическую продолжительность хранения, но они не находятся в стеке.
Теперь это очищено:
- переменные в теле функции сохраняютсяв стеке
- объекты, созданные
new
, хранятся в куче (и их адрес возвращается)
С примерами, чтобы быть немного более наглядным:
void f0() {
Class* c = new Class();
}
void f1() {
Class* c = 0;
c = new Class();
}
Здесь c
(типа Class*
) хранится в стеке и указывает на объект (типа Class
), который хранится в куче
void f2() {
Class c = Class();
}
void f3() {
Class c;
}
Здесь c
хранится в стеке.В f2
может быть временным (объект без имени), созданным выражением Class()
и затем скопированным в c
(в зависимости от того, исключает ли компилятор копию или нет), хранилищеВременные значения не рассматриваются Стандартом ... хотя они обычно используют стек.
Последнее слово: заканчивается ли это тем, что фактически используется какое-то место в стеке или нет, это другой вопрос.
- Компилятор может полностью исключить необходимость в объекте
- Переменные могут храниться либо в стеке, либо в регистрах (специфичные для процессора "слоты")
Inдействие:
// Simple test.cpp
#include <cstdio>
struct Class { void foo(int& a) { a += 1; } };
int main() {
Class c;
int a = 0;
c.foo(a);
printf("%d", a);
}
Компилятор (использующий Clang / LLVM ... слегка переработанный) генерирует:
@.str = private unnamed_addr constant [3 x i8] c"%d\00", align 1
define i32 @main() nounwind uwtable {
%1 = tail call i32 (i8*, ...)* @printf(@.str, i32 1)
ret i32 0
}
Обратите внимание, как: 1. Класс был удален, 2. Вызовдо foo
был удален, 3. a
даже не появляется.Переведенный обратно в C ++ мы получим:
#include <cstdio>
int main() {
printf("%d", 1);
}
И если мы сгенерируем сборку (64-битный X86):
main: # @main
pushq %rax # save content of 'rax' on the stack
movl $.L.str, %edi # move address of "%d" into the 'edi' register
movl $1, %esi # move 1 into the 'esi' register
xorb %al, %al # --
callq printf # call printf, it'll look up its parameters in registers
xorl %eax, %eax # --
popq %rdx # restore content from stack to 'rdx'
ret # return
Обратите внимание, как константы ($1
и $.L.str
)помещаются в регистры (%esi
и %esi
соответственно) и никогда не "попадают" в стек.Единственные манипуляции со стеком pushq
и popq
(и я понятия не имею, что они на самом деле сохраняют / восстанавливают.