С++ Максимальный допустимый адрес памяти
Я часто вижу код, который добавляет значение, например длину к указателю, а затем использует это значение, например.
T* end = buffer + bufferLen;//T* + size_t
if (p < end)
Однако возможно ли, чтобы буфер был выделен достаточно близко к концу памяти, который может переполнять "buffer + bufferLen" (например, 0xFFFFFFF0 + 0x10), в результате чего "p < end" является ложным, даже если p действительный адрес элемента (например, 0xFFFFFFF8).
Если это возможно, как его можно избежать, когда я вижу много вещей, которые работают с диапазоном начала/конца, где конец следующего элемента после последнего
Ответы
Ответ 1
Из стандарта:
С++ 11
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
Включает отладку кучи страниц, которая проверяет динамическую память кучи операций, включая распределения и освобождения, и вызывает отладчик если он обнаруживает ошибку кучи.
Эта опция позволяет полностью отлаживать кучу страниц при настройке для файлов изображений и стандартная отладка кучи страниц при установке в системном реестре или ядре Режим.
Установка этого флага для файла изображения такая же, как и для ввода gflags/p enable/full для файла изображения в командной строке
Что касается проблемы перекоса указателя: никакая операционная система не выделяет страницу, содержащую адрес VA 0xFFFFFFFF, так же, как ни одна операционная система никогда не выделяет страницу, содержащую 0x00000000. Для такого переполнения размер *start
должен быть достаточно большим для start+1
, чтобы перепрыгнуть через все зарезервированные VA в конце допустимых диапазонов. Но в таком случае добавка, выделенная для start
, должна быть как минимум одного такого размера ниже последнего действительного адреса VA, и это означает, что start+1
будет действительным (это следует за start+N
также всегда справедливо, если start
был выделен как sizeof(*start)*N
).
Ответ 4
Не беспокойтесь об этом. Ваш распределитель (возможно, new
, но, возможно, что-то еще) не даст вам что-то настолько близкое к концу памяти, которое оно обертывает.
Будем беспокоиться о проверке границ. Вы никогда не получите выделение, которое обертывается таким образом, так как до тех пор, пока вы не перегружаете массивы (что в любом случае имеет поведение undefined), вы не закончите обертку.
Также полезно отметить, что большие ядра адресного пространства процесса зарезервированы для ядра. В большинстве операционных систем эта область высокого порядка зарезервирована.