Доступ к массивам по индексу[массив] в C и C++

Есть небольшой вопрос с подвохом, который некоторые интервьюеры любят задавать по любой причине:

int arr[] = {1, 2, 3};
2[arr] = 5; // does this line compile?
assert(arr[2] == 5); // does this assertion fail?

Насколько я понимаю, a[b] преобразуется в *(a + b), и, поскольку сложение является коммутативным, их порядок на самом деле не имеет значения, поэтому 2[a] на самом деле *(2 + a), и это работает нормально.

Гарантировано ли, что это работает по спецификациям C и/или C++?


person NullUserException    schedule 22.02.2011    source источник
comment
Об этом упоминалось в разделе Скрытые функции C++.   -  person erickb    schedule 22.02.2011
comment
@erickb - в этом вопросе конкретно спрашивается, является ли это (чрезвычайно популярной) случайностью реализации или это предусмотрено стандартом.   -  person Chris Lutz    schedule 22.02.2011
comment
В этом конкретном случае вы получите сообщение об ошибке о том, что a не определено (поскольку ваше объявление объявляет arr, а не a), но я думаю, что все понимают, о чем вы на самом деле спрашиваете...   -  person Chris Dodd    schedule 22.02.2011
comment
Вы даже можете сойти с ума со строковыми указателями. abcd[1] — это «б». Или для вас 1[abcd].   -  person Graham Perks    schedule 22.02.2011


Ответы (2)


Да. 6.5.2.1 пункт 1 (стандарт C99) описывает аргументы оператора []:

Одно из выражений должно иметь тип "указатель на объект type", другое выражение должно иметь целочисленный тип, а результат - тип "type".

6.5.2.1 пункт 2 (курсив наш):

Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексным обозначением элемента объекта массива. Определение оператора нижнего индекса [] состоит в том, что E1[E2] идентичен (*((E1)+(E2))). Из-за правил преобразования, которые применяются к двоичному оператору +, если E1 является объектом массива (эквивалентно, указателем на начальный элемент объекта массива), а E2 является целым числом, E1[E2] обозначает E2-й элемент E1 (считая с нуля).

Он ничего не говорит о том, что порядок аргументов до [] должен быть разумным.

person Chris Lutz    schedule 22.02.2011
comment
@Chris - Как это распространяется на многомерные массивы? Можете ли вы выполнять те же трюки, чтобы сбить людей с толку? (Или мне следует задать новый вопрос из-за основных поворотов строк и столбцов?) - person jww; 03.10.2014
comment
@jww - Не совсем так. Вы могли бы сделать 3[x][2], но не могли бы сделать 3[2][x] - это было бы эквивалентно *(*(3 + 2) + x), и вы бы разыменовали integer 5, что почти наверняка плохо. - person Chris Lutz; 03.10.2014
comment
Ну, он называет операнды E1 и E2, а затем говорит только о том, что E1 является массивом. Что в моей книге устанавливает порядок аргументов. - person Peter - Reinstate Monica; 20.12.2020

В общем, 2[a] идентично a[2], и это гарантированно эквивалентно как в C, так и в C++ (при условии отсутствия перегрузки оператора), потому что, как вы имели в виду, оно преобразуется в *(2+a) или *(a+2) соответственно. Поскольку оператор «плюс» коммутативен, эти две формы эквивалентны.

Хотя формы эквивалентны, пожалуйста, ради всего святого (и будущих программистов сопровождения), предпочтите форму «a[2]» другой.

P.S. Если вас спросят об этом на собеседовании, пожалуйста, отомстите от имени сообщества C/C++ и убедитесь, что вы попросите интервьюера перечислить все последовательности триграфов в качестве предварительного условия для вас. Ваш ответ. Возможно, это разубедит его/ее в будущем задавать такие (бесполезные, с точки зрения собственно программирования что-либо) вопросы. В том странном случае, если интервьюер на самом деле знает все девять последовательностей триграфов, вы всегда можете сделать еще одну попытку задавить их вопросом о порядке уничтожения виртуальных базовых классов — вопрос, столь же ошеломляюще неуместный для повседневного программирования.

person Michael Goldshteyn    schedule 22.02.2011
comment
+1, но в С++ вы можете использовать #define ARRAY_SIZE(a) (sizeof(a) / sizeof(0[a])) в качестве макроса, чтобы найти размер массива, который не будет (и не может быть) работать для std::vector и типов, которые перегружают оператор [], что не может предотвратить int *a = /*something*/; ARRAY_SIZE(a), но может быть довольно безопасно для всего остального. (Это единственное полезное использование 0[a], которое я когда-либо видел) - person Chris Lutz; 22.02.2011
comment
@Chris, я добавил дополнение о перегрузке оператора. Спасибо за ваш вклад. - person Michael Goldshteyn; 22.02.2011
comment
В C++ вы также можете template<typename T, size_t N> size_t ARRAYSIZE(T (&a)[N]) { return N; }, что действительно предотвращает int *a = /*something*/; ARRAYSIZE(a). - person Logan Capaldo; 22.02.2011
comment
@Logan Capaldo - я не говорил, что это соответствующее использование, просто это было правильное использование. :P Могут быть другие контексты, в которых вы хотите явно избежать вызова потенциального класса operator[] и работать только с переменными типов T* и T[], хотя это быстро сворачивает в царство надуманных примеров. - person Chris Lutz; 22.02.2011
comment
@ Крис, я просто указал на другие варианты на случай, если кто-то сбежит изменить все свои countof макросы;). Преимущество вашего заключается в том, что вы работаете на C и C++ без изменений и получаете дополнительную функциональность на C++. - person Logan Capaldo; 22.02.2011
comment
@Logan - я обычно выступаю против совместимости C / C ++, поэтому я фактически забыл, что именно по этой причине кто-то предложил эту версию макроса. - person Chris Lutz; 22.02.2011
comment
@ Майкл, позвольте мне сказать вам, что это был один из вопросов, заданных в письменном тесте Microsoft в нашем институте. - person Algorithmist; 22.02.2011