Is & arr [размер] действителен?

Скажем, у меня есть функция, называемая так:

void mysort(int *arr, std::size_t size)
{
  std::sort(&arr[0], &arr[size]);
}

int main()
{
  int a[] = { 42, 314 };
  mysort(a, 2);
}

Мой вопрос: имеет ли код mysort (точнее, &arr[size]) поведение?

Я знаю, что это было бы совершенно верно, если заменить на arr + size; Арифметика указателя позволяет указывать в конце концов. Однако мой вопрос касается использования & и [].

Per С++ 11 5.2.1/1, arr[size] эквивалентно *(arr + size).

Цитата 5.3.1/1, правила для унарного *:

Унарный оператор * выполняет косвенное обращение: выражение, к которому оно применяется, должно быть указателем для тип объекта или указатель на тип функции, а результат - это lvalue, относящийся к объекту или функции , на которое указывает выражение. Если тип выражения является "указателем на T", то тип результата будет "T". [Примечание: указатель на неполный тип (кроме cv void) может быть разыменован. Полученная таким образом lvalue может использоваться ограниченным образом (например, для инициализации ссылки); это значение не должно быть преобразовано в prvalue, см. 4.1. -end note]

Наконец, 5.3.1/3, дающий правила для &:

Результат унарного оператора & является указателем на его операнд. Операнд должен быть lvalue... если тип выражение T, результат имеет тип "указатель на T" и является prvalue, который является адресом указанного объекта (1.7) или указателем на назначенную функцию.

(Акцент и эллипсы мои).

Я не могу решить об этом. Я точно знаю, что преобразование lvalue-to-rvalue на arr[size] будет Undefined. Но такое преобразование не происходит в коде. arr + size не указывает на объект; но в то время как вышеприведенные параграфы говорят об объектах, они, похоже, явно не указывают на необходимость существования объекта в этом месте (в отличие от преобразования lvalue-to-rvalue в 4.1/1).

Итак, questio: есть mysort, как он называется, действителен или нет?

(Обратите внимание, что я цитирую С++ 11 выше, но если это будет рассмотрено более подробно в более позднем стандарте/черновике, я был бы в восторге от этого).

Ответ 1

Недействительно. Вы вычеркнули "результат - это lvalue, относящийся к объекту или функции, на которые выражение указывает" в вашем вопросе. Это точно проблема. array + size - допустимое значение указателя, которое не указывает на объект. Поэтому ваша цитата о *(array + size) не указывает, к какому относится результат, и это значит, что для &*(array + size) нет требования предоставить такое же значение, как array + size.

В C это считалось дефектом и исправлено так, что спецификация теперь говорит в &*ptr, ни оценка &, ни * не оценивается. С++ еще не получил фиксированной формулировки. Это тема очень старого, все еще активного DR: DR # 232. Цель состоит в том, что она действительна, как и в C, но стандарт не говорит об этом.

Ответ 2

В контексте обычных массивов С++ да. Законно формировать адрес элемента, прошедшего через конец массива. Нельзя читать или писать, на что он указывает, однако (в конце концов, там нет никакого элемента). Поэтому, когда вы выполняете &arr[size], arr[size] формирует то, что вы можете считать ссылкой на элемент "один конец", но еще не пыталось получить доступ к этому элементу. Затем & получает адрес этого элемента. Поскольку ничто не пыталось фактически следовать этому указателю, ничего плохого не произошло.

Это не случайно, так что указатели на массивы ведут себя как итераторы. Таким образом, &a[0] по существу .begin() на массиве, а &a[size] (где size - количество элементов в массиве) по существу .end(). (См. Также std::array, где это становится более явным)

Изменить: Эмм, мне, возможно, придется отменить этот ответ. Хотя это, вероятно, применимо в большинстве случаев, если тип, хранящийся в массиве, имеет переопределенный operator&, тогда, когда вы выполняете &a[size], метод operator& может попытаться получить доступ к членам экземпляра типа в a[size] , где нет экземпляра.

Ответ 3

Предполагая, что size - это фактический размер массива, вы передаете указатель на прошлый элемент на std::sort().

Итак, насколько я понимаю, вопрос сводится к следующему: этот указатель эквивалентен arr.end()?

Существует мало сомнений в том, что это верно для каждого существующего компилятора, поскольку итераторы массива действительно простые старые указатели, поэтому &arr[size] является очевидным выбором для arr.end().

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

Итак, ради аргумента, вы могли бы представить себе компилятор, использующий бит "прошлого конца" в дополнение к фактическому адресу для реализации итераторов старых старых массивов внутри себя и извращенно рисовать ваши усы розовыми, если обнаружил какую-либо приемлемую несогласованность между итераторы и адреса, полученные с помощью арифметики указателей. Этот причудливый компилятор приведет к сбою большого количества существующего кода на С++, фактически не нарушив спецификацию, которая может стоить усилий для ее проектирования...

Ответ 4

Если мы допустим, что arr[i] является просто сокращением для *(arr + i), мы можем переписать &arr[size] как &*(arr + size). Следовательно, мы разыскиваем указатель, указывающий на элемент, прошедший мимо конца, что приводит к поведению undefined. Как вы правильно говорите, arr + size вместо этого будет легальным, потому что не выполняется операция разыменования.

Кстати, это также представлено как опрос в Степанов notes (стр. 11).

Ответ 5

Это отлично и четко определено, если размер не больше размера фактического массива (в единицах элементов массива).

Итак, если main(), называемый mysort (a, 100), & arr [size], уже будет undefined (но, скорее всего, не обнаружено, но std:: sort, очевидно, тоже будет не так плохо).