Stack, Static и Heap в С++ - программирование

Stack, Static и Heap в С++

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

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

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

Как только кто-то сказал мне, что с этим выражением:

int * asafe=new int;

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

asafe=new int;

?

4b9b3361

Ответ 1

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

Сводная информация о том, какая статическая память, куча и стек:

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

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

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

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

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

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

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

Сбор мусора

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

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

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

Ответ 2

Конечно, все не совсем точное. Возьмите его с солью, когда прочтете это:)

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


Время автоматического хранения

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

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

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


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

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

// 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 не уничтожается при выходе из своего блока. Вы можете сказать, что объекты, имеющие локальную область, начинают жизнь , когда управление достигает своего определения. Для localA это происходит, когда вводится тело функции. Для объектов в области пространства имен срок службы начинается с запуска программы. То же самое верно для статических объектов класса scope:

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();
}

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

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

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

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

Ответ 3

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

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

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

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

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


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

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

Ответ 4

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

Stack

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

кучного

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

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

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

Ответ 5

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

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

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

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

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

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

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

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

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

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

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

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

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

Ответ 9

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