Вставка -1 в указатель как специальное значение

В C можно вставить значение -1 (например, 0xFFFFFFFF) в указатель, используя такой подход, как this one, и ожидать, что такой адрес памяти никогда не выделяется во время выполнения?

Идея состоит в том, чтобы значение указателя использовалось как адрес памяти, за исключением случаев, когда он имеет это «специальное» значение -1. Указатель следует рассматривать как адрес памяти, даже если он равен NULL (в этом случае объект, на который он указывает, еще не построен).

Я понимаю, что это может зависеть от платформы, но предполагается, что данная программа будет работать в Linux, Windows и MacOSX.

Проблема гораздо больше, чем описано здесь, поэтому комментарии или ответы, которые ставят под сомнение этот подход, бесполезны. Я знаю, что это немного взломано, но альтернатива - серьезный рефакторинг: /

Заранее спасибо.


person faken    schedule 08.07.2014    source источник
comment
на самом деле я не уверен, получил ли я ваш вопрос. в основном NULL - это адрес, который не равен ни одному адресу. так почему бы не использовать только NULL? не говоря уже о том, что на какой-то платформе он может иметь значение 0xFFFFFFFF.   -  person Hayri Uğur Koltuk    schedule 08.07.2014
comment
Почему бы вам не выделить статическую контрольную переменную, чтобы никогда ничего не писать в нее, а чтобы зарезервировать адрес? Вы будете уверены, что ни один действительный адрес не совпадает с адресом этого дозорного.   -  person Pascal Cuoq    schedule 08.07.2014
comment
Чтобы уточнить, 0XFFFFFFFF равен только -1, если указатели 32-битные. В противном случае это просто случайное значение указателя, которое можно было бы использовать в обычном режиме.   -  person Mooing Duck    schedule 08.07.2014
comment
В Linux можно безопасно присвоить указателю -1. Безопасно в том смысле, что ни malloc, ни mmap никогда не вернут этот адрес.   -  person rslemos    schedule 08.07.2014
comment
Однако он не может быть одновременно и -1, и NULL. Другими словами: 0xfffffff != NULL.   -  person CodeClown42    schedule 08.07.2014
comment
+1 Паскаль Куок. Также можно принять адрес любой функции (скажем, например, main) в качестве дозорного.   -  person rslemos    schedule 08.07.2014
comment
@ HayriUğurKoltuk, я не могу использовать NULL из-за реальной сложности проблемы, которую я не описал в вопросе. В противном случае это был бы лучший подход.   -  person faken    schedule 08.07.2014
comment
хорошо, тогда в чем настоящая проблема?   -  person Hayri Uğur Koltuk    schedule 08.07.2014
comment
@PascalCuoq, я раньше думал о вашем подходе, и это, безусловно, выполнимо. Однако это библиотека, поэтому у меня нет main (), чтобы делать это красиво. И я бы предпочел избежать этого типа инициализации среды выполнения. Кроме того, библиотеку можно использовать в многопоточной среде, поэтому инициализация должна быть атомарной ... она становится немного уродливой.   -  person faken    schedule 08.07.2014
comment
@MooingDuck, 0xFFFFFFFF - это просто пример (например) того, во что -1 будет преобразован с точки зрения адреса памяти.   -  person faken    schedule 08.07.2014
comment
@rslemos, спасибо, это было бы так в MacOSX и Windows?   -  person faken    schedule 08.07.2014
comment
@faken: Он сказал, что вы можете использовать адрес любой функции (скажем, например, main). Акцент на любом, а не на главном. Я полагаю, у вашей библиотеки есть функция.   -  person Mooing Duck    schedule 08.07.2014
comment
@ HayriUğurKoltuk, моей настоящей проблемой был бы совсем другой вопрос: D   -  person faken    schedule 08.07.2014
comment
В OS X «адрес -1 никогда не будет выделен во время выполнения.   -  person Stephen Canon    schedule 08.07.2014
comment
@faken Насколько мне известно, &sentinel является «постоянным выражением» (в смысле C99 6.6) и может использоваться в инициализаторе. Зачем нужен какой-либо код?   -  person Pascal Cuoq    schedule 08.07.2014
comment
@MooingDuck, я отвечал на его первый комментарий. Адрес функции может быть возможным выбором.   -  person faken    schedule 08.07.2014
comment
@PascalCuoq, ты прав. Это возможное решение проблемы. Пожалуйста, разместите свой комментарий как более полный ответ ниже, в конце концов, вы были первым, кто предложил это решение.   -  person faken    schedule 08.07.2014
comment
Вот что делает sqlite: sqlite.org/c3ref/c_static.html   -  person michaelmeyer    schedule 08.07.2014


Ответы (2)


Один из способов определить контрольное значение, с которым не будет совпадать никакой другой действительный адрес, - это статическая переменная:

static t sentinel;
t *p = &sentinel;

Если вы собираетесь использовать плоское адресное пространство и все указатели имеют одинаковую ширину, вы можете минимизировать накладные расходы, объявив sentinel типа char вместо t.


Чтобы ответить на ваш вопрос о (t*)-1:

  • -1 имеет тип int. Я бы порекомендовал (t*)(uintptr_t)-1, который, скорее всего, будет последним адресом даже для 64-битного плоского адресного пространства.

  • он не очень чистый, но он должен работать на всех распространенных архитектурах, потому что, пока компилятор намеревается сравнивать указатели с помощью инструкции сборки беззнакового сравнения (как обычно), для любого объекта a, который компилятор может надеяться разместить в конец адресного пространства, &a + 1 должен сравниваться больше, чем &a. На практике это предотвращает использование последнего адреса для хранения чего-либо.

person Pascal Cuoq    schedule 08.07.2014
comment
Еще раз спасибо, Паскаль. Я использую макросы преобразования типов GLib (которые, как мне кажется, делают то, что вы предлагаете), поэтому преобразование -1 в целое число позаботится обо мне. - person faken; 08.07.2014

Это ГРАС (общепризнанный безопасный). Ни одна из основных ОС не будет выделять память, которая может конфликтовать с выбранным вами дозорным. Однако есть несколько патологических случаев, когда такое предположение было бы неверным. Например, патологический компилятор C ++ может выбрать запуск стека с 0xFFFFFFFF, не нарушая никаких ограничений в спецификации.

Только в пределах разумных ОС практически невозможно, чтобы 0xFFFFFFFF (или его 64-битный эквивалент) был действительным адресом памяти. Это не может быть допустимый адрес памяти массива (правила C ++ запрещают это). Технически это может быть действительный индекс символа объекта, размещенного в конце пространства, но этому препятствуют две вещи.

  1. У большинства ОС есть отступы
  2. Большинство ОС используют большие значения памяти в качестве памяти ядра.

Если у вас есть возможность использовать глобальное значение в качестве дозорного, это гарантированно безопасно.

char sentinel;

char* p = "Hello";
char* p2 = 0; // null pointer
char* p3 = &sentinel;

if (p3 == &sentinel)
    cout << "p3 was a sentinel" << endl;
person Cort Ammon    schedule 08.07.2014
comment
Спасибо @CortAmmon, ваш ответ кажется правильным, но Паскаль первым ответил на него в комментарии, поэтому я отмечу его ответ как правильный. Тем не менее, проголосуйте за. Спасибо еще раз. - person faken; 08.07.2014
comment
@CortAmmon Какие адреса с низким целым числом (1-4095)? Я знаю, что Linux отображает их как недоступные. Мне любопытны другие ОС. Несомненно, постоянный адрес имеет некоторые преимущества перед, например, статическим адресом. - person PSkocik; 18.06.2019