Правильное использование стека и кучи в C++?



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



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

7618   10  

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 или throw ing.

источник: ядро 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

    Ничего не найдено.