Насколько мне известно, в 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);
В большинстве систем они сохраняются. Завершающие биты целочисленного представления указателя будут представлять выравнивание указателя, и добавление определенного смещения к указателю аналогично добавлению смещения, умноженного на размер типа указателя, к целочисленному представлению указателя.
Однако есть ли какой-либо способ определить, в идеале во время компиляции, будут ли в какой-либо конкретной реализации эти предположения всегда справедливы для любого указателя, при условии, что арифметика указателей в любом случае создала бы действительный указатель, поскольку Стандарт не гарантирует их? Например, для раннего отказа неподдерживаемых реализаций, перехода на альтернативную реализацию и т. д.
@MikeNakis Вычитание двух указателей даст целое число, но при вычитании они все равно остаются указателями. Я не уверен в гарантии того, что сначала приведение их к целым числам, а затем их вычитание даст количество байтов, то есть количество элементов, умноженное на размер каждого элемента, разделяющего их.
ладно это, я не знаю. Я точно знаю, что на x86 это будет работать, но может быть на территории UB.
(p - q) * sizeof *p
даст количество байтов (p
, q
указывают на один и тот же объект).
@WeatherVane Да, но я считаю, что abs((uintptr_t)p - (uintptr_t)q)
не гарантирует тот же результат, верно?
Какой ответ вы здесь ищете? Как уже говорилось, ответ — нет. Стандарт C не предоставляет никакого способа сделать это, и поэтому не всегда возможно принять решение. Вам просто нужно подтверждение этого? Конкретная реализация C может предоставить эту информацию через предопределенный макрос препроцессора или другие средства. Вы ищете информацию об этом? Вы уже находитесь на неправильном пути, говоря: «В большинстве систем они будут сохраняться», потому что эти свойства формально являются особенностями реализаций C, а не систем, на которые они нацелены.
Что касается «любого способа определить… при условии, что арифметика указателя в любом случае создала бы действительный указатель»: это некорректно. Предположим, в некоторой реализации C целые числа, соответствующие указателям (то есть целые числа, полученные в результате преобразования указателей в uintptr_t
) для последовательных указателей (скажем, для последовательных элементов массива char
), не являются последовательными. Тогда (uintptr_t) p + 1
может не соответствовать ни одному указателю в массиве и, следовательно, (char *) ((uintptr_t) p + 1)
не удовлетворяет требованиям «в любом случае создал бы действительный указатель»…
… итак, вы спрашиваете, будет ли тест выполнен для недопустимого указателя, если предположить, что был создан действительный указатель. Другими словами, вы спрашиваете, равен ли x y, предполагая, что y допустим, учитывая, что y недействителен. Вопрос не имеет смысла.
@EricPostpischil Я имел в виду, что p + 1
является допустимым указателем, а не (char *)((uintptr_t)p + 1)
является допустимым указателем. Я исключаю случаи, когда p + 1
выходит за пределы допустимого или что-то в этом роде, поэтому оно было бы недействительным, даже если бы (char *)((uintptr_t)p + 1)
было действительным.
@CPlus: Как я уже писал, стандарт C не предоставляет никакого способа сделать это. Я не писал, что стандарт не гарантирует линейные указатели. Да, есть некоторые вещи, которые стандарт не гарантирует (например, использует ли int
дополнение до двух, дополнение до одного или знак и величину), но которые вы можете проверить гарантированным способом, используя вещи, полностью определенные стандартом. Линейные указатели не входят в их число: исходя из моего знания стандарта, я могу заверить вас, что стандарт не предоставляет никаких средств, с помощью которых вы могли бы надежно это проверить.
@EricPostpischil Тогда ответ на этот вопрос: «Вы не можете».
Вы не можете выполнять арифметику указателей в целочисленном константном выражении. Поэтому я считаю, что единственный способ правильно скомпилировать и протестировать это — создать другую программу и вывести константу препроцессора, с которой вы сможете сделать #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 оптимизирует точно так же (godbolt.org/z/xEEKsWKdn). Вы можете попробовать несколько комбинаций, но, честно говоря, я думаю, что вы слишком параноики. Я бы просто предположил разумное поведение, определяемое реализацией указателей как целых чисел, для сопоставлений указателей и арифметики uintptr_t<=> и даже не пытался поддерживать экзотические платформы.
Согласно обсуждению в комментариях, вопрос заключается в том, обеспечивают ли спецификации стандарта C надежный способ определить, будут ли утверждения справедливы в любой конкретной реализации C. (Таким образом, вопрос не в том, может ли реализация C предоставить какой-либо способ узнать это, например, путем предопределения макроса препроцессора, который программа могла бы протестировать. Вопрос в том, существует ли тест, который работает во всех реализациях C.)
Ответ — нет.
Поскольку этот ответ основан на том факте, что в стандарте C для этого ничего нет, нет другого способа продемонстрировать это, кроме как сослаться на весь стандарт C. Я достаточно знаком с текущим стандартом C и считаю, что он не предоставляет такого механизма.
В примечании 69 к C 2018 6.3.2.2 5 говорится: «Функции отображения для преобразования указателя в целое число или целого числа в указатель предназначены для обеспечения совместимости со структурой адресации среды выполнения», но это не является нормативной частью. стандарта, и я считаю, что в стандарте нет ничего, что говорило бы, что любая информация об этом сопоставлении должна предоставляться реализацией C. Также не существует каких-либо заявленных свойств указателей и отображений на целые числа, которые могли бы обеспечить проверку того, всегда ли выполняются утверждения.
Я думаю, что первое утверждение справедливо. Я думаю, что пока
p
указывает внутриa[]
, вы можете выполнять арифметику с указателями, и она будет отражать целочисленную арифметику. При необходимости вы можете вычесть два указателя, и результатомsize_t
будет количество элементов, разделяющих их, если бы они указывали на один и тот же массив.