адреса в памяти начинаются с наивысшего к низшему
Адреса домов на вашей улице упорядочены по возрастанию или убыванию? Ну, это зависит от того, как ты ездишь.
Так же, как почтовые адреса, адреса памяти на самом деле не упорядочены вообще. Каждый адрес просто идентифицирует уникальное место в памяти (по крайней мере, концептуально. Мы на мгновение проигнорируем сегментированную или виртуальную память).
Но когда ваш почтальон доставляет ежедневную почту, он, скорее всего, работает либо в порядке убывания, либо в порядке убывания (вероятно, как по одной стороне улицы, так и по другую сторону). Это, конечно, более эффективно, чем случайный прыжок из дома в дом. Кроме того, это значительно облегчает работу перевозчика. Если бы он прыгал от дома к дому в произвольном порядке, было бы трудно отследить, какие дома он уже посетил, а какие еще нуждаются в доставке. Если он просто идет по порядку, то положение его грузовика - это все, что ему нужно для отслеживания.
Стек похож на это. Он не занимает произвольные позиции в памяти, а вместо этого занимает первую позицию, а последующие позиции следуют в логическом порядке оттуда. Таким образом, указатель стека (часто «SP») - это все, что необходимо для отслеживания того, какие ячейки стека заняты, а какие свободны.
Куча обязательно другая, хотя. Хотя стек по своей природе имеет порядок «первым пришел - последним вышел», куча изначально неупорядочена. Память кучи может быть выделена и освобождена в любое время. Более ранние распределения могут пережить более поздние распределения. Поэтому куча должна иметь возможность выделять произвольные диапазоны адресов и должна отслеживать их все.
Из-за различных способов работы стека и кучи они должны занимать разные области памяти. В вашем примере второе выделение стека перезапишет память, занятую вашим распределением кучи. Очевидно, это было бы плохо, и это то, что называется переполнение стека .
Большинство современных процессоров имеют все функции, необходимые для полного разделения стековой памяти и динамической памяти. Именно здесь в игру вступают сегменты памяти и виртуальная память. На некоторых платформах стек и куча могут быть идентифицированы одним и тем же диапазоном адресов , при этом все еще занимая различные области физической памяти или даже вторичного хранилища. Обсуждение того, как это работает, выходит за рамки этого поста.
Большинство современных операционных систем на самом деле этого не делают. Чаще всего используется «плоское» адресное пространство, где все адреса, будь то стек, куча, код или что-то еще, относятся к одному адресному пространству. Это облегчает работу разработчика приложений, избавляя от необходимости манипулировать идентификаторами сегментов для каждого адреса.
В плоском адресном пространстве используется та же схема разделения стека и кучи, которая использовалась в древних процессорах, которые не имели сегментации или виртуализации памяти: стек растет от «верха» памяти (более высокие адреса), и куча растет из нижней части памяти (нижние адреса). Определенная точка между ними может быть выбрана как предел обоих, и когда одна из них достигает этой точки, возникает состояние ошибки - либо переполнение стека , либо нехватка памяти .
Конечно, это описание является огромным упрощением, но, надеюсь, оно даст лучшее базовое понимание.