С массивами, почему это так, [5] == 5 [a]?

Как указывает Джоэл в подкасте # 34, в Язык программирования C (aka: K и R), упоминается это свойство массивов в C: a[5] == 5[a]

Джоэл говорит, что это из-за арифметики указателя, но я до сих пор не понимаю. Почему a[5] == 5[a]?

1440
задан Dinah 19 дек. '08 в 20:01
источник поделиться
18 ответов

Стандарт C определяет оператор [] следующим образом:

a[b] == *(a + b)

Поэтому a[5] будет оценивать:

*(a + 5)

и 5[a] будут оценивать:

*(5 + a)

a является указателем на первый элемент массива. a[5] - это значение, которое 5 элементов дальше от a, что совпадает с *(a + 5), а из математики начальной школы мы знаем, что они равны (добавление commutative).

1716
ответ дан Mehrdad Afshari 19 дек. '08 в 20:04
источник поделиться

Поскольку доступ к массиву определяется с точки зрения указателей. a[i] определяется как означающий *(a + i), который является коммутативным.

273
ответ дан David Thornley 19 дек. '08 в 20:05
источник поделиться

Я думаю, что что-то упускают другие ответы.

Да, p[i] по определению эквивалентен *(p+i), который (поскольку добавление является коммутативным) эквивалентен *(i+p), который (опять же, по определению оператора []) эквивалентен i[p].

(И в array[i] имя массива неявно преобразуется в указатель на первый элемент массива.)

Но коммутативность сложения в этом случае не так очевидна.

Если оба операнда одного типа или даже разные числовые типы, которые продвигаются до общего типа, коммутативность имеет смысл: x + y == y + x.

Но в этом случае мы говорим конкретно об арифметике указателя, где один операнд является указателем, а другой - целым числом. (Integer + integer - это другая операция, а указатель + указатель - глупость.)

Стандартное описание оператора + C (N1570 6.5.6) гласит:

Для добавления оба операнда должны иметь арифметический тип или один операнд должен быть указателем на полный тип объекта, а другой должен иметь целочисленный тип.

Можно так же легко сказать:

Для добавления оба операнда должны иметь арифметический тип или левый операнд должен быть указателем на полный тип объекта и правый операнд должен иметь целочисленный тип.

и в этом случае оба i + p и i[p] были бы незаконными.

В терминах С++ у нас действительно есть два набора перегруженных операторов +, которые можно условно описать как:

pointer operator+(pointer p, integer i);

и

pointer operator+(integer i, pointer p);

из которых действительно необходимо только первое.

Так почему это так?

С++ унаследовал это определение из C, которое получило его из B (коммутативность индексации массива явно упоминается в 1972 Ссылка для пользователей на B), которая получила это от BCPL (руководство от 1967 г.), которое, возможно, получило его из более ранних языков (CPL? Algol?).

Итак, идея о том, что индексирование массива определяется с точки зрения добавления, и что добавление, даже указателя и целого числа, является коммутативным, возвращается много десятилетий к языкам языков предков.

Эти языки были гораздо менее строго типизированы, чем современный C. В частности, различие между указателями и целыми числами часто игнорировалось. (Ранние программисты C иногда использовали указатели как целые числа без знака, до того, как к языку было добавлено ключевое слово unsigned.) Таким образом, идея сделать добавочную некоммутативную, поскольку операнды разных типов, вероятно, не возникли бы у разработчиков эти языки. Если пользователь хотел добавить две "вещи", независимо от того, являются ли эти "целые" целые числа, указатели или что-то еще, это не зависит от языка, чтобы предотвратить его.

И с годами любое изменение этого правила нарушило бы существующий код (хотя стандарт ANSI C 1989 года, возможно, был хорошей возможностью).

Смена C и/или С++ требует ввода указателя слева, а целое справа может сломать некоторый существующий код, но не будет потери реальной выразительной мощности.

Итак, теперь мы имеем arr[3] и 3[arr], что означает одно и то же, хотя последняя форма никогда не должна появляться вне IOCCC.

188
ответ дан Keith Thompson 23 авг. '13 в 4:37
источник поделиться

