Вопрос по c++ – почему размер стековой памяти так ограничен?

68

Когда вы выделяете память в куче, единственным ограничением является свободное ОЗУ (или виртуальная память). Это делает Гб памяти.

Так почему же размер стека так ограничен (около 1 Мб)? Какая техническая причина мешает вам создавать действительно большие объекты в стеке?

Update : Мое намерение может быть неясным, яdo not want разместить огромные объекты в стеке, и яdo not need больший стек. Этот вопрос - просто любопытство.

Почему было бы целесообразно создавать большие объекты в куче? (Цепочки вызовов обычно идут в стек.) Makoto
Я думаю, что реальный ответ проще, чем изображает большинство ответов: «потому что это то, как мы всегда это делали, и до сих пор все было в порядке, так почему меняемся?» Jerry Coffin
@ user1202136: Я прочитал все из них - но люди догадываются, и я предполагаю, что многие из факторов, которые они цитируют, вероятно, даже не учитывались при принятии первоначальных решений по этому вопросу. Для обозначения фразы «иногда сигара - это всего лишь сигара». Jerry Coffin
@JerryCoffin Читали ли вы какие-либо ответы, опубликованные до сих пор? Есть больше понимания этого вопроса. user1202136
& quot; Насколько большим должен быть стек по умолчанию? & quot; "О, я не знаю, сколько потоков мы можем запустить?" "Взрывается где-то над К" «Хорошо, тогда мы назовем это 2K, у нас есть 2 гигабайта виртуальных, так как насчет 1 мегабайта?» "Да, хорошо, каков следующий выпуск?" Martin James

Ваш Ответ

7   ответов
40

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

Например, 32-разрядное приложение обычно имеет виртуальное адресное пространство 2 ГБ. Это означает, что если размер стека составляет 2 МБ (по умолчанию в pthreads), то вы можете создать максимум 1024 потоков. Это может быть мало для приложений, таких как веб-серверы. Увеличение размера стека, скажем, до 100 МБ (т.е. вы резервируете 100 МБ, но не обязательно сразу выделяете 100 МБ для стека), ограничило бы количество потоков до 20, что может быть ограничено даже для простых приложений с графическим интерфейсом.

Интересный вопрос, почему у нас все еще есть этот предел на 64-битных платформах. Я не знаю ответа, но я предполагаю, что люди уже привыкли к некоторым «наилучшим методам стека»: будьте осторожны, выделяя огромные объекты в куче и, если необходимо, вручную увеличивайте размер стека. Поэтому никто не нашел полезным добавить «огромный» поддержка стека на 64-битных платформах.

Многие 64-разрядные машины имеют только 48-разрядные адреса (с большим преимуществом по сравнению с 32-разрядными, но все еще ограничены). Даже с дополнительным пространством вам нужно беспокоиться о том, как выполняется резервирование по отношению к таблицам страниц, то есть всегда есть издержки при наличии большего пространства. Вероятно, столь же дешево, если не дешевле, выделить новый сегмент (mmap) вместо того, чтобы резервировать огромные стековые пространства для каждого потока.
@ edA-qamort-ora-y: об этом ответе не идет речьallocationэто говорит оvirtual memory reserveration, который почти бесплатно, и, конечно,much быстрее, чем mmap.
21

A limited stack size is an error detection and containment mechanism.

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

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

0

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

Стек живет в куче, но, тем не менее, поскольку он используется непрерывно, он, вероятно, никогда не покидает кэш-память ЦП, что делает его быстрее, чем просто средний доступ к куче. Это является причиной сохранения размера стека; сохранить его в кеше как можно больше. Выделение больших стековых объектов (возможно, автоматическое изменение размера стека при переполнении) идет вразрез с этим принципом.

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

Хотя я считаю, что кэширование играет большую роль в причине искусственного уменьшения размера стека, я должен исправить вас в утверждении «стек живет в куче». И стек, и куча живут в памяти (виртуально или физически).
5

стек является непрерывным, поэтому, если вы выделяете 12 МБ, вы должны удалить 12 МБ, если хотите опуститься ниже того, что создали. Также перемещение объектов вокруг становится намного сложнее. Вот пример из реальной жизни, который может облегчить понимание:

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

stacking boxes of any weight on top of each other, but when you need to get something on the bottom you have to undo your entire pile. If you want to take a item out of the pile and give it to someone else you must take off all of the boxes and move the box to the other person's pile (Stack only) You put all of your boxes (except for really small boxes) over in a special area where you do not stack stuff on top of other stuff and write down where you put it on a piece of paper (a pointer) and put the paper on the pile. If you need to give the box to someone else you just hand them the slip of paper from your pile, or just give them a photocopy of the paper and leave the original where it was in your pile. (Stack + heap)

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

