2009-03-01 3 views
120

Я программировал некоторое время, но это были в основном Java и C#. Мне никогда не приходилось управлять памятью самостоятельно. Недавно я начал программирование на C++, и я немного смущен, когда должен хранить вещи в стеке и когда их хранить в куче.Правильное использование стека и кучи в C++?

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

+0

Возможный дубликат [Когда лучше использовать стек вместо кучи и наоборот?] (Http://stackoverflow.com/questions/102009/when-is-it-best-to-use -the-stack-вместо-heap-and-vice-versa) –

ответ

238

Нет, разница между стеком и кучей не является производительностью. Это срок службы: любая локальная переменная внутри функции (все, что вы не malloc() или новое) живет в стеке. Он исчезает, когда вы возвращаетесь из функции. Если вы хотите, чтобы что-то жило дольше, чем функция, объявившая его, вы должны выделить его в кучу.

class Thingy; 

Thingy* foo() 
{ 
    int a; // this int lives on the stack 
    Thingy B; // this thingy lives on the stack and will be deleted when we return from foo 
    Thingy *pointerToB = &B; // this points to an address on the stack 
    Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap. 
            // pointerToC contains its address. 

    // this is safe: C lives on the heap and outlives foo(). 
    // Whoever you pass this to must remember to delete it! 
    return pointerToC; 

    // this is NOT SAFE: B lives on the stack and will be deleted when foo() returns. 
    // whoever uses this returned pointer will probably cause a crash! 
    return pointerToB; 
} 

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

+6

Было бы безопасно добавить, что информация с переменным размером обычно идет в кучу. Единственными исключениями, о которых я знаю, являются VLA в C99 (который имеет ограниченную поддержку) и функцию alloca(), которую часто неправильно понимают даже программисты C. –

+10

Хорошее объяснение, хотя в многопоточном сценарии с частыми выделениями и/или освобождением памяти куча * - это точка раздора, что влияет на производительность. Тем не менее, Scope почти всегда является решающим фактором. – peterchen

+17

Конечно, и новый/malloc() сам по себе является медленной операцией, а стек, скорее всего, находится в dcache, чем в произвольной строке кучи. Это реальные соображения, но обычно они вторгаются в вопрос о продолжительности жизни. – Crashworks

1

Выбор того, следует ли выделять кучу или в стеке, - это тот, который сделан для вас, в зависимости от того, как распределена ваша переменная. Если вы выделяете что-то динамически, используя «новый» вызов, вы выделяете из кучи. Если вы выделяете что-то как глобальную переменную или как параметр в функции, она выделяется в стеке.

+3

Я подозреваю, что он спрашивал, когда положить вещи в кучу, а не как. –

6

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

5

Чтобы добавить к другим ответам, это также может быть связано с производительностью, по крайней мере, немного. Не стоит беспокоиться об этом, если это не относится к вам, но:

Выделение в куче требует поиска следящего блока памяти, который не является операцией постоянного времени (и занимает несколько циклов и накладных расходов). Это может замедляться по мере фрагментации памяти и/или вы приближаетесь к использованию 100% вашего адресного пространства. С другой стороны, распределения стека являются постоянными, в основном «свободными» операциями.

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

+0

Как куча, так и стек представляют собой виртуальную память подкачки. Время поиска кучи невероятно быстро по сравнению с тем, что требуется для отображения в новой памяти. В 32-битном Linux я могу положить> 2gig в свой стек. Под Mac, я думаю, что стек с жестким ограничением до 65 мегабайт. –

39

Я бы сказал:

Хранить его в стек, если вы можете.

Храните его в куче, если вам НУЖНО.

Поэтому предпочитайте стек кучи.Некоторые возможные причины, по которым вы не можете сохранить что-то в стеке:

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

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

+0

Что-нибудь большее, чем пара KB, обычно лучше всего накладывать на кучу. Я не знаю специфики, но я не помню, чтобы когда-либо работала со стеком, который был «несколько мегабайт». –

+2

Это то, с чем я бы не обращался к пользователю с самого начала. Для пользователя векторы и списки, по-видимому, выделяются в стеке, даже если STL сохраняет содержимое в куче. Вопрос, казалось, был больше на пути решения, когда нужно явно называть new/delete. –

+1

Dan: Я поставил 2 гига (Да, G как в GIGS) на стек под 32-битным Linux. Пределы стека зависят от ОС. –

0

На мой взгляд, есть два решающих факторов

1) Scope of variable 
2) Performance. 

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

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

