Являются ли два указателя, сравнивающих равные, преобразованными в целочисленный тип сравнения равными? - PullRequest
6 голосов
/ 21 мая 2019

Вопрос: Если указатели, сравнивающие равные, равны ли их целочисленным значениям?

Например:

void *ptr1 = //...
void *ptr2 = //...
printf("%d", ptr1 == ptr2); //prints 1

Означает ли это, что (intptr_t) ptr1 == (intptr_t) ptr2 также 1?

С прагматической точки зрения это должно быть правильно. Но учитывая то, что Стандарт указывает на 7.20.1.4(p1):

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

    intptr_t

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

Так что, я думаю, нет, целочисленные значения указателей, сравнивающих равные, необязательно равны друг другу.

Ответы [ 3 ]

7 голосов
/ 21 мая 2019

Ваш анализ верен.Кроме разрешения преобразований в целые числа в §6.3.2.3 , в стандарте не упоминается, как должно вести себя это преобразование.Разумеется, для intptr_t существует требование «туда-обратно», но оно не исключает возможности более чем одной поездки, поскольку компилятор выбирает тот или иной вариант на основе некоторого ограничения или требования.

Итакдействительно, стандарт C не требует (intptr_t) ptr1 == (intptr_t) ptr2 для хранения.

6 голосов
/ 21 мая 2019

Почти во всех реализациях два указателя равны тогда и только тогда, когда их представления равны, но стандарт этого не гарантирует.

Тот факт, что ptr1 == ptr2 не означает, что ptr1 и ptr2 имеют одинаковое представление. N1570 6.5.9 пункт 6:

Два указателя сравниваются равными, если и только если оба указателя равны нулю, оба указатели на один и тот же объект (включая указатель на объект и подобъект в начале) или функция, оба являются указателями на один за последним элементом того же объекта массива, или один указатель на один за концом одного объекта массива, а другой указатель на начало другого объекта массива, который следует сразу за первый объект массива в адресном пространстве.

Например, предположим, что указатель представлен в виде объекта из двух частей, причем первая часть идентифицирует сегмент памяти, а вторая часть - смещение байта в этом сегменте. Если два сегмента могут перекрываться, тогда могут быть два разных представления указателя для одного и того же адреса памяти. Два указателя будут сравниваться как равные (и сгенерированный код, вероятно, должен будет выполнить некоторую дополнительную работу, чтобы это произошло), но если преобразование в intptr_t просто копирует представление, то (intptr_t)ptr1 != (intptr_t)ptr2.

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

Эта возможность объясняет, почему == и != хорошо определены для указателей на разные объекты, но операторы отношений (<, <=, >, >=) не определены. Операторы равенства должны определить, указывают ли два указателя на одно и то же местоположение, но реляционным операторам разрешено сравнивать только смещения и игнорировать базовую часть (при условии, что каждый объект находится в одном сегменте). На практике почти все современные системы имеют монолитное адресное пространство, и операторы равенства и реляции работают согласованно, даже если стандарт не требует от них этого.

6 голосов
/ 21 мая 2019

Реализация, в которой размер указателя находится между двумя целочисленными типами (например, сегментированный режим 80386, где указатели были 48 битами), может обрабатывать что-то вроде:

uintptr_t my_uintptr = (uintptr_t)myptr;

путем сохранения myptr в первых 48 битах my_uintptr и оставления оставшихся битов, содержащих произвольные значения, при условии, что последующее преобразование myptr = (void*)my_uintptr; игнорирует значение этих битов.

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

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

...