@MartinJames: спецификация C ++ также говорит, что функция обычно может помещать данные непосредственно в буфер назначения и не использовать временный, так что, если вы будете осторожны, нет необходимости возвращать буфер размером 12 МБ по значению.
@MooingDuck Да, но вы работаете в виртуальной памяти в вашей программе. Если я введу подпрограмму, положу что-то в стек, а затем вернусь из подпрограммы, мне потребуется либо отменить выделение, либо переместить созданный мною объект, прежде чем я смогу раскрутить стек, чтобы вернуться туда, откуда я пришел.
хотя мой комментарий произошел из-за неверного толкования (и я его удалил), я все еще не согласен с этим ответом. Удаление 12 МБ с вершины стека - это буквально один код операции. Это в основном бесплатно. Кроме того, компиляторы могут и действительно обманывать "стек" Правило, так что нет, им не нужно копировать / перемещать объект, прежде чем раскрутить, чтобы вернуть его. Поэтому я думаю, что ваш комментарий также неверен.
Ну, обычно не имеет большого значения, что освобождение 12 МБ занимает один код операции в стеке, а не 100 в куче - это, вероятно, ниже уровня шума фактической обработки буфера 12 МБ. Если компиляторы хотят обмануть, когда они замечают, что возвращается смехотворно большой объект (например, путем перемещения SP перед вызовом, чтобы сделать пространство объектов частью стека вызывающих), тогда это нормально, TBH, разработчики, которые Возврат таких объектов (а не указателей / ссылок) в некоторой степени затрудняет программирование.
14

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

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

Вы должны найти правильный баланс для вашей программы.

Некоторые люди, такие как @BJovke, считают, что виртуальная память по существу свободна. Это правда, что вам не нужно, чтобы физическая память поддерживала всю виртуальную память. Вы должны быть в состоянии по крайней мере выдавать адреса виртуальной памяти.

Однако на типичном 32-разрядном ПК размер виртуальной памяти совпадает с размером физической памяти, поскольку у нас есть только 32 бита для любого адреса, виртуального или нет.

Поскольку все потоки в процессе используют одно и то же адресное пространство, они должны делить его между собой. И после того, как операционная система взяла на себя свою роль, «только» 2-3 ГБ осталось под заявку. И этот размер является пределом дляboth физическийand виртуальной памяти, потому что просто больше нет адресов.

Пространство не будет потрачено впустую, размер стека - это просто резервирование непрерывного виртуального адресного пространства. Так что, если вы установите размер стека 100 МБ, объем оперативной памяти, который будетactually быть использованы зависит от потребления стека в потоках.
@BJovke - Ноvirtual адресное пространство все равно будет использовано. В 32-разрядном процессе это ограничивается несколькими ГБ, поэтому резервирование 20 * 100 МБ может вызвать проблемы.
@MartinJames: никто не говорит, что все объекты должны быть в стеке, мы обсуждаем, почему размер стека по умолчанию невелик.
Самая большая проблема с потоками заключается в том, что вы не можете легко передавать объекты стека другим потокам. Либо поток производителя должен синхронно ждать, пока поток потребителя освободит объект, либо должны быть сделаны дорогостоящие и генерирующие конкуренцию глубокие копии.
0

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

Алгоритмы Седжвика "Алгоритмы" есть несколько хороших примеров "удаления" рекурсия из рекурсивных алгоритмов, таких как QuickSort, путем замены рекурсии итерацией. На самом деле алгоритм все еще рекурсивен, и все еще остается в виде стека, но вы выделяете стек сортировки в куче, а не используете стек времени выполнения.

(Я предпочитаю второе издание с алгоритмами, приведенными на Паскале. Его можно было использовать за восемь баксов.)

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

-8

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

Это не мало! Сколько вызовов функций вам нужно было бы выполнить, чтобы использовать 1 МБ стека? Значения по умолчанию в любом случае легко изменяются в компоновщике, и поэтому у нас остается «зачем использовать стек вместо кучи?»
Ну, во-первых, я не хочу, чтобы 12 МБ (или даже 1 МБ) требования к стеку накладывались на стек каждого потока, вызывающего поврежденную функцию. Тем не менее, я должен согласиться, что 1MB немного скупой. Я был бы счастлив с 100 МБ по умолчанию, в конце концов, ничто не мешает мне уменьшить его до 128 КБ точно так же, как ничто не мешает другим разработчикам включить его.
один вызов функции.int main() { char buffer[1048576]; }  Это очень распространенная проблема новичка. Конечно, есть простой обходной путь, но почему мы должны обходить размер стека?
Почему бы вам не захотеть наложить 12МБ стека на ваш поток? Единственная причина этого в том, что стеки маленькие. Это рекурсивный аргумент.
Никто не говорит, что все объекты должны быть в стеке, мы обсуждаем, почему размер стека по умолчанию невелик.

Похожие вопросы