С++ Максимальный допустимый адрес памяти

Я часто вижу код, который добавляет значение, например длину к указателю, а затем использует это значение, например.

T* end = buffer + bufferLen;//T* + size_t

if (p < end)

Однако возможно ли, чтобы буфер был выделен достаточно близко к концу памяти, который может переполнять "buffer + bufferLen" (например, 0xFFFFFFF0 + 0x10), в результате чего "p < end" является ложным, даже если p действительный адрес элемента (например, 0xFFFFFFF8).

Если это возможно, как его можно избежать, когда я вижу много вещей, которые работают с диапазоном начала/конца, где конец следующего элемента после последнего

Ответ 1

Из стандарта:

5.9 Реляционные операторы [expr.rel]

Если два указателя указывают на элементы одного и того же массива или один за ним конец массива, указатель на объект с более высоким индексом сравнивается выше.

Так что вам не нужно беспокоиться; согласованная реализация гарантирует, что указатель прошлого конца правильно сравнивается с остальной частью массива. Кроме того,

3.7.4.1 Функции распределения [basic.stc.dynamic.allocation]

[...] Возвращаемый указатель должен быть соответствующим образом выровнен, чтобы он мог быть преобразован к указателю любого полного типа объекта с фундаментальным требованием выравнивания (3.11), а затем используется для доступа к объекту или массиву в выделенном хранилище [...]

Импликация заключается в том, что возвращаемый указатель должен быть обработан как указатель на начало массива соответствующего размера, поэтому 5.9 продолжает удерживаться. Это будет иметь место, если вызов функции распределения является результатом вызова operator new[] (5.3.4: 5).

Как практический вопрос, если вы находитесь на платформе, где возможно, чтобы распределитель (несоответствующий) возвращал блок памяти, заканчивающийся на 0xFFFFFFFF, вы могли бы в большинстве случаев писать

if (p != end)

Ответ 2

Элементы смежного выделения памяти не могут иметь несмежные адреса. end всегда имеет адрес более высокого значения, чем start.

В случае, когда распределение происходит, например, с точностью до 0xFFFFFFFF, значение end будет 0x00000000, это будет ошибкой, и код должен быть исправлен для размещения этого сценария.

На некоторых платформах, хотя этот сценарий невозможен по дизайну и может быть разумным компромиссом в логике для простоты. Например, я без колебаний напишу if(p < end) в пользовательском режиме Windows.

Ответ 3

Правда, во многих [start, end) парах конечных точек алгоритма проходит последняя действительная запись. Но ваша реализация никогда не должна разыгрываться end, последняя доступная к ней запись должна быть end-1, которая, как гарантируется, находится в допустимой области. Если ваши алгоритмы dereferences *end, то это ошибка. На самом деле есть распределители тестов, которые преднамеренно размещают регион на самых последних байтах действительной страницы, сразу же следуя нераспределенной области. При использовании таких распределителей алгоритм, который разделяет *end, вызовет ошибку защиты.

FLG_HEAP_PAGE_ALLOCS

Включает отладку кучи страниц, которая проверяет динамическую память кучи операций, включая распределения и освобождения, и вызывает отладчик если он обнаруживает ошибку кучи.

Эта опция позволяет полностью отлаживать кучу страниц при настройке для файлов изображений и стандартная отладка кучи страниц при установке в системном реестре или ядре Режим.

  • Полноценная отладка кучи (для/i) помещает недоступную страницу в конец распределения.

  • Стандартная отладка кучи страниц (для/r или /k ) проверяет распределения как они освобождаются.

Установка этого флага для файла изображения такая же, как и для ввода gflags/p enable/full для файла изображения в командной строке

Что касается проблемы перекоса указателя: никакая операционная система не выделяет страницу, содержащую адрес VA 0xFFFFFFFF, так же, как ни одна операционная система никогда не выделяет страницу, содержащую 0x00000000. Для такого переполнения размер *start должен быть достаточно большим для start+1, чтобы перепрыгнуть через все зарезервированные VA в конце допустимых диапазонов. Но в таком случае добавка, выделенная для start, должна быть как минимум одного такого размера ниже последнего действительного адреса VA, и это означает, что start+1 будет действительным (это следует за start+N также всегда справедливо, если start был выделен как sizeof(*start)*N).

Ответ 4

Не беспокойтесь об этом. Ваш распределитель (возможно, new, но, возможно, что-то еще) не даст вам что-то настолько близкое к концу памяти, которое оно обертывает.

Будем беспокоиться о проверке границ. Вы никогда не получите выделение, которое обертывается таким образом, так как до тех пор, пока вы не перегружаете массивы (что в любом случае имеет поведение undefined), вы не закончите обертку.

Также полезно отметить, что большие ядра адресного пространства процесса зарезервированы для ядра. В большинстве операционных систем эта область высокого порядка зарезервирована.