Ответ 1
Как указывал Mads, чтобы уловить большинство запросов с помощью нулевых указателей, Unix-подобные системы, как правило, делают страницу с нулевым адресом "unmapped". Таким образом, доступ немедленно вызывает исключение CPU, другими словами, segfault. Это намного лучше, чем позволить запуску приложения. Однако таблица векторов исключений может быть по любому адресу, по крайней мере, на процессорах x86 (для этого есть специальный регистр, загруженный кодом операции lidt
).
Адрес начальной точки является частью набора соглашений, которые описывают, как выкладывается память. Компилятор, когда он создает исполняемый двоичный файл, должен знать эти соглашения, поэтому они вряд ли изменятся. В принципе, для Linux соглашения о компоновке памяти наследуются от самых первых версий Linux, в начале 90-х. Процесс должен иметь доступ к нескольким областям:
- Код должен быть в диапазоне, который включает начальную точку.
- Должен быть стек.
- Должна быть куча с пределом, который увеличивается с системными вызовами
brk()
иsbrk()
. - Должно быть место для системных вызовов
mmap()
, включая загрузку разделяемой библиотеки.
В настоящее время куча, где malloc()
идет, поддерживается mmap()
вызовами, которые получают куски памяти по любому адресу, которое ядро считает нужным. Но в более старые времена Linux был похож на предыдущие Unix-подобные системы, и его куча требовала большой площади в одном непрерывном куске, который мог бы увеличиться по отношению к увеличению адресов. Таким образом, независимо от того, что было в соглашении, он должен был набивать код и стекать по низким адресам, а каждый кусок адресного пространства после заданной точки - в кучу.
Но есть также стек, который обычно довольно мал, но может расти довольно резко в некоторых случаях. Стек растет, и когда стек заполнен, мы действительно хотим, чтобы процесс предсказуемо разбился, а не перезаписал некоторые данные. Таким образом, для стека должна быть широкая область, а на нижнем конце этой области - открытая страница. И вот! На нулевом адресе есть неотображаемая страница, чтобы перехватывать нулевые указатели указателя. Следовательно, было определено, что стек получит первые 128 МБ адресного пространства, за исключением первой страницы. Это означает, что код должен был идти после этих 128 МБ по адресу, подобному 0x080xxxxx.
Как отмечает Майкл, "потерять" 128 МБ адресного пространства не имеет большого значения, потому что адресное пространство было очень большим в отношении того, что можно было бы фактически использовать. В то время ядро Linux ограничивало адресное пространство для одного процесса до 1 ГБ, максимально допустимое аппаратным обеспечением на 4 ГБ, и это не считалось большой проблемой.