И, конечно,

 ("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')

Основная причина этого заключалась в том, что еще в 70-х годах, когда C был разработан, у компьютеров не было много памяти (64KB было много), поэтому компилятор C не проводил много синтаксической проверки. Следовательно, "X[Y]" был скорее слепо переведен в "*(X+Y)"

Это также объясняет синтаксисы "+=" и "++". Все в форме "A = B + C" имело ту же скомпилированную форму. Но если B был тем же объектом, что и A, тогда была доступна оптимизация уровня сборки. Но компилятор не был достаточно ярким, чтобы распознать его, поэтому разработчику пришлось (A += C). Аналогично, если C был 1, была доступна другая оптимизация уровня сборки, и разработчик должен был сделать это явным, потому что компилятор не узнал его. (Совсем недавно компиляторы делают, поэтому в наши дни эти синтаксисы в значительной степени не нужны)

184
ответ дан James Curran 19 дек. '08 в 20:07
источник поделиться

Одна вещь, кажется, не упоминала о проблеме Дины с sizeof:

Вы можете добавить целое число в указатель, вы не можете добавить два указателя вместе. Таким образом, при добавлении указателя на целое число или целое число в указатель, компилятор всегда знает, какой бит имеет размер, который необходимо принять во внимание.

51
ответ дан user30364 11 февр. '09 в 18:56
источник поделиться

Чтобы ответить на вопрос буквально. Не всегда верно, что x == x

double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;

печатает

false
46
ответ дан Peter Lawrey 11 авг. '11 в 16:50
источник поделиться

Хороший вопрос/ответы.

Просто хочу указать, что C-указатели и массивы не совпадают, хотя в этом случае разница не является существенной.

Рассмотрим следующие объявления:

int a[10];
int* p = a;

В a.out символ a находится по адресу, который начинается с массивом, а символ p находится по адресу, где хранится указатель, а значение указателя в этой ячейке памяти является началом массив.

23
ответ дан PolyThinker 20 дек. '08 в 11:16
источник поделиться

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

int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a;  //  s == 5

for(int i = 0 ; i < s ; ++i) {  

           cout << a[a[a[i]]] << endl;
           // ... is equivalent to ... 
           cout << i[a][a][a] << endl;  // but I prefer this one, it easier to increase the level of indirection (without loop)

}

Конечно, я совершенно уверен, что в реальном коде нет смысла использовать, но мне все равно было интересно:)

21
ответ дан Frédéric Terrazzoni 10 июня '12 в 22:50
источник поделиться

Для указателей в C имеем

a[5] == *(a + 5)

а также

5[a] == *(5 + a)

Следовательно, верно, что a[5] == 5[a].

17
ответ дан user1287577 23 марта '12 в 10:05
источник поделиться

Не ответ, а просто пища для размышлений. Если класс имеет перегруженный индекс/индексный оператор, выражение 0[x] не будет работать:

class Sub
{
public:
    int operator [](size_t nIndex)
    {
        return 0;
    }   
};

int main()
{
    Sub s;
    s[0];
    0[s]; // ERROR 
}

Так как мы не имеем доступа к классу int, это не может быть сделано:

class int
{
   int operator[](const Sub&);
};
14
ответ дан Ajay 19 июня '11 в 11:37
источник поделиться

У него очень хорошее объяснение в учебном пособии по баллам и атакам в C Тедом Дженсеном.

Тед Дженсен объяснил это как:

На самом деле это верно, то есть где бы ни пишут a[i], это может быть заменяется на *(a + i) без каких-либо проблем. Фактически, компилятор создаст тот же код в любом случае. Таким образом, мы видим, что указатель арифметика - это то же самое, что индексирование массива. Любой синтаксис дает тот же результат.

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

Теперь, глядя на это последнее выражение, его часть.. (a + i), является простым дополнением, использующим + оператора и правил состояния C, что такое выражение коммутативной. То есть (a + i) идентично (i + a). Таким образом, мы могли бы пишите *(i + a) так же легко, как *(a + i). Но *(i + a) мог бы прийти от i[a]! Из всего этого возникает любопытный правда, что если:

char a[20];

писать

a[3] = 'x';

совпадает с записью

3[a] = 'x';
9
ответ дан A.s. Bhullar 27 сент. '13 в 9:46
источник поделиться

Я знаю, что на вопрос ответили, но я не мог не согласиться с этим объяснением.

Я помню Принципы проектирования компилятора, Предположим, что a является массивом int, а размер int равен 2 байтам, & Амп; Базовый адрес для a - 1000.

Как a[5] будет работать →

Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

Итак,

Аналогично, когда код c разбивается на 3-адресный код, 5[a] станет →

Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010 

