Стек, статический и куча в C++

Я искал, но я не очень хорошо понял эти три понятия. Когда мне нужно использовать динамическое распределение (в куче) и в чем его реальное преимущество? Каковы проблемы static и stack? Могу ли я написать целое приложение без выделения переменных в куче?

Я слышал, что другие языки включают "сборщик мусора", поэтому вам не придется беспокоиться о памяти. Что делает сборщик мусора?

что вы могли бы сделать манипулирование памятью самостоятельно, что вы не могли сделать с помощью этого сборщика мусора?

однажды кто-то сказал мне, что с этой декларацией:

int * asafe=new int;

у меня есть "указатель на указатель". Что это значит? Это отличается от:

asafe=new int;

?

9 ответов


аналогичный вопрос спросили, но он не спросил о статике.

сводка того, что статическая, куча и память стека:

  • статическая переменная в основном является глобальной переменной, даже если вы не можете получить к ней глобальный доступ. Обычно для него есть адрес, который находится в самом исполняемом файле. Существует только одна копия для всей программы. Независимо от того, сколько раз вы входите в вызов функции (или класса) (и сколько потоков!) этот переменная относится к одному и тому же местоположению памяти.

  • куча-это куча памяти, которая может быть динамически. Если вы хотите 4kb для объекта, то динамический распределитель будет просматривать список свободного места в куче, выбирать кусок 4kb и давать его вам. Как правило, динамический распределитель памяти (malloc, new и т. д.) начинается в конце памяти и работает в обратном направлении.

  • объясняя, как стек растет и сжимается немного снаружи область этого ответа, но достаточно сказать, что вы всегда добавляете и удаляете только с конца. Стеки обычно начинаются с высоких и растут до более низких адресов. У вас заканчивается память, когда стек встречается с динамическим распределителем где-то посередине (но относятся к физической или виртуальной памяти и фрагментации). Для нескольких потоков потребуется несколько стеков (процесс обычно резервирует минимальный размер для стека).

когда вы хотели бы использовать каждый один:

  • статика / глобалы полезны для памяти, что вы знаете, что вы всегда будете нуждаться, и вы знаете, что вы никогда не хотите освободить. (Кстати, встроенные среды можно рассматривать как имеющие только статическую память... стек и куча являются частью известного адресного пространства, разделяемого третьим типом памяти: кодом программы. Программы часто выполняют динамическое распределение из своей статической памяти, когда им нужны такие вещи, как связанные списки. Но несмотря на это, статическая память сам по себе (буфер) не является "выделенным", а скорее другие объекты выделяются из памяти, удерживаемой буфером для этой цели. Вы можете сделать это и в не встроенных, и консольные игры часто избегают встроенных механизмов динамической памяти в пользу жесткого контроля процесса выделения, используя буферы заданных размеров для всех выделений.)

  • переменные стека полезны, когда вы знаете, что пока функция находится в области видимости (на stack somewhere), вы захотите, чтобы переменные оставались. Стеки хороши для переменных, которые вам нужны для кода, где они расположены, но которые не нужны вне этого кода. Они также очень хороши, когда вы получаете доступ к ресурсу, например файлу, и хотите, чтобы ресурс автоматически исчез, когда вы оставите этот код.

  • выделения кучи (динамически выделяемая память) полезны, когда вы хотите быть более гибкими, чем выше. Часто, функция вызывается в ответ на событие (пользователь нажимает кнопку "Создать ящик"). Правильный ответ может потребовать выделения нового объекта (нового объекта Box), который должен оставаться долго после выхода функции, поэтому он не может быть в стеке. Но вы не знаете, сколько ящиков вы хотите в начале программы,поэтому это не может быть статическим.

Вывоз Мусора

в последнее время я много слышал о том, насколько велики сборщики мусора, так что, может быть, немного голос несогласия был бы полезен.

сбор мусора-замечательный механизм, когда производительность не является огромной проблемой. Я слышал, что GCs становится лучше и сложнее, но факт в том, что вы можете быть вынуждены принять штраф за производительность (в зависимости от случая использования). И если вы ленивы, это все еще может не работать должным образом. В лучшие времена сборщики мусора понимают, что ваша память уходит, когда она понимает, что больше нет ссылок на нее (см. ссылка подсчет). Но если у вас есть объект, который ссылается на себя (возможно, ссылаясь на другой объект, который ссылается назад), то подсчет ссылок сам по себе не будет указывать на то, что память может быть удалена. В этом случае GC должен посмотреть на весь эталонный суп и выяснить, есть ли какие-либо острова, которые упоминаются только сами по себе. Навскидку, я бы предположил, что это операция O(n^2), но что бы это ни было, это может стать плохим, если вы вообще обеспокоены производительностью. (Редактировать: Мартин Б указывает что это O (n) для разумно эффективных алгоритмов. Это все еще O (n) слишком много, если вы обеспокоены производительностью и можете освободить место в постоянное время без сбора мусора.)

