Лучше использовать переменные кучи или стека?

Опытный пользователь C++ сказал мне, что я должен стремиться использовать переменные кучи, т.е.:

A* obj = new A("A");

в отличие от:

A obj("A");

Помимо всего того, что использование указателей удобно и гибко, он сказал, что лучше помещать вещи в кучу, а не в стек (что-то о том, что стек меньше, чем куча?). Это правда? Если да, то почему?

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


person Some Newbie    schedule 02.06.2012    source источник
comment
Первый код создает переменную в куче, а не в стеке. (Указатель находится в стеке, а самого объекта нет.)   -  person huon    schedule 02.06.2012
comment
Переменная new находится в куче; другой находится в стеке. Вы начинаете с того, что говорите, что ваш друг рекомендовал переменные стека; тогда вы говорите, что он сказал, что лучше складывать вещи в кучу. Это сбивает нас с толку. Не могли бы вы уточнить, какой совет, по вашему мнению, вам действительно давали?   -  person Jonathan Leffler    schedule 02.06.2012
comment
newd объект находится в куче (технически имеет длительность динамического хранения). Это не переменная, поскольку с этим объектом не связано имя; единственная переменная, объявленная в A* obj = new A("A");, это obj, которая находится в стеке (технически имеет длительность автоматического хранения), но является указателем.   -  person CB Bailey    schedule 02.06.2012
comment
Извините, я хотел сказать, что он предпочитает переменные кучи. Была опечатка.   -  person Some Newbie    schedule 02.06.2012


Ответы (8)


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

Таким образом, небольшие переменные, небольшие объекты, размер которых можно определить во время компиляции, и указатели должны храниться в стеке. Проблема хранения объектов в куче или в свободном хранилище заключается в том, что управление памятью становится затруднительным. Есть вероятность утечки памяти, что плохо. Также, если приложение попытается получить доступ к объекту, который уже удален, может произойти нарушение прав доступа, что может привести к сбою приложения.

В C++11 представлены интеллектуальные указатели (общие, уникальные), упрощающие управление памятью с кучей. Фактический ссылочный объект находится в куче, но инкапсулируется интеллектуальным указателем, который всегда находится в стеке. Следовательно, когда стек откатывается во время события возврата функции или во время исключения, деструктор интеллектуального указателя удаляет фактический объект в куче. В случае общего указателя счетчик ссылок сохраняется, а фактический объект удаляется, когда счетчик ссылок равен нулю. http://en.wikipedia.org/wiki/Smart_pointer

person Abhijit-K    schedule 02.06.2012
comment
Хорошо, это похоже на то, что говорил мой друг. Посмотрим, что скажут другие. Кстати, C++11 работает с Visual Studio? Меня беспокоит использование С++ 11 в том, что код, написанный с его помощью, может иметь проблемы с совместимостью (раньше не использовал С++ 11). - person Some Newbie; 02.06.2012
comment
Да, в VS2010, который уже выпущен, есть интеллектуальные указатели, которые вам нужно включить в заголовок ‹memory›. Другие функции находятся в бета-версии VS2011. Ссылка здесь. Новые функции можно разделить на языковые и библиотечные. blogs.msdn.com/b/vcblog/archive/ 2011/09/12/10209291.aspx - person Abhijit-K; 02.06.2012
comment
Спецификация C++11 была выпущена в прошлом году, и компиляторы начали реализацию в зависимости от приоритета. C++11 теперь C++. Что именно вы имеете в виду под проблемами совместимости? Вы не можете скомпилировать код С++ 11 в компиляторе предыдущей версии, в чем проблема? - person Abhijit-K; 02.06.2012
comment
В основном это касается совместного использования кода. Действительно ли все будут использовать C++11? В python, похоже, есть сторонники как 2.6.5. и 3.0+, причем последний не имеет обратной совместимости. - person Some Newbie; 02.06.2012
comment
Думаю, это будет проблемой еще на один год. Но мы всегда можем указать, что код компилируется такой-то версией компилятора. Это также произошло с последним выпуском C++, C++98. Другой вариант — использовать интеллектуальные указатели библиотеки boost. Интеллектуальные указатели библиотеки С++ 11 являются производными от интеллектуальных указателей boost lib. Лучшим вариантом является обновление до последней версии компилятора. Как долго проект должен компилироваться со старой версией? Мы часто переходим или вынуждены переходить на последние версии. - person Abhijit-K; 02.06.2012
comment
Спасибо, попробую умные указатели. Я предполагаю, что они будут чем-то вроде gcnew и сделают C++ по существу похожим на Java (мой родной язык, который я считаю гораздо более элегантным языком). - person Some Newbie; 02.06.2012

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

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

  • Локальные переменные функции или блока кода {} (не выделенные new) находятся в стеке. Они автоматически уничтожаются, когда вы возвращаетесь из функции. (вызываются их деструкторы и освобождается их память).
  • Но если вам нужен какой-то объект, который будет использоваться вне функции, вам придется выделить его в куче (используя new) или вернуть копию.

