Правильное использование стека и кучи в C++?
я программировал некоторое время, но это было в основном Java и C#. Мне никогда не приходилось управлять памятью самостоятельно. Недавно я начал программировать на C++, и я немного смущен тем, когда я должен хранить вещи в стеке и когда хранить их в куче.
Я понимаю, что переменные, к которым обращаются очень часто, должны храниться в стеке и объектах, редко используемые переменные и большие структуры данных должны храниться в куче. Заключаться в следующем правильно или я ошибаюсь?
10 ответов:
нет, разница между стеком и кучей, не производительность. Это продолжительность жизни: любая локальная переменная внутри функции (все, что вы не malloc () или new) живет в стеке. Он уходит, когда вы возвращаетесь из функции. Если вы хотите, чтобы что-то жило дольше, чем объявившая его функция, вы должны выделить его в куче.
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; }для более ясного понимания того, что такое стек, подойдите к нему с другого конца-вместо того, чтобы пытаться понять, что делает стек термины языка высокого уровня, посмотрите "стек вызовов" и "соглашение о вызове" и посмотрите, что машина действительно делает при вызове функции. Память компьютера-это всего лишь ряд адресов;" куча "и" стек " - изобретения компилятора.
Я бы сказал так:
храните его в стеке, если можете.
храните его в куче, если вам нужно.
поэтому предпочитайте стек куче. Некоторые возможные причины, по которым вы не можете хранить что-то в стеке:
- это слишком большой-на многопоточных программах на 32-битной ОС стек имеет небольшой и фиксированный (по крайней мере, во время создания потока) размер (обычно всего несколько мегабайт. Это значит, что вы можете создать много потоков без исчерпание адресного пространства. Для 64-разрядных программ или однопоточных (Linux в любом случае) программ это не является серьезной проблемой. Под 32-битным Linux, однопоточные программы обычно используют динамические стеки, которые могут продолжать расти, пока они не достигнут вершины кучи.
- вам нужно получить доступ к нему вне рамок исходного кадра стека-это действительно основная причина.
с помощью разумных компиляторов можно выделять объекты не фиксированного размера в куче (обычно массивы, размер которых не известен во время компиляции).
это более тонко, чем предполагают другие ответы. Нет абсолютного разделения между данными в стеке и данными в куче на основе того, как вы его объявляете. Например:
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функция (и любой сложный тип значения должен иметь один) может служить своего рода привязываемой ссылкой на некоторые данные кучи, в рамках системы, которая гарантирует одного владельца этих данных.поэтому современный подход C++ заключается в том, чтобы никогда хранить адрес данных кучи в голых локальных переменных указателя. Все распределения кучи должны быть скрыты внутри классов.
если вы это сделаете, вы можете думать обо всех переменных в своей программе, как если бы они были простыми типами значений, и вообще забыть о куче (за исключением написания нового класса-оболочки для некоторых данных кучи, которые должны быть необычными).
вы просто должны сохранить один специальный бит знаний, чтобы помочь вам оптимизировать: где возможно, вместо того, чтобы присваивать одну переменную другой, как это:
a = b;поменять их вот так:
a.swap(b);потому что это намного быстрее и не бросать исключения. Единственное требование заключается в том, что вам не нужно
bчтобы продолжать удерживать то же значение (он собирается получитьaвместо этого значение, которое было бы разбито вa = b).недостатком является то, что этот подход заставляет вас возвращать значения из функций через выходные параметры вместо фактического возвращаемого значения. Но они исправляют это в C++0x с rvalue-ссылки.
в самых сложных ситуациях вы бы взяли эту идею в общую крайность и использовали интеллектуальный класс указателя, такой как
shared_ptrкоторый уже находится в tr1. (Хотя я бы сказал, что если вам это нужно, вы, возможно, вышли за пределы применимости стандартного C++.)
вы также будете хранить элемент в куче, если он должен использоваться вне области действия функции, в которой он создан. Одна идиома, используемая с объектами стека, называется RAII - это включает в себя использование объекта на основе стека в качестве оболочки для ресурса, когда объект уничтожается, ресурс будет очищен. Объекты на основе стека легче отслеживать, когда вы можете создавать исключения - вам не нужно беспокоиться об удалении объекта на основе кучи в исключении обработчик. Вот почему необработанные указатели обычно не используются в современном C++, вы бы использовали интеллектуальный указатель, который может быть оболочкой на основе стека для необработанного указателя на объект на основе кучи.
чтобы добавить к другим ответам, это также может быть о производительности, по крайней мере немного. Не то, чтобы вы должны беспокоиться об этом, если это не имеет отношения к вам, но:
выделение в куче требует поиска отслеживания блока памяти, который не является операцией с постоянным временем (и занимает несколько циклов и накладных расходов). Это может замедлиться, поскольку память становится фрагментированной, и / или вы приближаетесь к использованию 100% вашего адресного пространства. С другой стороны, выделение стека постоянные, в основном "свободные" операции.
еще одна вещь, которую следует учитывать (опять же, действительно важно, если это становится проблемой), заключается в том, что обычно размер стека фиксирован и может быть намного меньше размера кучи. Поэтому, если вы выделяете большие объекты или много небольших объектов, вы, вероятно, захотите использовать кучу; если у вас закончится пространство стека, среда выполнения выдаст исключение заголовка сайта. Обычно это не имеет большого значения, но еще одна вещь, которую следует учитывать.
стек является более эффективным и простым в управлении данными с областью действия.
но куча должна использоваться для чего-либо большего, чем несколько КБ (это легко в C++, просто создать
boost::scoped_ptrна стеке держать указатель на выделенную память).рассмотрим рекурсивный алгоритм, который звонит в себя. Это очень трудно ограничить и / или угадать общее использование стека! В то время как в куче, распределитель (
malloc()илиnew) могут указывать из памяти, возвращаяNULLилиthrowing.источник: ядро Linux, стек которого не превышает 8 КБ!
для полноты картины вы можете прочитать статью Миро Самека о проблемах использования кучи в контексте встроенные программы.
выбор того, следует ли выделять в куче или в стеке, делается для вас, в зависимости от того, как выделяется ваша переменная. Если вы выделяете что-то динамически, используя "новый" вызов, вы выделяете из кучи. Если вы выделяете что-то как глобальную переменную или как параметр в функции, оно выделяется в стеке.
на мой взгляд есть два решающих факторов
1) Scope of variable 2) Performance.Я бы предпочел использовать стек в большинстве случаев, но если вам нужен доступ к переменной вне области, вы можете использовать кучу.
для повышения производительности при использовании кучи вы также можете использовать функциональность для создания блока кучи, и это может помочь в получении производительности, а не выделении каждой переменной в другом месте памяти.
вероятно, на это был дан довольно хороший ответ. Я хотел бы указать вам на нижеприведенную серию статей, чтобы иметь более глубокое понимание деталей низкого уровня. У Алекса Дарби есть серия статей, где он проводит вас с отладчиком. Вот Часть 3 о стеке. http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curriculum-part-3-the-stack/
Comments