c++ - с11 - стандарт c99 на русском




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

В 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 .

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

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

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

Скажем, ex.buf указывает на массив или некоторую память malloc'ed. Если мой код всегда гарантирует, что 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;
}

Стандарт 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 «не может быть пустым», любые последующие проверки нулевого указателя могут быть опущены, что приводит к тому, что код разбивается на пятно, не связанное с реальной «проблемой».


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

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

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





c89