Есть ли способ определить, является ли представление указателя «линейным» в C?

Насколько мне известно, в C нет никакой гарантии, что поведение арифметики указателей отражает поведение целочисленной арифметики, если указатель преобразуется в целое число. Другими словами, нет никакой гарантии, что любое из следующих утверждений будет выполнено:

char a[2]; // So that p + 1 is a valid memory location
char *p = a;
assert(p + 1 == (char *)((uintptr_t)p + 1));

int x;
int *p = &x;
assert((intptr_t)p % sizeof(int) == 0);

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

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

Я думаю, что первое утверждение справедливо. Я думаю, что пока p указывает внутри a[], вы можете выполнять арифметику с указателями, и она будет отражать целочисленную арифметику. При необходимости вы можете вычесть два указателя, и результатом size_t будет количество элементов, разделяющих их, если бы они указывали на один и тот же массив.

Mike Nakis 02.09.2024 23:30

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

CPlus 02.09.2024 23:33

ладно это, я не знаю. Я точно знаю, что на x86 это будет работать, но может быть на территории UB.

Mike Nakis 02.09.2024 23:35
(p - q) * sizeof *p даст количество байтов (p, q указывают на один и тот же объект).
Weather Vane 03.09.2024 00:07

@WeatherVane Да, но я считаю, что abs((uintptr_t)p - (uintptr_t)q) не гарантирует тот же результат, верно?

CPlus 03.09.2024 00:09

Какой ответ вы здесь ищете? Как уже говорилось, ответ — нет. Стандарт C не предоставляет никакого способа сделать это, и поэтому не всегда возможно принять решение. Вам просто нужно подтверждение этого? Конкретная реализация C может предоставить эту информацию через предопределенный макрос препроцессора или другие средства. Вы ищете информацию об этом? Вы уже находитесь на неправильном пути, говоря: «В большинстве систем они будут сохраняться», потому что эти свойства формально являются особенностями реализаций C, а не систем, на которые они нацелены.

Eric Postpischil 03.09.2024 01:13

Что касается «любого способа определить… при условии, что арифметика указателя в любом случае создала бы действительный указатель»: это некорректно. Предположим, в некоторой реализации C целые числа, соответствующие указателям (то есть целые числа, полученные в результате преобразования указателей в uintptr_t) для последовательных указателей (скажем, для последовательных элементов массива char), не являются последовательными. Тогда (uintptr_t) p + 1 может не соответствовать ни одному указателю в массиве и, следовательно, (char *) ((uintptr_t) p + 1) не удовлетворяет требованиям «в любом случае создал бы действительный указатель»…

Eric Postpischil 03.09.2024 01:17

… итак, вы спрашиваете, будет ли тест выполнен для недопустимого указателя, если предположить, что был создан действительный указатель. Другими словами, вы спрашиваете, равен ли x y, предполагая, что y допустим, учитывая, что y недействителен. Вопрос не имеет смысла.

Eric Postpischil 03.09.2024 01:17

@EricPostpischil Я имел в виду, что p + 1 является допустимым указателем, а не (char *)((uintptr_t)p + 1) является допустимым указателем. Я исключаю случаи, когда p + 1 выходит за пределы допустимого или что-то в этом роде, поэтому оно было бы недействительным, даже если бы (char *)((uintptr_t)p + 1) было действительным.

CPlus 03.09.2024 01:19

@CPlus: Как я уже писал, стандарт C не предоставляет никакого способа сделать это. Я не писал, что стандарт не гарантирует линейные указатели. Да, есть некоторые вещи, которые стандарт не гарантирует (например, использует ли int дополнение до двух, дополнение до одного или знак и величину), но которые вы можете проверить гарантированным способом, используя вещи, полностью определенные стандартом. Линейные указатели не входят в их число: исходя из моего знания стандарта, я могу заверить вас, что стандарт не предоставляет никаких средств, с помощью которых вы могли бы надежно это проверить.

Eric Postpischil 03.09.2024 01:20

@EricPostpischil Тогда ответ на этот вопрос: «Вы не можете».

CPlus 03.09.2024 01:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
11
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы не можете выполнять арифметику указателей в целочисленном константном выражении. Поэтому я считаю, что единственный способ правильно скомпилировать и протестировать это — создать другую программу и вывести константу препроцессора, с которой вы сможете сделать #if.

Однако, возможно, в этом нет необходимости. Де-факто выражение времени компиляции может подойти. Как для gcc, так и для clang выполните тест, например:

char a[2];
if (a + 1 != (char *)((uintptr_t)a + 1)) abort();

оптимизируется (не на clang с присвоением char *p = a;), т. е. тест рассматривается как де-факто константа времени компиляции (https://godbolt.org/z/z85a9W3E8). Возможно, вам будет достаточно положиться на эту оптимизацию.

Чтобы проверить любой указатель, вам следует протестировать типы указателей, которым разрешено иметь разные представления. Собственные типы указателей могут. Указатели на структуру/объединение могут быть нет. Или, может быть, не тестируйте и не объявляйте платформы, на которых это не работает, не заслуживающими поддержки.

Но a + 1 == (char *)((uintptr_t)a + 1) гарантирует a + 2 == (char *)((uintptr_t)a + 2).

CPlus 03.09.2024 00:01

@CPlus оптимизирует точно так же (godbolt.org/z/xEEKsWKdn). Вы можете попробовать несколько комбинаций, но, честно говоря, я думаю, что вы слишком параноики. Я бы просто предположил разумное поведение, определяемое реализацией указателей как целых чисел, для сопоставлений указателей и арифметики uintptr_t<=> и даже не пытался поддерживать экзотические платформы.

Petr Skocik 03.09.2024 00:20
Ответ принят как подходящий

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

Ответ — нет.

Поскольку этот ответ основан на том факте, что в стандарте C для этого ничего нет, нет другого способа продемонстрировать это, кроме как сослаться на весь стандарт C. Я достаточно знаком с текущим стандартом C и считаю, что он не предоставляет такого механизма.

В примечании 69 к C 2018 6.3.2.2 5 говорится: «Функции отображения для преобразования указателя в целое число или целого числа в указатель предназначены для обеспечения совместимости со структурой адресации среды выполнения», но это не является нормативной частью. стандарта, и я считаю, что в стандарте нет ничего, что говорило бы, что любая информация об этом сопоставлении должна предоставляться реализацией C. Также не существует каких-либо заявленных свойств указателей и отображений на целые числа, которые могли бы обеспечить проверку того, всегда ли выполняются утверждения.

Другие вопросы по теме