Является ли ((void *) -1) действительным адресом?

Verbatim from Linux 'man shmat:

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

[...] при ошибке (void *) -1 возвращается, а errno устанавливается, чтобы указать причину ошибки.

(POSIX говорит то же самое, используя несколько другую формулировку.)

Есть ли какое-либо обязательное правило или определение (стандарт?), что (void *) -1 не может быть действительным адресом?

Ответ 1

Чтобы ответить на вопрос напрямую, нет, нет обязательного правила, определения, стандарта или спецификации, в которых (void *) -1 не может быть действительным адресом.

(Конечно, никаких правил, определений, стандартов или спецификаций адресов памяти не обязательно. Я вижу, что люди ходят по улице каждый день, не соблюдая, например, стандарт C, но я никогда не видел, чтобы кто-то арестовывал за это Но даже если мы опускаем обязательную часть, использование (void *) -1 в качестве адреса, как правило, не запрещено стандартными спецификациями.)

Однако для того, чтобы shmat не работал, не нужно, чтобы (void *) -1 не был действительным адресом. Просто необходимо, чтобы успешный вызов shmat никогда не возвращал (void *) -1 и чтобы (void *) -1 поддерживался компилятором для тестирования возвращаемого значения из shmat. Если эти два условия выполнены, то программа всегда может отличить успешный вызов shmat от неудачного вызова shmat.

Что касается второго условия, стандарт C не гарантирует, что (void *) -1 может использоваться, поэтому POSIX, указав, что это возврат ошибки из shmat, неявно требует реализации C (или другого языка) для его поддержки. Таким образом, это расширение языка, требуемого POSIX, и, как правило, это просто для компиляторов.

Что касается первого условия, подумайте, когда мы хотим, чтобы shmat вернул (void *) -1 для успешного вызова. shmat можно вызвать с запрошенным пользователем адресом или без него, и в этом случае реализация выбирает адрес. В любой нормальной компьютерной архитектуре существует несколько причин использовать адреса, кратные различным значениям. Для shmat наиболее очевидным является отображение памяти. На архитектурах с виртуальной памятью память отображается в единицах страниц, а shmat, когда она отображает память для сегмента, будет отображаться на начало страницы. Любой ровный размер страницы не имеет кратных значений (void *) -1, так как последний нечетный, поэтому shmat никогда не выбирает отображение сегмента на (void *) -1. Даже если shmat не использовал размер страницы, он обычно использовал бы другое выравнивание, такое как 4, 8 или 16 байт, потому что предоставление выровненной памяти означает, что структуры, сохраненные в начале этой памяти, будут выровнены, что приведет к более быстрой памяти доступ на многие процессоры.

Это оставляет случай, когда пользователь запрашивает (void *) -1 в качестве адреса. Это было бы необычно, и это могло бы работать только в том случае, если сегмент памяти был одним байтом или модель памяти позволяла обертывать (или компилятор представил очень странную модель памяти, в которой (void *) -1 не был последним байтом в адресном пространстве), Я не могу точно сказать, поддерживают ли какие-либо POSIX-системы это или нет. Однако ясно, что это по сути бесполезно, и никто не имеет никаких оснований для этого, кроме любопытства. Таким образом, безопасно и разумно управлять этим случаем из shmat, просто говоря, что это не поддерживается, не делайте этого.

Ответ 2

0xffffffff является технически допустимым адресом в 32-битной среде, но в большинстве операционных систем (конечно, Linux/Windows) будет находиться в зарезервированной части ядра адресного пространства. Это означает, что в пользовательском режиме безопасно использовать его как код ошибки, так как функция распределения режима пользователя не возвращает это как полезный адрес.

Ответ 3

Для простоты рассмотрим машину с 16 битами адресного пространства. Этот аппарат может адресовать память от 0 до 65535. В этом случае (void*) -1 будет таким же, как 0xffff, что равно 65535. Хотя технически этот адрес действителен, существует несколько систем, в которых есть что-то там, что они позволят получить доступ.

Другое дело, что почти все системные вызовы POSIX возвращают -1 при ошибке.


Как отметил Бендж, на самом деле можно сопоставить адрес NULL. Это можно использовать, например, когда вы хотите увидеть, есть ли сопоставление с указанным shmid, и в этом случае аргумент shmaddr имеет значение NULL, а функция return NULL означает, что разделяемая память существует.