лично, когда я слышу, что люди говорят, что C++ не имеет сборки мусора, мой ум помечает это как особенность C++, но я, вероятно, в меньшинстве. Вероятно, самое сложное для людей узнать о программировании на C и C++ - это указатели и как правильно обрабатывать их динамические выделения памяти. Некоторые другие языки, такие как Python, были бы ужасны без GC, поэтому я думаю, что это сводится к тому, что вы хотите от языка. Если вам нужна надежная производительность, то C++ без сборки мусора-единственное, что я могу придумать по эту сторону Fortran. Если вы хотите простоту использования и учебных колес (чтобы спасти вас от сбоев, не требуя, чтобы вы изучили "правильное" управление памятью), выберите что-то с GC. Даже если вы знаете, как управлять память хорошо, это сэкономит вам время, которое вы можете потратить на оптимизацию другого кода. Там действительно не так много производительности, но если вам действительно нужна надежная работа (и способности точно знать, что происходит, когда, под одеялом), то я бы палку с C++. Существует причина, по которой каждый основной игровой движок, о котором я когда-либо слышал, находится на C++ (если не C или assembly). Python, et al хороши для сценариев, но не для основного игрового движка.


следующее, Конечно, все не совсем точно. Возьмите его с солью, когда вы его читаете:)

ну, три вещи, о которых вы говорите, это автоматическая, статическая и динамическая продолжительность хранения, что имеет какое-то отношение к тому, как долго живут объекты и когда они начинают жизнь.


автоматическая длительность хранения

вы используете автоматическую продолжительность хранения для недолгим и маленький данные, то есть нужно только локально в какой-то блок:

if(some condition) {
    int a[3]; // array a has automatic storage duration
    fill_it(a);
    print_it(a);
}

время жизни заканчивается, как только мы выходим из блока, и начинается, как только объект определен. Они самый простой вид продолжительности хранения, и путь более быстрый чем в частности динамическая продолжительность хранения.


статическая продолжительность хранения

вы используете статическую продолжительность хранения для свободных переменных, которые могут быть доступны любому коду все время, если их область позволяет такие использование (область пространства имен), и для локальных переменных, которые должны продлить свое время жизни через выход из своей области (локальная область), и для переменных-членов, которые должны совместно использоваться всеми объектами их класса (область классов). Их жизнь зависит от того, в какой области они находятся. Они могут иметь пространство имен и локальная область и объем класс. Что верно о них обоих, как только их жизнь начинается, жизнь заканчивается в конец программа. Вот два примера:--10-->

// static storage duration. in global namespace scope
string globalA; 
int main() {
    foo();
    foo();
}

void foo() {
    // static storage duration. in local scope
    static string localA;
    localA += "ab"
    cout << localA;
}

программа выводит ababab, потому что localA не разрушается при выходе из блока. Вы можете сказать, что объекты с локальной областью начинают lifetime когда контроль достигает своего определения. Для localA, это происходит, когда тело функции вводится. Для объектов в области пространства имен время жизни начинается с программа запуска. То же самое верно для статических объектов класса область применения:

class A {
    static string classScopeA;
};

string A::classScopeA;

A a, b; &a.classScopeA == &b.classScopeA == &A::classScopeA;

Как видите, classScopeA привязан не к конкретным объектам своего класса, а к самому классу. Адрес всех трех названий выше одинаков, и все они обозначают один и тот же объект. Существует специальное правило о том, когда и как инициализируются статические объекты, но давайте не будем об этом сейчас. Что означает термин статическая инициализация заказ фиаско.


динамическая память продолжительность

последнем хранения продолжительность динамична. Вы используете его, если хотите, чтобы объекты жили на другом острове, и вы хотите поместить указатели вокруг них. Вы также используете их, если ваши объекты большой, и если вы хотите создать массивы размера, известные только в время работы. Из-за этой гибкости объекты с динамической длительностью хранения являются сложными и медленными в управлении. Объекты, имеющие эту динамическую продолжительность, начинают срок службы, когда соответствующий новая оператор вызов происходит:

