Frage Warum ist es bei Arrays so, dass a [5] == 5 [a]?


Wie Joel darauf hinweist Überlauf-Podcast # 34, im C Programmiersprache (aka: K & R), es gibt eine Erwähnung dieser Eigenschaft von Arrays in C: a[5] == 5[a]

Joel sagt, dass es wegen der Zeigerarithmetik ist, aber ich verstehe immer noch nicht. Warum tut a[5] == 5[a]?


1427
2017-12-19 17:01


Ursprung


Antworten:


Der C-Standard definiert die [] Betreiber wie folgt:

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

Deshalb a[5] wird bewerten zu:

*(a + 5)

und 5[a] wird bewerten zu:

*(5 + a)

a ist ein Zeiger auf das erste Element des Arrays. a[5] ist der Wert, der 5 ist Elemente weiter von a, das ist das gleiche wie *(a + 5)und aus der Grundschulmathematik wissen wir, dass diese gleich sind (Addition ist kommutativ).


1704
2017-12-19 17:04



Weil Array-Zugriff in Bezug auf Zeiger definiert ist. a[i] ist definiert um zu bedeuten *(a + i)das ist kommutativ.


273
2017-12-19 17:05



Ich denke, etwas wird von den anderen Antworten übersehen.

Ja, p[i] ist per Definition gleichwertig *(p+i), die (weil Addition kommutativ ist) entspricht *(i+p), die (wiederum durch die Definition der [] Operator) entspricht i[p].

(Und in array[i]Der Array-Name wird implizit in einen Zeiger auf das erste Element des Arrays konvertiert.

Aber die Kommutativität der Addition ist in diesem Fall nicht so offensichtlich.

Wenn beide Operanden vom selben Typ oder sogar von verschiedenen numerischen Typen sind, die zu einem gemeinsamen Typ hochgestuft werden, ist die Kommutativität durchaus sinnvoll: x + y == y + x.

Aber in diesem Fall sprechen wir speziell über Zeigerarithmetik, wobei ein Operand ein Zeiger und der andere eine ganze Zahl ist. (Ganzzahl + Ganzzahl ist eine andere Operation, und Zeiger + Zeiger ist Unsinn.)

Die Beschreibung des C-Standards der + Operator (N1570 6.5.6) sagt:

Zum Hinzufügen müssen beide Operanden einen arithmetischen Typ oder einen haben   Der Operand soll ein Zeiger auf einen vollständigen Objekttyp und der andere sein   soll Integer-Typ haben.

Es hätte genauso gut sagen können:

Zum Hinzufügen müssen beide Operanden einen arithmetischen Typ oder aufweisen die linke   Operand soll ein Zeiger auf einen vollständigen Objekttyp sein und der rechter Operand   soll Integer-Typ haben.

in diesem Fall beide i + p und i[p] wäre illegal.

In C ++ haben wir wirklich zwei Sätze überladen + Operatoren, die wie folgt beschrieben werden können:

pointer operator+(pointer p, integer i);

und

pointer operator+(integer i, pointer p);

von denen nur das erste wirklich notwendig ist.

Warum ist es so?

C ++ erbte diese Definition von C, die es von B erhielt (die Kommutativität der Array - Indizierung wird 1972 ausdrücklich erwähnt) Benutzerreferenz zu B), von denen es kam BCPL (Handbuch von 1967), die es möglicherweise von noch früheren Sprachen bekommen haben (CPL? Algol?).

Die Idee, dass die Array-Indizierung als Addition definiert ist und dass die Addition, selbst eines Zeigers und einer Ganzzahl, kommutativ ist, geht viele Jahrzehnte zurück in die A-Vorfahren von C.

Diese Sprachen waren viel weniger typisiert als das moderne C. Insbesondere wurde die Unterscheidung zwischen Zeigern und ganzen Zahlen oft ignoriert. (Early C Programmierer verwendeten manchmal Zeiger als vorzeichenlose Ganzzahlen, vor dem unsigned Stichwort wurde der Sprache hinzugefügt.) Die Idee, die Addition nichtkommutativ zu machen, weil die Operanden unterschiedlicher Art sind, wäre den Entwicklern dieser Sprachen wahrscheinlich nicht eingefallen. Wenn ein Benutzer zwei "Dinge" hinzufügen wollte, ob diese "Dinge" Ganzzahlen, Zeiger oder etwas anderes sind, war es der Sprache nicht möglich, dies zu verhindern.

