Оператор равенства C на преобразованных указателях - PullRequest
11 голосов
/ 19 апреля 2019

Исходя из Приведение целочисленной константы к типу указателя

Из этого вопроса мы знаем из 6.3.2.3p5 (C11), что мы можем преобразовать любое целое число в указатель (т. Е. Оно не является UB само по себе):

Целое число может быть преобразовано в любой тип указателя. За исключением случаев, указанных ранее, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением прерывания.

Тогда из 6.5.9p6 имеем:

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

Так что, похоже, мы можем применить оператор равенства здесь без UB (в отличие от операторов отношений). Рассмотрим:

struct A;

int f(void) {
    struct A * a = (struct A *) 1;
    struct A * b = (struct A *) 1;
    return a == b;
}

Предполагая, что в адресе a 1 нет объекта A, можно утверждать, что f() должен вернуть false, поскольку ни одно из условий не соответствует приведенному выше.

Как это опровергается? Относится ли «указатель на один и тот же объект» к адресам, даже если там нет объектов (не так, как мог бы знать компилятор)? Должны ли мы просто понимать, что это определяется реализацией, поскольку предыдущие результаты уже были определены реализацией? Где это указано в стандарте?

Все основные компиляторы возвращают true для приведенного выше кода, как и следовало ожидать.

Ответы [ 4 ]

7 голосов
/ 19 апреля 2019

Как это опровергается? Относится ли «указатель на тот же объект» к адреса, даже если там нет объектов

Нет, я не думаю, что это было бы правдоподобным чтением. Если вы указываете, что значение указателя не является указателем на объект (и если это не нулевой указатель), то сравнение на равенство этого значения (указателя) с самим собой не удовлетворяет условию «только если» в 6.5.9 / 6, и, следовательно, сравнение должно оцениваться до 0.

Но не так быстро. Кто сказал, что (struct A *) 1 не является указателем на объект? Рассмотрим определение в стандарте «объект»:

объект
область хранения данных в среде исполнения, содержимое который может представлять значения

( C 2011, 3,15 / 1 )

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

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

(не так, как мог компилятор в любом случае)?

Конечно, компилятор может и должен знать. Он должен знать, чтобы оценить выражения, которые вы представляете. Самый простой подход - и, не случайно, самый распространенный - состоит в том, чтобы интерпретировать каждое ненулевое значение указателя, которое не является представлением ловушки, как указатель на объект.

Должны ли мы просто понимать, что это реализация, так как предыдущие результаты уже были от реализации

Будучи определяемым реализацией, предъявляется требование соответствия реализаций для документирования их выбора Поведение, о котором вы спрашиваете, может следовать из определяемого реализацией поведения преобразования целого числа в указатель, но само по себе оно не определяется реализацией.

Где это указано в стандарте?

Не указывает. В принципе, соответствующие реализации могут отличаться по этому вопросу. Однако на практике они довольно последовательны.

2 голосов
/ 19 апреля 2019

Нарушение ограничения

Целое число может быть преобразовано в любой тип указателя. За исключением случаев, указанных ранее, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением ловушки. C17dr §6.3.2.3 5

С кодом (struct A *) 1 попытка преобразования. В результате определяется реализацией , может отсутствовать выравнивание , ... может быть trap .

Следующий код пытается инициализировать a ниже.

struct A * a = (struct A *) 1;

Ограничения инициализации включают в себя:

Ни один инициализатор не должен пытаться предоставить значение для объекта, не содержащегося в инициализируемом объекте. §6.7.9 2

Не определено, что (struct A *) 1 соответствует этому ограничению.

1 голос
/ 19 апреля 2019

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

Подробное обсуждение - в блоге Штефана Шульце Фрилингауса . Я цитирую из статьи Указатели более абстрактны, чем вы могли ожидать в C - здесь есть подробное объяснение, где вы можете увидеть конкретный пример, идентичный вашему, поэтому:

Обратите внимание, что указатели p и q указывают на один и тот же адрес памяти. Тем не менее выражение p == q оценивается как ложное, что на первый взгляд очень удивительно.

0 голосов
/ 19 апреля 2019

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

Это, однако, должно быть в порядке:

struct A * a = (struct A *) 1;
struct A * b = (struct A *) 1;
return (int)a == (int)b;

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

...