Таким образом, в основном оба оператора указывают на одно и то же место в памяти и, следовательно, a[5] = 5[a].

Это объяснение также является причиной того, что отрицательные индексы в массивах работают в C.

то есть. если я получаю доступ к a[-5], он даст мне

Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

Он вернет мне объект в точке 990.

6
ответ дан Ajinkya Patil 04 мая '16 в 11:24
источник поделиться

В массивы C, arr[3] и 3[arr] совпадают, а их эквивалентные обозначения указателей *(arr + 3) - *(3 + arr), Но, наоборот, [arr]3 или [3]arr неверно и приведет к синтаксической ошибке, так как (arr + 3)* и (3 + arr)* являются недопустимыми выражениями. Причина заключается в том, что оператор разыменования должен быть помещен перед адресом, заданным выражением, а не после адреса.

4
ответ дан Krishan 17 дек. '13 в 14:22
источник поделиться

в компиляторе c

a[i]
i[a]
*(a+i)

- это разные способы обращения к элементу массива! (НЕ НА ВСЕ ВРЕМЯ)

3
ответ дан AVIK DUTTA 29 окт. '14 в 12:14
источник поделиться

В C

 int a[]={10,20,30,40,50};
 int *p=a;
 printf("%d\n",*p++);//output will be 10
 printf("%d\n",*a++);//will give an error

Указатель представляет собой "переменную"

имя массива - это "мнемонический" или "синоним"

p++; действителен, но a++ недействителен

a[2] равно 2 [a], потому что внутренняя операция на обоих из них

"Арифметика указателей" внутренне рассчитана как

*(a+3) равно *(3+a)

1
ответ дан Jayghosh Wankar 12 февр. '17 в 16:54
источник поделиться

Ну, это функция, которая возможна только из-за поддержки языка.

Компилятор интерпретирует a[i] как *(a+i), а выражение 5[a] оценивается как *(5+a). Так как сложение коммутативно, оказывается, что оба они равны. Следовательно, выражение оценивается как true.

0
ответ дан Harsha JK 02 апр. '18 в 21:42
источник поделиться

В C-языке указатель и массив очень близки друг к другу, массив можно разделить в виде pointer. Имя массива является указателем на его первый элемент. Поэтому, если acData является массивом символов, то "acData" будет адресом его первого элемента. Вы также можете сказать, что "acData" похож на & acData [0].

В соответствии со стандартом C мы можем представить 1D-массив в виде указателя.

См. приведенное ниже выражение

acData [i] = * (acData + i); --------- > 1D массив в виде указателя

Итак, если я = 5;

cData [5] = * (acData +5);

Мы можем также представить выражение в форме ниже,

cData [5] = * (5 + acData);

Итак, теперь мы можем написать

cData [5] = 5 [cData];

См. приведенный ниже код

#include <stdio.h>

int main(int argc, char *argv[]) {

 char cData  [] = {'w', 'o', 'r', 'l' ,'d' }; // character array

 int index = 0;

 for(index = 0; index < sizeof(cData ); ++index)
 {
     printf("Array element access by pointer = %c\n\n",cData[index]);

     printf("Array element access by   array = %c\n\n",index[cData]);
 }


    return 0;
}

Литература, https://aticleworld.com/array-in-c/

0
ответ дан amlendra mishra 18 апр. '18 в 9:52
источник поделиться

типы указателей

1) указатель на данные

int *ptr;

2) указатель const на данные

int const *ptr;

3) указатель const для данных const

int const *const ptr;

и массивы являются типом (2) из нашего списка
Когда вы определяете массив за один раз, этот адрес инициализируется в этом указателе
Как мы знаем, мы не можем изменить или изменить значение const в нашей программе, потому что он генерирует ERROR во время компиляции

Основная разница, которую я нашел, - это...

Мы можем повторно инициализировать указатель по адресу, но не в том же случае с массивом.

======
и вернемся к вашему вопросу...
a [5] - это не что иное, как * (a + 5)
вы можете легко понять
a - содержащий адрес (люди называют его базовым адресом), как и указатель типа (2) в нашем списке
[] - этот оператор может быть заменен указателем *.

так наконец...

a[5] == *(a +5) == *(5 + a) == 5[a] 
0
ответ дан Jeet Parikh 13 июля '18 в 10:34
источник поделиться

Другие вопросы по меткам