Определено ли поведение вычитания двух указателей NULL?

Является ли разница двух переменных, не являющихся пустыми указателями, определенными (для C99 и /или C ++ 98), если они обе NULL ценится?

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

struct buf {
  char *buf;
  char *pwrite;
  char *pread;
} ex;

Скажем, ex.buf указывает на массив или некоторую память, выделенную из памяти. Если мой код всегда гарантирует, что pwrite и pread указать на этот массив или один за ним, тогда я вполне уверен, что ex.pwrite - ex.pread всегда будет определен. Однако что если pwrite и pread оба NULL. Могу ли я ожидать, что вычитание этих двух значений определено как (ptrdiff_t)0, или же строго совместимый код должен проверять указатели на NULL? Обратите внимание, что единственный интересующий меня случай - это когда указатели оба равны NULL (что представляет собой буфер без инициализации). Причина связана с полностью совместимой «доступной» функцией с учетом предыдущих предположений:

size_t buf_avail(const struct s_buf *b)
{     
    return b->pwrite - b->pread;
}
75 голосов | спросил John Luebs 15 22011vEurope/Moscow11bEurope/MoscowTue, 15 Nov 2011 01:11:47 +0400 2011, 01:11:47

4 ответа


0

В C99 это технически неопределенное поведение. C99 §6.5.6 говорит:

  

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

     

[...]

     

9) Когда вычтены два указателя, оба должны указывать на элементы одного и того же объекта массива,   или один за последним элементом массива объекта; Результатом является разница   индексы двух элементов массива. [...]

И §6.3.2.3 /3 гласит:

  

Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу   void *, называется константой нулевого указателя. 55) Если константа нулевого указателя преобразуется в тип указателя результирующий указатель, называемый нулевым указателем , гарантированно сравнивается с указателем на любой объект или функцию.

Так как нулевой указатель не равен ни одному объекту, он нарушает предварительные условия 6.5.6 /9, поэтому его поведение не определено. Но на практике я был бы готов поспорить, что почти каждый компилятор будет возвращать результат 0 без каких-либо побочных эффектов.

В C89 это также неопределенное поведение, хотя формулировка стандарта немного отличается.

C ++ 03, с другой стороны, имеет определенное поведение в этом случае. Стандарт делает специальное исключение для вычитания двух нулевых указателей. C ++ 03 §5.7 /7 гласит:

  

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

C ++ 11 (а также последний черновик C ++ 14, n3690) имеют идентичную формулировку C ++ 03 с незначительным изменением std::ptrdiff_t вместо ptrdiff_t.

ответил Adam Rosenfield 15 22011vEurope/Moscow11bEurope/MoscowTue, 15 Nov 2011 01:28:15 +0400 2011, 01:28:15
0

Я нашел это в стандарте C ++ (5.7 [expr.add] /7):

  

Если два указателя [...] оба равны нулю, а два указателя   вычтенный результат сравнивается равным значению 0, преобразованному в   тип std :: ptrdiff_t

Как уже говорили другие, C99 требует сложения /вычитания между двумя указателями одного и того же объекта массива. NULL не указывает на допустимый объект, поэтому вы не можете использовать его для вычитания.

ответил Pubby 15 22011vEurope/Moscow11bEurope/MoscowTue, 15 Nov 2011 01:22:41 +0400 2011, 01:22:41
0

Изменить : этот ответ действителен только для C, я не видел тег C ++, когда отвечал.

Нет, арифметика указателей разрешена только для указателей, которые указывают в пределах одного и того же объекта. Так как по определению стандартные нулевые указатели C не указывают ни на один объект, это неопределенное поведение.

(Хотя, я думаю, что любой разумный компилятор вернет только 0, но кто знает.)

ответил Jens Gustedt 15 22011vEurope/Moscow11bEurope/MoscowTue, 15 Nov 2011 01:16:31 +0400 2011, 01:16:31
0

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

В любой соответствующей реализации C и почти во всех (если не во всех) реализациях C-подобных диалектов для любого указателя p такой, что либо *p, либо *(p-1) идентифицирует некоторый объект:

  • Для любого целочисленного значения z, равного нулю, значения указателя (p+z) и (p-z) будут во всех отношениях эквивалентны p, за исключением того, что они будут постоянными, только если оба p и z являются постоянными.
  • Для любого q, что эквивалентно p, выражения p-q и q-p оба будут давать ноль.

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

Если бы была реализация, в которой затраты на поддержание гарантий были бы велики, но немногие, если какие-либо программы получили бы от них какую-либо выгоду, имело бы смысл позволить ей перехватывать вычисления «ноль + ноль» и требовать, чтобы Пользовательский код для такой реализации включает в себя ручные проверки на нуль, которые гарантии могли бы сделать ненужными. Ожидается, что такой резерв не повлияет на другие 99,44% реализаций, где стоимость поддержки гарантий будет превышать стоимость. Такие реализации должны поддерживать такие гарантии, но их авторам не нужно, чтобы авторы Стандарта сообщали им об этом.

Авторы C ++ решили, что соответствующие реализации должны поддерживать вышеуказанные гарантии любой ценой, даже на платформах, где они могут существенно снизить производительность арифметики с указателями. Они посчитали, что стоимость гарантий даже на платформах, где их было бы дорого поддерживать, превысила бы стоимость. На такое отношение могло повлиять желание относиться к C ++ как к языку более высокого уровня, чем к C. Можно ожидать, что программист AC узнает, когда конкретная целевая платформа будет обрабатывать случаи, подобные (ноль + ноль), необычным образом, но программисты C ++ не ожидалось, что они будут заниматься такими вещами. Таким образом, гарантирование последовательной поведенческой модели было признано оправданным.

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

void out_characters(unsigned char *p, int n)
{
  unsigned char *end = p+n;
  while(p < end)
    out_byte(*p++);
}

старые компиляторы генерировали бы код, который не мог бы надежно ничего выводить, с без побочных эффектов, если p == NULL и n == 0, без особого случая n == 0. На новые компиляторы, однако, придется добавить дополнительный код:

void out_characters(unsigned char *p, int n)
{
  if (n)
  {
    unsigned char *end = p+n;
    while(p < end)
      out_byte(*p++);
  }
}

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

ответил supercat 12 AMpTue, 12 Apr 2016 10:25:18 +030025Tuesday 2016, 10:25:18

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132