У вас есть адресное пространство памяти, скажем, оно работает от 1 до 100. Вы выделяете свой стек от 1 и выше, а свою кучу - от 100 и ниже. Хорошо, пока?
Из-за самой природы стека он всегда компактен (без отверстий). Это происходит потому, что все, что находится в стеке, является контекстом некоторой вызванной функции. Всякий раз, когда функция завершается, ее контекст удаляется с вершины стека, и мы возвращаемся к предыдущей функции. Я думаю, что вы можете хорошо это понять, если вы получите отладчик и просто будете следовать вызовам функций, помня, каким должен быть стек.
Куча, с другой стороны, не так хорошо себя ведет, скажем, мы зарезервировали память от 70 до 100 для кучи. Мы можем выделить там блок из 4 байтов, и он может увеличиться с 70 до 74, затем мы выделим еще 4 байта, и теперь у нас есть память, выделенная от 70 до 78. Но эта память может быть освобождена в любой точке программы. Таким образом, вы можете освободить 4 байта, которые вы выделили в начале, создав таким образом дыру.
Вот так все и происходит в вашем адресном пространстве. Ядро хранит таблицу, которая отображает страницы из адресного пространства на страницы в реальной памяти. Как вы, наверное, заметили, вы не можете надеяться, что все будет так хорошо настроено, когда у вас запущено более одной программы. Поэтому ядро заставляет каждый процесс думать, что все адресное пространство является непрерывной памятью (давайте пока не будем думать об устройствах, отображенных в памяти), даже если оно может отображаться в памяти несмежно.
Я надеюсь дать разумный обзор по этому вопросу, но, возможно, есть лучшие авторы, чем я, и вам, вероятно, понравится читать намного больше. Так что ищите тексты в виртуальной памяти, это может быть хорошей отправной точкой для понимания того, что вы хотите. Есть несколько книг, которые опишут это более или менее подробно. Несколько из тех, что я знаю: структурированная компьютерная организация, от tanenbaum; Концепция операционной системы, автор Silberschatz. Я почти уверен, что Кнут обсуждает это и в своих книгах по алгоритмам. Если вы любите приключения, попробуйте прочитать о его реализации в x86 в руководствах Intel.