int main() {
    // the object that s points to has dynamic storage 
    // duration
    string *s = new string;
    // pass a pointer pointing to the object around. 
    // the object itself isn't touched
    foo(s);
    delete s;
}

void foo(string *s) {
    cout << s->size();
}

его срок службы заканчивается только при вызове удалить для них. Если вы забудете об этом, эти объекты никогда не закончат жизнь. И объекты класса, определяющие объявленный пользователем конструктор, не будут вызывать деструкторы. Объекты с динамической длительностью хранения требуют ручной обработки времени их жизни и связанного ресурса памяти. Библиотеки существуют для облегчения их использования. явная сборка мусора на конкретное объекты можно установить с помощью смарт-указателя:

int main() {
    shared_ptr<string> s(new string);
    foo(s);
}

void foo(shared_ptr<string> s) {
    cout << s->size();
}

вам не нужно беспокоиться о вызове delete: общий ptr делает это за вас, если последний указатель, который ссылается на объект, выходит за рамки. Сам общий ptr имеет автоматическую продолжительность хранения. Так что его lifetime автоматически управляется, позволяя ему проверить, следует ли удалить указанный динамический объект в его деструкторе. Для ссылки shared_ptr см. документы boost: http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm


Это было сказано тщательно, так же, как"короткий ответ":

  • статическая переменная (класс)
    lifetime = время выполнения программы (1)
    видимость = определяется модификаторами доступа (private/protected/public)

  • статическая переменная (глобальная область)
    lifetime = время выполнения программы (1)
    visibility = единица компиляции, экземпляр которой создается в (2)

  • переменной кучи
    lifetime = определенный вами (новый для удаления)
    visibility = определенный вами (независимо от того, что вы назначаете указателю)

  • переменной стека
    visibility = от объявления до выхода из области
    lifetime = от объявления до выхода области объявления


(1) более точно: от инициализации до деинициализации блок компиляции (т. е. файл C / C++). Порядок инициализации единиц компиляции стандартом не определен.

(2) Будьте осторожны: если вы создаете статическую переменную в заголовке, каждая единица компиляции получает свою собственную копию.


Я уверен, что один из педантов скоро придумает лучший ответ, но главное отличие-скорость и размер.

стек

значительно быстрее выделить. Это делается в O (1), так как он выделяется при настройке кадра стека, поэтому он по существу свободен. Недостатком является то, что если у вас закончится пространство стека, вы будете костью. Вы можете настроить размер стека,но IIRC у вас есть ~2 МБ, чтобы играть. Кроме того, как только вы выходите из функции, все в стеке очищенный. Поэтому ссылаться на него позже может быть проблематично. (Указатели на стек выделенных объектов приводит к ошибкам.)

кучу

значительно медленнее выделить. Но у вас есть GB, чтобы играть и указывать.

Сборщик Мусора

сборщик мусора-это некоторый код, который работает в фоновом режиме и освобождает память. Когда вы выделяете память в куче, очень легко забыть освободить ее, что известно как утечка памяти. Со временем память твоя приложение потребляет растет и растет, пока не произойдет сбой. Наличие сборщика мусора периодически освобождает память, которая вам больше не нужна, помогает устранить этот класс ошибок. Конечно, за это приходится платить, поскольку сборщик мусора замедляет работу.


каковы проблемы статики и стека?

проблема со "статическим" распределением заключается в том, что распределение выполняется во время компиляции: вы не можете использовать его для выделения некоторого переменного количества данных, количество которых неизвестно до времени выполнения.

проблема с выделением в "стеке" заключается в том, что выделение уничтожается, как только возвращается подпрограмма, которая выполняет выделение.

Я мог бы написать целую приложение без выделения переменных в куче?

возможно, но не нетривиальное, нормальное, большое приложение (но так называемые "встроенные" программы могут быть написаны без кучи, используя подмножество C++).

что делает сборщик мусора ?

он продолжает следить за вашими данными ("Марк и развертка"), чтобы обнаружить, когда ваше приложение больше не ссылается на него. Это удобно для приложения, потому что приложению не нужно освободите данные ... но сборщик мусора может быть вычислительно дорогим.

сборщики мусора не являются обычной особенностью программирования на C++.