24

Это более тонкий, чем другие рекомендации. Абсолютного разрыва между данными в стеке и данными в куче нет, основываясь на том, как вы его объявляете. Например:

std::vector<int> v(10); 

В теле функции, которая объявляет vector (динамический массив) из десяти целых чисел в стеке. Но хранилище, управляемое vector, не находится в стеке.

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

Не так. Пусть функция была:

void GetSomeNumbers(std::vector<int> &result) 
{ 
    std::vector<int> v(10); 

    // fill v with numbers 

    result.swap(v); 
} 

Так что ничего с swap функции (и любого сложного типа значения должны иметь один) может служить своего рода rebindable ссылкой на некоторые данные кучи, в рамках системы, которая гарантирует единый владелец это данные.

Поэтому современный подход на С ++ относится к никогда. сохраняет адрес данных кучи в голых переменных локального указателя. Все распределения кучи должны быть скрыты внутри классов.

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

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

a = b; 

поменять их местами, как это:

a.swap(b); 

, потому что это намного быстрее, и это не бросает исключений. Единственное требование - вам не нужно b, чтобы продолжить удерживать ту же величину (вместо этого получится значение a, которое будет разбито в a = b).

Недостатком является то, что этот подход заставляет вас возвращать значения из функций через выходные параметры вместо фактического возвращаемого значения. Но они фиксируют это в C++ 0x с rvalue references.

В самых сложных ситуациях вы должны использовать эту идею в целом и использовать класс интеллектуальных указателей, такой как shared_ptr, который уже находится в tr1. (Хотя я бы утверждал, что, если вам это кажется, вы, возможно, перешли на сладость сорта Standard C++ применимости.)

2

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

A Heap of Problems

3

Стек является более эффективным, и легче управляемым контекстными данными.

Но куча должна быть использована для чего-нибудь большего, чем несколько KB (это легко в C++, просто создать boost::scoped_ptr в стеке, чтобы держать указатель на выделенную память).

Рассмотрим рекурсивный алгоритм, который вызывает вызов в себя. Очень сложно ограничить и угадать общее использование стека! В то время как на куче распределитель (malloc() или new) может указывать на недостаток памяти, возвращая NULL или throw ing.

Источник: Ядро Linux, стек которого не превышает 8 КБ!

+0

Для справки других читателей: (A) «должно» здесь чисто личное мнение пользователя, составленное в лучшем случае 1 цитата и 1 сценарий, с которыми многие пользователи вряд ли столкнутся (рекурсия). Кроме того, (B) стандартная библиотека предоставляет 'std :: unique_ptr', которая должна быть предпочтительнее любой внешней библиотеки, такой как Boost (хотя она и кормится со стандартом с течением времени). –

0

Возможно, на это хорошо ответил. Я хотел бы указать вам на следующую серию статей, чтобы получить более глубокое понимание деталей низкого уровня. У Alex Darby есть серия статей, где он проводит вас с помощью отладчика. Вот часть 3 о стеке. http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curriculum-part-3-the-stack/

+0

Ссылка, похоже, мертва, но проверка машины выхода в интернет-хранилище указывает, что она говорит только о стеке и поэтому ничего не дает для ответа на конкретный вопрос здесь о стеке ** _ versus_ heap **. -1 –