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


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

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


1420


источник


Ответы:


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

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

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

*(a + 5)

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

*(5 + a)

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


1694



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


273



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

Да, 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]будет незаконным.

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

pointer operator+(pointer p, integer i);

а также

pointer operator+(integer i, pointer p);

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

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

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

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

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

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

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

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


183



И, конечно же,

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

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

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


183



One thing no-one seems to have mentioned about Dinah's problem with sizeof:

You can only add an integer to a pointer, you can't add two pointers together. That way when adding a pointer to an integer, or an integer to a pointer, the compiler always knows which bit has a size that needs to be taken into account.


50



To answer the question literally. It is not always true that x == x

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

prints

false

45



Nice question/answers.

Just want to point out that C pointers and arrays are not the same, although in this case the difference is not essential.

Consider the following declarations:

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

In a.out, the symbol a is at an address that's the beginning of the array, and symbol p is at an address where a pointer is stored, and the value of the pointer at that memory location is the beginning of the array.


23



I just find out this ugly syntax could be "useful", or at least very fun to play with when you want to deal with an array of indexes which refer to positions into the same array. It can replace nested square brackets and make the code more readable !

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's easier to increase the level of indirection (without loop)

}

Of course, I'm quite sure that there is no use case for that in real code, but I found it interesting anyway :)


21



For pointers in C, we have

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

and also

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

Hence it is true that a[5] == 5[a].


17



Not an answer, but just some food for thought. If class is having overloaded index/subscript operator, the expression 0[x] will not work:

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

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

Since we dont have access to int class, this cannot be done:

class int
{
   int operator[](const Sub&);
};

14



It has very good explanation in A TUTORIAL ON POINTERS AND ARRAYS IN C by Ted Jensen.

Ted Jensen explained it as:

In fact, this is true, i.e wherever one writes a[i] it can be replaced with *(a + i) without any problems. In fact, the compiler will create the same code in either case. Thus we see that pointer arithmetic is the same thing as array indexing. Either syntax produces the same result.

This is NOT saying that pointers and arrays are the same thing, they are not. We are only saying that to identify a given element of an array we have the choice of two syntaxes, one using array indexing and the other using pointer arithmetic, which yield identical results.

Now, looking at this last expression, part of it.. (a + i), is a simple addition using the + operator and the rules of C state that such an expression is commutative. That is (a + i) is identical to (i + a). Thus we could write *(i + a) just as easily as *(a + i). But *(i + a) could have come from i[a] ! From all of this comes the curious truth that if:

char a[20];

writing

a[3] = 'x';

is the same as writing

3[a] = 'x';

9