что вы могли бы сделать, манипулируя памятью самостоятельно, что вы не могли бы сделать с помощью этого сборщика мусора?

изучите механизмы C++ для детерминированного освобождения памяти:

  • 'static': никогда не освобождается
  • 'stack': как только переменная "выходит за рамки"
  • 'heap': когда указатель удаляется (явно удаляется приложением или неявно удаляется в рамках той или иной подпрограммы)

распределение памяти стека (переменные функции, локальные переменные) может быть проблематичным, когда ваш стек слишком "глубок", и вы переполняете память, доступную для распределения стека. Куча предназначена для объектов, к которым необходимо обращаться из нескольких потоков или в течение жизненного цикла программы. Вы можете написать целую программу без использования кучи.

вы можете легко пропускать память без сборщика мусора, но вы также можете диктовать, когда освобождаются объекты и память. Я бежал в к проблемам с Java, когда он запускает GC, и у меня есть процесс в реальном времени, потому что GC-эксклюзивный поток (больше ничего не может работать). Поэтому, если производительность критична, и вы можете гарантировать, что нет просочившихся объектов, не использование GC очень полезно. В противном случае это просто заставляет вас ненавидеть жизнь, когда ваше приложение потребляет память, и вам нужно отслеживать источник утечки.


Что делать, если ваша программа не знает заранее, сколько памяти выделить (следовательно, вы не можете использовать переменные стека). Скажем, связанные списки, списки могут расти, не зная заранее, каков их размер. Поэтому выделение в куче имеет смысл для связанного списка, когда вы не знаете, сколько элементов будет вставлено в него.


преимуществом GC в некоторых ситуациях является раздражение в других; опора на GC поощряет не думать об этом. Теоретически, ждет до периода "простоя" или до тех пор, пока это абсолютно необходимо, когда он украдет пропускную способность и вызовет задержку ответа в вашем приложении.

но вы не должны не думать об этом.- Как и все остальное в многопоточных приложениях, когда ты можешь уступить, ты можешь уступить. Так, например, в .Net можно запросить GC; делая это, вместо менее частого более длительного выполнения GC вы можете иметь более частый более короткий запуск GC и распространять задержку, связанную с этими накладными расходами.

но это побеждает основную привлекательность GC, которая, по-видимому, " поощряется не думать об этом, потому что это авто-мат-ic."

Если вы впервые подверглись программированию до того, как GC стал распространенным и были удобны с malloc / free и new/delete, то может быть даже так, что вы найдете GC a немного раздражает и / или недоверчивы(как можно не доверять "оптимизации", которая имеет пеструю историю.) Многие приложения допускают случайную задержку. Но для приложений, которые этого не делают, где случайная задержка менее приемлема, общая реакция-избегать сред GC и двигаться в направлении чисто неуправляемого кода (или, не дай бог, долгого умирающего искусства, ассемблерного языка.)

У меня был летний студент здесь некоторое время назад, интерн, умный ребенок, который был отлучен от GC; он был таким adament о превосходстве GC, что даже при программировании на неуправляемом C/C++ он отказался следовать модели malloc/free new/delete, потому что, цитата: "вам не нужно делать это на современном языке программирования.- А ты знаешь? Для крошечных, коротких запущенных приложений вы действительно можете уйти с этим, но не для длительных приложений.


стек-это память, выделенная компилятором, когда мы когда-либо компилируем программу, по умолчанию компилятор выделяет некоторую память из ОС ( мы можем изменить настройки из настроек компилятора в вашей среде IDE), и ОС-это та, которая дает вам память, ее зависит от многих доступных памяти в системе и многих других вещей, и приходя к памяти стека выделяют, когда мы объявляем переменную, которую они копируют (ref как формалы), эти переменные по умолчанию его CDECL в Visual studios экс: инфиксной нотации : c=a+b; толкание стека выполняется справа налево, B в стек, оператор, a в стек и результат этих i,e c в стек. В предварительном исправить нотации: =+кабина Здесь все переменные помещаются в стек 1st (справа налево), а затем выполняется операция. Эта память, выделенная компилятором, исправлена. Итак, предположим, что 1 МБ памяти выделяется нашему приложению, скажем, переменные, используемые 700kb памяти(все локальные переменные выталкиваются в стек если они не распределены динамически), поэтому оставшаяся память 324kb выделяется куче. И у этого стека меньше времени жизни, когда область действия функции заканчивается, эти стеки очищаются.