@dandropov95

Почему нельзя применять инкремент к имени массива?

#include <stdio.h>
#include <math.h>

#define SIZE 8

int main(void)
{
	int arr[SIZE];

	for (int i = 0; i < SIZE; i++)
		arr[i] = pow(2, i);

	for (int i = SIZE - 1; i >= 0; i--)
	    printf("%d ", *(arr + i));

	return 0;
}


Почему нельзя записать так: *(arr++)?
И почему можно применять инкремент/декремент к имени, если выделить память в куче?
int * arr = malloc(sizeof (int) * 8);
// some code
printf("%d", *(arr++)); // success
  • Вопрос задан
  • 1524 просмотра
Пригласить эксперта
Ответы на вопрос 2
"Working Draft, Standard for Programming Language C++" (N4713)
#11.3.4 Note 7 (p.186) "Objects of array types cannot be modified"
Ответ написан
Комментировать
@Mace_UA
Потому что это бессмысленно. Как и нельзя применить инкремент к объекту типа std::vector. Неважно, где выделена память -- важно, к какому объекту вы пытаетесь применить эту операцию.
В случае с массивами инкремент не имеет смысла, т.к. неясно, что должно произойти с самим массивом после этого (ведь инкремент -- операция, модифицирующая свой аргумент, в отличие от, например, оператора +).
Инкрементить можно итератор/указатель на какой-то элемент этого массива, чтобы получить итератор/указатель на следующий элемент.

Важно понимать, что массивы не являются указателями. Это принципиально разные сущности.
Массив содержит в себе все свои элементы и охватывает весь диапазон (нельзя заставить массив уже после создания начать охватывать только часть своего диапазона).
Указатель же только ссылается на память, принадлежащую кому-то другому (возможно, какому-нибудь массиву).

Хотя массивы и умеют неявно приводиться к указателям, и во многих случаях это делают автоматически.
Например, при применении оператора + к массиву.

Оператор + (в общем случае) не изменяет своих операндов, а создаёт новый объект, поэтому с ним такой неоднозначности не будет.
В выражении arr+1 (где arr -- массив) компилятор сначала как бы попытается применить оператор + к массиву и целому числу, у него не получится (т.к. к массиву самому по себе что-то прибавлять нельзя -- опять же, это бессмысленная/неоднозначная операция). Но затем он увидит, что у этого массива есть оператор неявной конвертации к чему-то, что можно использовать в таком контексте: указателю. И поэтому воспользуется данным приведением, сконвертирует arr к новому указателю, и применит +1 уже к этому указателю, в результате вернув указатель на следующий элемент.
Принципиальное отличие в том, что в данном примере сам массив никто не пытается модифицировать.

Вы можете спросить: почему тогда в случае с ++arr компилятор не провернёт ту же фишку, приведя массив к указателю и применив инкремент к этому указателю?
Фишка в том, что сам массив от этого не изменится (т.к. менялся бы только временный объект-указатель), и в результате вы бы не получили ничего. Вот совсем ничего. А это вряд ли то, что вы ожидаете. Потому что этот временный проинкрементированный указатель после завершения операции был бы тут же уничтожен -- на оригинальном массиве это бы никак не сказалось. Операция оказалась полностью бессмысленной.

Поэтому для встроенных типов такие операции над prvalue (временными безымянными объектами, не привязанными к переменной) запрещены. Здесь уже речь не идёт конкретно о массивах.
Это то же самое, что написать ++(1+2). Не скомпилится. Так как выражение (1+2) вернёт временный объект типа int, не привязанный ни к какой именованной переменной, и его инкрементировать попросту бессмысленно.

Так что если вы хотите использовать инкремент с массивом, предполагая, что эта операция должна осуществлять переход к следующему элементу -- просто создайте именованную переменную-указатель через описанное выше неявное приведение, и инкрементируйте уже этот указатель.
В таком случае всё будет в порядке, поскольку 1) данный указатель будет новым объектом, и его изменение само по себе никак не повлияет на сам массив; 2) инкремент указателя -- операция понятная и однозначная:

int arr[42];
//++arr; // error!
int* ptr = arr; // array-to-pointer decay
++ptr; // ok!


То же самое можно сказать и о динамических массивах, не являющихся указателями. В C++ их роль чаще всего играют экземпляры шаблона std::vector:

std::vector<int> arr(42);
//++arr; // error!
int* ptr = arr.data();
++ptr; // ok!
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы