TL; DR : (T*)-1
, скорее всего, будет работать так, как задумано на практике, но для обеспечения безопасности, переносимости и проверки на будущее вы должны использовать вместо указателей нулевые указатели.
Я просматривал некоторый код, который в дополнение к нулевым указателям использует некоторые специальные значения, такие как (T*)-1
, как правило, в качестве возвращаемых значений при сбое какой-либо функции create.
Фактически, некоторые интерфейсы POSIX, такие как shmat()
, ведут себя аналогично, возвращая (void *)-1
, чтобы указать на ошибку. Для них это эквивалент многих других стандартных функций, возвращающих значение int
-1. Это значение, которое никогда не будет допустимым возвращаемым значением для успешного вызова. Следовательно, это должно работать в каждой реализации, соответствующей POSIX, и я думаю, что другие требования POSIX имеют общий эффект, требующий того же самого для типов указателей, отличных от void *
, также.
В более общем смысле, C явно разрешает преобразование целых чисел в указатели без ограничений, но с оговоркой, что
За исключением [для констант нулевого указателя], результат определяется реализацией,
может быть неправильно выровнен, может не указывать на сущность
ссылочный тип и может быть представлением прерываний.
(C2011, 6.3.2.3 / 5 ). Основными проблемами такого преобразования являются
- что результатом
(T*)-1
является представление ловушки, и в этом случае описанная вами схема приводит к неопределенному поведению.
- что результатом
(T*)-1
может быть действительный указатель на T
, и в этом случае использование его в качестве сторожа небезопасно.
Насколько мне известно, первое из них не является проблемой для любой реализации C, с которой вы, вероятно, столкнетесь. Я думаю, что второе вряд ли будет проблемой для вас на практике, но если вы ориентируетесь на системы, отличные от POSIX, то я менее уверен в этом.
Вы продолжаете спрашивать,
Где указывается тип, достаточно большой, чтобы
((T *) - n) + sizeof (T) будет переполнен, что означает, что адрес никогда не сможет
на самом деле быть выделенным для экземпляра типа T, это нормально? Может ли
компилятор увидит что-то вроде if (ptr == (T *) - 1), решив, что это
невозможно и оптимизировать это?
Это интересный вопрос. Предполагая, что (T*)-1
не создает представление ловушек, применяется это положение:
Два указателя сравниваются равными тогда и только тогда, когда оба являются нулевыми указателями, оба
указатели на один и тот же объект (включая указатель на объект и
подобъект в начале) или функция, оба являются указателями на один
за последним элементом того же объекта массива, или один указатель на
один за концом одного объекта массива, а другой указатель на
начало другого объекта массива, который следует сразу за
первый объект массива в адресном пространстве.
(C2011, 6.5.9 / 6 )
К сожалению, однако, это немного беспорядок.
Хотя стандарт накладывает ограничения на типы операндов указателей выражения ==
, он не требует, чтобы их значения были действительными указателями. Чтобы не было никаких сомнений по этому поводу, это необходимо для внутренней согласованности с положениями раздела 6.3.2.3 , в которых указаны результаты сравнений на равенство с использованием нулевых указателей (не ограничивающихся константами нулевых указателей).
Если хотя бы один из операндов x == y
является недопустимым указателем, отличным от нулевого указателя, например, мы можем предположить, (T *)-1
, то не выполняется ни одна из альтернатив, указанных в 6.5.9 / 6, поэтому выражение должно иметь значение 0. Компилятор может использовать это для оправдания оптимизации теста и ветвления.
Однако на практике реализации часто не соответствуют этому. Вместо этого они исходят из исторического поведения, возможно, оправдывая себя мимолетной ссылкой на адресное пространство в 6.5.9 / 6 или, возможно, придерживаясь либерального взгляда на то, что такое объект. Для реализаций, которые предоставляют плоское представление адресного пространства, это проявляется как ==
, оцениваемый с точки зрения того, совпадают ли адреса, которым соответствуют значения указателя, независимо от отношения этих адресов к любому объекту. Такая реализация, как , не должна оптимизировать тест ==
, поскольку она не может с уверенностью предположить, что она всегда будет терпеть неудачу.
Суть в том, что хотя компилятор вряд ли оптимизирует тестирование, вы не можете полагаться на стандарт для гарантии того, что он этого не сделает. Вы находитесь в более безопасном положении, если вы используете нулевые указатели в качестве сторожей, поскольку, несмотря на несоответствие, которое я на практике выявлял, нулевые указатели одного типа do сравниваются равными во всех реализациях в соответствии с 6.3.2.3/ 4.