Пример:

 void myFun()
 {
   A onStack; // On the stack
   A* onHeap = new A(); // On the heap
   // Do things...

 } // End of the function onStack is destroyed, but the &onHeap is still alive

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

Однако если бы вы вернули указатель на onStack, поскольку onStack был уничтожен при выходе из функции, использование указателя могло бы привести к неопределенному поведению. При использовании onHeap по-прежнему действует.

Чтобы лучше понять, как работают переменные стека, вам следует поискать информацию о стеке вызовов, например эта статья в Википедии. В нем объясняется, как складываются переменные для использования в функции.

person Mesop    schedule 02.06.2012

Не существует общих правил относительно использования переменных, выделенных в стеке, и переменных, выделенных в куче. Есть только рекомендации, в зависимости от того, что вы пытаетесь сделать.

Вот некоторые плюсы и минусы:

Распределение кучи:

Плюсы:

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

Минусы:

  • медленнее — динамическое выделение обычно медленнее, чем стековое.
  • может вызвать фрагментацию памяти - выделение и освобождение объектов разных размеров сделает память похожей на швейцарский сыр :), что приведет к сбою некоторых выделений, если нет доступного блока памяти требуемого размера
  • сложнее поддерживать - как вы знаете, за каждым динамическим выделением должно следовать освобождение, которое должен выполнять пользователь - это подвержено ошибкам, так как во многих случаях люди забывают сопоставить каждый вызов malloc() с free( ) вызов или new() с помощью delete()

Распределение стека:

Плюсы:

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

Минусы:

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

Я думаю, что это отражает некоторые плюсы и минусы. Я уверен, что есть и другие.

В конце концов, это зависит от того, что нужно вашему приложению.

person Alexandru C.    schedule 02.06.2012

Всегда лучше избегать использования new как можно чаще в C++.
Однако бывают случаи, когда вы не можете этого избежать.
Например:
Желание, чтобы переменные существовали за пределами их масштабы.

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

person Alok Save    schedule 02.06.2012
comment
Может быть, потому, что она поддерживается религией, а не фактами. Вы не можете использовать слова, как всегда, если это не всегда! - person Emilio Garavaglia; 02.06.2012
comment
@EmilioGaravaglia: Здесь всегда значит всегда и должно быть так. В ответе четко указано лошади для занятий и есть ли выбор. Я не вижу места для какого-либо не фактизма. - person Alok Save; 02.06.2012
comment
Спасибо, Алс, не могли бы вы прокомментировать сообщение Абхиджита Кадама выше? - person Some Newbie; 02.06.2012
comment
@SomeNewbie: я думаю, что ответ достаточно справедлив. Он подробно объясняет, что я имел в виду под лошадями для курсов в своем ответе. Честно говоря, мне было лень добавлять такой длинный ответ. - person Alok Save; 02.06.2012
comment
Хотя это не совсем относится к моему вопросу. Насколько я знаю, я решительно предпочитаю переменные стека по тем же причинам, что и другие, но я полагаю, что сообщение Абхиджи Кадама подняло вопрос о том, где провести черту. Точнее, каким будет приблизительный наименьший размер переменной s.t. вы бы предпочли поместить его в кучу вместо стека? - person Some Newbie; 02.06.2012
comment
@SomeNewbie: здесь нет рисования линии и точно, нет фиксированных рекомендаций, вы не можете программировать по набору определенных правил. Его микрооптимизация для выбора стека или кучи для повышения производительности. Выберите тот, который сделает вашу программу интуитивно понятной и более простой. писать и понимать и главное правильно работает. - person Alok Save; 02.06.2012
comment
Согласитесь, конкретных указаний нет. Однако это зависит от того, что вы делаете с памятью, например: ссылка stackoverflow.com/questions/6219878/stack-overflow-c - person Abhijit-K; 02.06.2012
comment
Хорошо... Я выберу комбинацию, связанную с эффективностью развития. Я полагаю, что в любом случае эти вещи становятся важными только для высокопроизводительных приложений. - person Some Newbie; 02.06.2012
comment
@AbhijitKadam: я не понимаю цели вашего комментария. Он предназначался мне? - person Alok Save; 02.06.2012
comment
@SomeNewbie; да. Микрооптимизация вредна. Просто напишите корректно работающий код, не заморачиваясь микрооптимизацией. Хорошо оптимизированный, но не работающий код бесполезен, а корректно работающий код всегда можно оптимизировать, когда возникнет необходимость. - person Alok Save; 02.06.2012
comment
@Als Я имею в виду, когда кто-то получает исключение stackoverflow или думает, что может получить его на какой-то машине, тогда он может решить использовать там новое. - person Abhijit-K; 02.06.2012
comment
@AbhijitKadam: Разве программа, которая работает правильно, не покрывает это? Это уже было указано в комментарии, на который вы (вероятно) ответили, поэтому я не знаю, почему? - person Alok Save; 02.06.2012
comment
Да, вы правы, я привел пример. Если я нечаянно обидел вас, то прошу прощения. - person Abhijit-K; 02.06.2012
comment
@AbhijitKadam: Не обиделся, просто я был сбит с толку, если ваш комментарий был для меня или для ОП, вам, вероятно, следовало использовать _@имя пользователя ОП, если он предназначался для ОП. - person Alok Save; 02.06.2012
comment
Кроме того, я всегда думал, что улучшение будет чем-то большим, но теперь, когда вы упомянули об этом, мой друг — микрооптимизатор OCD, который вдавался бы в мельчайшие детали, чтобы даже микроуправлять особенностями распределения памяти для различных переменных. . - person Some Newbie; 02.06.2012

