Подтвердить что ты не робот

Почему эта арифметика указателя не разрешена в C?

char arr[] = "Hello";
arr = arr + 1;  // error occurs

Насколько я знаю, выражение, которое имеет тип массива, преобразуется в тип указателя, который указывает на начальный элемент массива. Поэтому я ожидал, что arr = arr + 1 (указатель на первый элемент (arr) массива станет указателем на второй элемент массива) для работы. Почему это не работает в C?

4b9b3361

Ответ 1

arr + 1 действительно является указателем на второй элемент массива (т.е. &arr[1]).

Однако это не означает, что вы можете как-то записать это значение указателя обратно в arr. Вы не можете сделать это по крайней мере по двум причинам.

Во-первых, arr - это массив элементов char, а не указатель. Там очевидное несоответствие типов.

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

Наконец, если мы просто проигнорируем более глубокие тонкости и сосредоточимся на том, что формально происходит на верхнем уровне, из-за распад типа массива ваше выражение эквивалентно

(char *) arr = (char *) arr + 1;

Назначение невозможно, так как левая часть является результатом преобразования [неявного] типа. В преобразованиях типа C всегда производятся значения r. Вы не можете назначать значения r.

Другими словами, это не "арифметика указателей", которая здесь запрещена. Арифметика указателя в порядке. Это то, что вы делаете с результатом этой арифметики указателя, которая вызывает ошибку.

Ответ 2

Массивы не изменяются lvalues. Они не могут быть левым операндом оператора присваивания.

C11-§6.3.2.1:

Модифицируемое значение l является значением l, которое не имеет типа массива, не имеет неполного типа, [...]

§6.5.16/2:

Оператор присваивания должен иметь модифицируемое значение lval в качестве его левого операнда.

В заявлении

arr = arr + 1; 

arr является левым операндом оператора = и имеет тип массива. Его нельзя изменить.
Таким образом, это не арифметика указателя, а ограничение языка на операторе присваивания, которое является причиной синтаксической ошибки.


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

char *ptr = &arr[0] + 1 => &(*(arr + 0)) + 1 => &(*arr) + 1 => arr + 1 // * and & nullify each other 

Ответ 3

Это потому, что массивы похожи на указатели, за исключением того, что они не могут быть изменены. Однако вы можете изменить указатель, указывающий на массив. В приведенном выше примере вы можете сделать следующее:

char arr[]="Hello";
char *ptr=arr;
ptr=ptr+1;

Изначально указатель ptr будет указывать на первый символ массива i.e. 'H' и после изменения значения он будет указывать на второй символ, т.е. 'e'. Вы также можете сделать следующее:

char arr[]="Hello";
char *ptr=arr;
ptr=arr+1;

Оба производят тот же эффект, который показывает, что arr+1 действительно является арифметикой указателя. Однако вы не можете изменить значение arr, потому что его тип - это массив символов, а не указатель на массив символов.

Ответ 4

Насколько я знаю, выражение, которое имеет тип массива, преобразуется в тип указателя, который указывает на элемент inital массива.

Это верно в большинстве контекстов. Это неверно в следующих контекстах:

  • При использовании оператора addressof (&arr). Тип &arr - char (*)[6]. Это не char**.

  • При использовании оператора sizeof. sizeof(arr) 6. Если бы это был указатель, это был бы размер указателя (4 или 8 на большинстве распространенных платформ).

  • При использовании в качестве LHS оператора присваивания. Переменная типа массива не модифицируется.

Поэтому я ожидал, что arr = arr + 1 (указатель на первый элемент (arr) массива станет указателем на второй элемент массива) для работы. Почему эта работа не работает в C?

RHS выражения вычисляет указатель на второй элемент arr. Однако эта линия не работает из-за (3) выше. arr не является изменяемым значением. Он не может использоваться как LHS оператора присваивания.

Ответ 5

char[] не является указателем, а char* - указателем. Это работает, но это неправильное решение:

int main()
{
    char *arr = "Hello";
    arr = arr + 1;  // Wrong!
    printf("%s\n", arr); // Output: ello
}

Если arr выделено в виде кучи с malloc, вы можете получить утечку памяти, если free память запустится arr+1 not arr.

Но вы можете сделать что-то вроде этого:

int main()
{
    char arr[] = "Hello";
    char *wordFromSecondLetter = &arr[0] + 1;
    printf("%s\n", wordFromSecondLetter); // Output: ello
}

Или как это

int main()
{
    char arr[] = "Hello";
    printf("%s\n", &arr[1]); // Output: ello
}

Ответ 6

Потому что arr не является указателем, а массивом char. Вы можете проверить это, установив sizeof arr. Чтобы получить указатель на char, вы должны использовать char *arr = "Hello";.

Самая большая разница между указателем и массивом заключается в том, что вы можете напрямую назначить значение указателю, но вы не можете сделать это с массивом.

На самом деле, когда вы пишете arr + 1, arr "распадается" на указатель на его первый элемент, то есть arr == &arr[0]. Итак, arr + 1 - это арифметика с легальным указателем, но ее значение arr, которое является массивом, является незаконным.