Может ли следующий код быть правдой для указателей на разные вещи

У меня есть часть памяти, которую я "охраняю", определяемый

typedef unsigned char byte;

byte * guardArea;
size_t guardSize;

byte * guardArea = getGuardArea();
size_t guardSize = getGuardSize();

Допустимая реализация для этого будет:

size_t glGuardSize = 1024; /* protect an area of 1kb */
byte * getGuardArea()
{
     return malloc( glGuardSize );
}
size_t getGuardSize()
{
     return glGuardSize;
}

Может ли следующий сниппет вернуть true для любого указателя (из другого malloc, из стека и т.д.)?

if ( ptr >= guardArea && ptr < (guardArea + guardSize)) {
     return true;
}

В стандарте указано, что:

  • Значения
  • в области возвращают значение true. (Когда ptr был членом, все действует правильно.)

  • указатели будут отличаться (a == b, только если они одинаковы).

  • Доступ к всем адресам в массиве байтов можно получить путем увеличения базы.
  • любой указатель может быть преобразован в char * и без него.

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

Edit:

Что такое прецедент?

Возможность определить, находится ли указатель внутри региона, действительно важна, в какой-то момент код написан

if (  isInMyAreaOfInterest( unknownPointer ) ) {
    doMySpecialThing( unknownPointer );
} else {
    doSomethingElse( unknownPointer );
}

Я думаю, что язык должен поддерживать разработчика, делая такие конструкции простыми и очевидными, и наша интерпретация стандарта заключается в том, что разработчику необходимо преобразовать в int. Из-за "undefined поведения" сравнения указателей различных объектов.

Я надеялся на некоторую ясность в том, почему я не могу сделать то, что мне хотелось бы (мой фрагмент), поскольку все сообщения, которые я нашел, говорят, что стандартные требования undefined, без каких-либо объяснений, или примеры почему стандарт лучше, чем я хотел бы, чтобы он работал.

На данный момент у нас есть правило, мы не понимаем, почему существует правило, или спрашиваем, помогает ли нам правило

Пример сообщений:

SO: проверка наличия указателя в области malloced

SO: C указатели сравнения

Ответ 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)

Ответ 2

Да.

void foo(void) {}
void(*a) = foo;
void *b = malloc(69);
uintptr_t ua = a, ub = b;

ua и ub на самом деле разрешено иметь одинаковое значение. Это часто происходило в сегментированных системах (например, MS-DOS), которые могли бы помещать код и данные в отдельные сегменты.