Когда точно определяется указатель?

У меня вопрос о различиях указателей и результирующего типа, ptrdiff_t.

C99 §6.5.6 (9) гласит:

Когда два указателя вычитаются, оба должны указывать на элементы одного и того же объекта массива или один за последним элементом объекта массива; результатом является разность индексов двух элементов массива. Размер результата определяется реализацией, а его тип (целочисленный тип со знаком) ptrdiff_t, определенный в заголовке. Если результат не представлен в объекте такого типа, поведение undefined. Другими словами, если выражения P и Q указывают соответственно i-ый и j-ый элементы объекта массива, выражение (P) - (Q) имеет значение i-j, если значение вписывается в объект типа ptrdiff_t.

В § 7.18.3 (2) требуется ptrdiff_t иметь диапазон, по меньшей мере, [-65535, +65535]

Меня интересует поведение undefined, если результат слишком велик. Я не мог найти ничего в стандарте, гарантируя, по крайней мере, тот же диапазон, что и подписанная версия size_t или что-то подобное. Итак, теперь вот мой вопрос: может ли соответствующая реализация сделать ptrdiff_t подписанный 16-разрядный тип, но size_t 64 бит? Как указано в Guntram Blohm, 16-разрядная подписка составляет максимум 32767, поэтому мой пример явно не соответствует] Насколько я вижу, я не могу вычитание указателей на массивы с более чем 65535 элементами в строго соответствующем коде, даже если реализация поддерживает объекты, намного большие, чем это. Кроме того, программа может даже сбой.

Обоснование (V5.10) § 6.5.6 гласит:

Важно, чтобы этот тип [ ptrdiff_t] был подписан, чтобы получить правильное алгебраическое упорядочение при работе с указателями внутри одного и того же массива. Однако величина разницы указателей может быть такой же, как размер самого большого объекта, который может быть объявлен; и поскольку это неподписанный тип, разница между двумя указателями может привести к переполнению некоторых реализаций.

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

Чтобы проиллюстрировать, предположим, что T - это любой тип объекта и n объект size_t, неизвестный во время компиляции. Я хочу выделить пространство для n объектов T, и я хочу сделать вычитание указателя с адресами в выделенном диапазоне.

size_t half = sizeof(T)>1 ? 1 : 2; // (*)
if( SIZE_MAX/half/sizeof(T)<n ) /* do some error handling */;
size_t size = n * sizeof(T);
T *foo = malloc(size);
if(!foo) /* ... */;

не будет строго соответствовать, я должен был сделать

if( SIZE_MAX/sizeof(T) < n || PTRDIFF_MAX < n )

вместо этого. Неужели это так? И если да, то кто-нибудь знает причину этого (т.е. Не требует PTRDIFF_MAX >= SIZE_MAX/2 [изменить: изменено > на >=] или что-то подобное)?

(*) Полуфабрикат в первой версии - это то, что я узнал, когда писал этот текст, у меня был

if( SIZE_MAX/2/sizeof(T) < n )

сначала, взяв половину SIZE_MAX для решения проблем, упомянутых в Обосновании; но тогда я понял, что нам нужно всего лишь половину SIZE_MAX, если sizeof(T) равно 1. Учитывая этот код, вторая версия (ту, которая, безусловно, строго соответствует), кажется, не так уж плоха. Но тем не менее, меня интересует, насколько я прав.

C11 содержит формулировку пунктов 6.5.6 (9), ответы на эту тему, связанные с С++, также приветствуются.

Ответ 1

Чтобы дать вам ответ на вопрос в названии: сама разница указателей не может использоваться для определения разницы двух указателей, не приводя при этом к поведению undefined. Это будет серьезное ограничение, как вы заметили, на системах, где PTRDIFF_MAX намного меньше возможного размера объекта. Но такие системы редки (я не знаю никого), поэтому, если у вас будет код, который зависит от возможности разделить большие объекты, вы всегда ставите что-то вроде

#if PTRDIFF_MAX < SIZE_MAX/2
# error "we need an architecture with sufficiently wide ptrdiff_t"
#endif

