Ответ 1
По-прежнему возможно выделение для создания указателя, который удовлетворяет условию, несмотря на то, что указатель не указывает на область. Это произойдет, например, в режиме 80286 в защищенном режиме, который используется Windows 3.x в стандартном режиме и OS/2 1.x.
В этой системе указатели представляют собой 32-битные значения, разделенные на две 16-битные части, традиционно написанные как XXXX:YYYY
. Первая 16-битная часть (XXXX
) - это "селектор", который выбирает банк размером 64 КБ. Вторая 16-битная часть (YYYY
) - это "смещение", которая выбирает байт в этом банке на 64 КБ. (Это сложнее, чем это, но позвольте просто оставить это при этом для обсуждения.)
Блоки памяти, превышающие 64 КБ, разбиты на блоки размером 64 КБ. Чтобы перейти от одного блока к другому, вы добавите 8 в селектор. Например, байт после 0101:FFFF
равен 0109:0000
.
Но почему вы добавляете 8 для перехода к следующему селектору? Почему бы не просто увеличить селектор? Поскольку нижние три бита селектора используются для других вещей.
В частности, нижний бит селектора используется для выбора таблицы селектора. (Пусть игнорируют биты 1 и 2, так как они не имеют отношения к обсуждению. Предположим для удобства, что они всегда равны нулю.)
Существуют две таблицы селекторов, таблица глобального выбора (для общей памяти для всех процессов) и таблица локального выбора (для памяти, доступной для отдельного процесса). Поэтому селекторами, доступными для частной памяти процесса, являются 0001
, 0009
, 0011
, 0019
и т.д. Между тем, селекторами, доступными для глобальной памяти, являются 0008
, 0010
, 0018
, 0020
и т.д. (Селектор 0000
зарезервирован.)
Хорошо, теперь мы можем настроить наш встречный пример. Предположим, что guardArea = 0101:0000
и guardSize = 0x00020000
. Это означает, что защищенные адреса 0101:0000
через 0101:FFFF
и 0109:0000
через 0109:FFFF
. Кроме того, guardArea + guardSize = 0111:0000
.
Между тем, предположим, что есть некоторая глобальная память, которая распределяется по 0108:0000
. Это глобальное распределение памяти, потому что селектор является четным числом.
Обратите внимание, что выделение глобальной памяти не является частью охраняемой области, но его значение указателя удовлетворяет числовому неравенству 0101:0000 <= 0108:0000 < 0111:0000
.
Бонусная болтовня. Даже на архитектурах CPU с плоской моделью памяти тест может завершиться неудачей. Современные компиляторы используют поведение undefined и оптимизируют соответственно. Если они видят реляционное сравнение между указателями, им разрешено предположить, что указатели указывают на один и тот же массив (или один за последним элементом этого массива). В частности, единственными указателями, которые можно юридически сравнить с guardArea
, являются те формы guardArea
, guardArea+1
, guardArea+2
,..., guardArea + guardSize
. Для всех этих указателей условие ptr >= guardArea
истинно и поэтому может быть оптимизировано, что уменьшит ваш тест до
if (ptr < (guardArea + guardSize))
который теперь будет выполняться для указателей, численно меньших guardArea
.
Мораль истории. Этот код небезопасен даже на плоских архитектурах.
Но все не потеряно. Преобразование указателя в целое является реализацией, что означает, что ваша реализация должна документировать, как она работает. Если ваша реализация определяет преобразование указатель-целое как производящее числовое значение указателя, и вы знаете, что вы находитесь на плоской архитектуре, то вы можете сравнить целые числа, а не указатели. Целочисленные сравнения не ограничены тем же способом, что и сравнение указателей.
if ((uintptr_t)ptr >= (uintptr_t)guardArea &&
(uintptr_t)ptr < (uintptr_t)guardArea + (uintptr_t)guardSize)