Und im Laufe der Jahre hätte jede Änderung dieser Regel den bestehenden Code durchbrochen (obwohl der 1989 ANSI C-Standard eine gute Gelegenheit gewesen wäre).

Das Ändern von C und / oder C ++, um den Zeiger auf die linke und die ganze Zahl auf der rechten Seite setzen zu müssen, könnte zwar vorhandenen Code zerstören, aber es würde keinen Verlust an echter Ausdruckskraft geben.

So, jetzt haben wir arr[3] und 3[arr] was genau dasselbe bedeutet, obwohl die letztere Form nie außerhalb der erscheinen sollte IOCCC.


186
2017-08-23 01:37



Und natürlich

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

Der Hauptgrund dafür war, dass Computer in den 70er Jahren, als C entwickelt wurde, nicht viel Speicher hatten (64 KB waren eine Menge), so dass der C-Compiler nicht viel Syntax-Checking machte. Daher "X[Y]"wurde eher blind übersetzt in"*(X+Y)"

Dies erklärt auch die "+=" und "++"Syntaxen. Alles in der Form"A = B + C"Hatte die gleiche kompilierte Form. Aber, wenn B das gleiche Objekt wie A war, dann war eine Assembly-Level-Optimierung verfügbar. Aber der Compiler war nicht hell genug, um es zu erkennen, also musste der Entwickler (A += C). Ähnlich, wenn C war 1, eine andere Assembly-Level-Optimierung war verfügbar, und wieder musste der Entwickler es explizit machen, weil der Compiler es nicht erkannte. (Neuere Compiler tun dies, daher sind diese Syntaxen in diesen Tagen größtenteils unnötig)


184
2017-12-19 17:07



Eine Sache scheint niemand über Dinahs Problem mit erwähnt zu haben sizeof:

Sie können einem Zeiger nur eine Ganzzahl hinzufügen, Sie können nicht zwei Zeiger zusammen hinzufügen. Auf diese Weise weiß der Compiler beim Hinzufügen eines Zeigers zu einer Ganzzahl oder einer Ganzzahl zu einem Zeiger immer, welches Bit eine Größe hat, die berücksichtigt werden muss.


51
2018-02-11 15:56



Um die Frage wörtlich zu beantworten. Das stimmt nicht immer x == x

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

Drucke

false

46
2017-08-11 13:50



Nette Frage / Antworten.

Ich möchte nur darauf hinweisen, dass C-Zeiger und Arrays nicht die sind gleich, obwohl in diesem Fall der Unterschied nicht wesentlich ist.

Betrachten Sie die folgenden Deklarationen:

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

Im a.out, das Symbol ein ist an einer Adresse, die der Anfang des Arrays und Symbols ist p befindet sich an einer Adresse, an der ein Zeiger gespeichert ist, und der Wert des Zeigers an dieser Speicherstelle ist der Anfang des Felds.


23
2017-12-20 08:16



Ich finde nur heraus, dass diese hässliche Syntax "nützlich" oder zumindest sehr unterhaltsam sein kann, wenn Sie mit einem Array von Indizes arbeiten wollen, die auf Positionen im selben Array verweisen. Es kann verschachtelte eckige Klammern ersetzen und den Code lesbarer machen!

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)

}

Natürlich bin ich mir ziemlich sicher, dass es in realem Code keinen Anwendungsfall dafür gibt, aber ich fand es trotzdem interessant :)


21
2018-06-10 19:50



Für Zeiger in C haben wir

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

und auch

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

Daher ist es wahr, dass a[5] == 5[a].


17
2018-03-23 07:05



Keine Antwort, sondern nur ein paar Denkanstöße. Wenn die Klasse einen Index- / Indexoperator überladen hat, der Ausdruck 0[x] wird nicht funktionieren:

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

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

Da haben wir keinen Zugang zu int Klasse, das geht nicht:

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

14
2018-06-19 08:37