Правильное использование стека и кучи в C ++? - PullRequest
121 голосов
/ 01 марта 2009

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

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

Ответы [ 10 ]

240 голосов
/ 01 марта 2009

Нет, разница между стеком и кучей не в производительности. Срок его службы: любая локальная переменная внутри функции (все, что вы не используете 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;
}

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

42 голосов
/ 01 марта 2009

Я бы сказал:

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

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

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

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

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

24 голосов
/ 01 марта 2009

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

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 с ссылочными значениями .

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

6 голосов
/ 01 марта 2009

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

5 голосов
/ 01 марта 2009

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

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

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

3 голосов
/ 28 декабря 2011

Стек - более эффективный и простой в управлении объем данных.

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

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

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

2 голосов
/ 25 января 2010

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

Куча проблем

1 голос
/ 01 марта 2009

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

0 голосов
/ 21 июля 2012

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

0 голосов
/ 01 марта 2009

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

1) Scope of variable
2) Performance.

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

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

...