Но даже в таком случае (слишком узкий ptrdiff_t) вы всегда сможете вычислить разницу между двумя указателями одного и того же более крупного объекта.

  • Определите, какая из двух (p или q) меньше. Это всегда хорошо определен.
  • Скажите p меньше, затем проверьте все p + i на size_t i начиная с 1 до достижения q или i SIZE_MAX.
  • Если окончательный i равен SIZE_MAX, и вы не достигли q, разница не представима. В противном случае i плюс конечный знак - это информация, которую вы ищете.

Это не совсем удовлетворительно, но я не смог понять, как улучшить этот линейный алгоритм, чтобы что-то логарифмическое: чтобы избежать UB, нам не удавалось выйти за пределы q при сравнении.

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

Edit:

С трюком mafso для получения наиболее значимого разряда разницы указателей это можно сделать в O(log(n)), где n - требуемое расстояние. Сначала объявите две внутренние функции, которые предполагают, что p < q

// computes the maximum value bit of the pointer difference
//
// assumes that p < q
inline
uintmax_t ptrdiff_maxbit(char const* p, char const* q) {
  uintmax_t len = 1;
  while (p+len <= q-len)
    len <<= 1;
  return len;
}

// compute the pointer difference
//
// assumes that p < q
// assumes that len2 is a power of two
// assumes that the difference is strictly less than 2*len2
inline
uintmax_t ptrdiff_bounded(char const* p, char const* q, uintmax_t len2) {
  if (len2 < 2) return len2;
  uintmax_t len = (len2 >> 1);
  p += len;
  q -= len;
  for (; len; len >>= 1)
    if (p + len <= q) {
      len2 |= len;
      p += len;
    }
  return len2;
}

Затем определите функцию, которая вычисляет разницу в байтах, и добавляет соглашение в случае, если разница не представляется в intmax_t:

inline
intmax_t ptrdiff_byte(void const* p0, void const* q0) {
  char const * p = p0;
  char const * q = q0;
  if (p == q) return 0;
  if (p < q) {
    uintmax_t ret = ptrdiff_bounded(p, q, ptrdiff_maxbit(p, q));
    if (ret > (-(INTMAX_MIN+1))+UINTMAX_C(1)) return INTMAX_MIN;
    else return -ret;
  } else {
    uintmax_t ret = ptrdiff_bounded(q, p, ptrdiff_maxbit(q, p));
    if (ret > INTMAX_MAX) return INTMAX_MAX;
    else return ret;
  }
}

Наконец, макрос, который подходит ему с типом *p.

#define ptrdiff(P, Q) (ptrdiff_byte((P), (Q))/(intmax_t)sizeof(*Q))

Ответ 2

Помню, в прежние времена некоторые 16-битные 80х86 компиляторы имели "большие" или "огромные" данные, где указатели имели 32 бит, но целые числа все еще имели только 16 бит. Эти компиляторы позволили вам создавать массивы размером более 65536 байт, но с целыми числами всего 16 бит, обращаясь к элементам, которые не были в первой 64K необходимой манипуляции с указателем (что было странно, указатель состоял из 16-битного сегмента значение и значение смещения 16 бит, причем реальный адрес ((сегмент < 4) + смещение))

Я не знаю, насколько совместимы эти компиляторы, но они должны были бы определить SIZE_MAX на что-то вроде 1M (так как это самый большой объект, который вы можете адресовать, учитывая странную модель указателя), но ptrdiff_t был бы 16 бит целое число (которое не будет соответствовать, поскольку диапазон составляет только от -32768 до +32767).

Итак, разумная реализация C на разумном оборудовании не будет иметь никаких причин, чтобы PTRDIFF_MAX был меньше SIZE_MAX. Но может быть экзотическое оборудование (которое в случае 80x86 в то время не было действительно экзотичным), что позволяет вам определять большие массивы, но не позволяет вам получить доступ ко всем им "сразу". В этом случае PTRDIFF_MAX может быть ниже SIZE_MAX/2.