Ответ не так однозначен, как некоторые заставляют вас поверить.

В общем, вы должны предпочесть автоматические переменные (в стеке), потому что это просто проще. Однако в некоторых ситуациях требуется динамическое выделение (в куче):

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

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

Лично я использую следующий принцип:

  • локальные объекты выделяются автоматически
  • локальные массивы откладываются до std::vector<T>, который динамически распределяет их внутри

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

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

person Matthieu M.    schedule 02.06.2012

C++ не упоминает кучу или стек. Что касается языка, то они не существуют/не являются отдельными вещами.

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

person Adrian Cornish    schedule 02.06.2012
comment
В C++ нет упоминания о куче или стеке, это правда. Но ваш компилятор, скорее всего, упоминает кучу и стек. - person Mesop; 02.06.2012
comment
@olchauvin Полностью согласен - но если вы используете мой C++ 14882:2011 100% компилятор жалоб на моем AdeProcessor0.1 - можете ли вы гарантировать, где находятся ваши переменные ;-) - ОП звучит так, как будто они просят меня о предварительной оптимизации - person Adrian Cornish; 02.06.2012
comment
100% компилятор жалоб ? Я использовал один или два из них в прошлом. ;-) - person Paul R; 02.06.2012
comment
@PaulR lol - это то, что comp.lang.c++ и comp.lang.c показали мне существование. Лично мне нравится грязный компилятор, такой как g++ , если вы хороши, он будет делать нестандартные вещи. Но, честно говоря, ОП спрашивает о вещах, связанных с имплантацией. Подобно SPARC, используемому для ядра, если вы выполняли математические операции над целыми числами, которые не были правильно выровнены, независимо от того, как вы их приводили. - person Adrian Cornish; 02.06.2012
comment
-1 Я считаю, что ответы, которые зациклены на точных стандартных определениях (а не гарантиях), бесполезны и раздражают, когда вопрос касается только практических аспектов чего-либо. - person Paul Manta; 02.06.2012
comment
Адриан: специально для имплантации ? Вы должны использовать что-то вроде OS X 10.7 с включенной автозаменой? - person Paul R; 02.06.2012
comment
Я ценю, что Адриан нашел время, чтобы ответить, но использование того, что работает лучше всего, на самом деле мало что говорит, потому что мой вопрос больше о том, как вы определяете, что лучше всего? - person Some Newbie; 02.06.2012
comment
@SomeNewbie лучше всего работает то, что лучше всего работает для вас. Что важнее обслужить миллиард запросов и не пропустить ни одного или обслужить запрос менее чем за 10 мс? Может быть, у нас есть только 10 КБ оперативной памяти для работы, может быть, система никогда не выйдет из строя. Я не могу сказать вам, что лучше всего подходит для вас в вашей ситуации. Это ваши критерии. - person Adrian Cornish; 04.06.2012

Проще говоря, не управляйте собственной памятью без необходимости. ;)

person James Mitch    schedule 02.06.2012

Стек = статические данные, выделенные во время компиляции. (не динамический)

Куча = динамические данные, выделенные во время выполнения. (Очень динамично)

Хотя указатели находятся в стеке... Эти указатели прекрасны, потому что они открывают двери для динамического, спонтанного создания данных (в зависимости от того, как вы кодируете свою программу).

(Но я просто дикарь, так какая разница, что я говорю)

person savageWays    schedule 08.08.2014