web-dev-qa-db-de.com

Warum ist bei Arrays a [5] == 5 [a]?

Wie Joel in Stapelüberlauf-Podcast # 34 und in C-Programmiersprache (alias K & R) hervorhebt, wird diese Eigenschaft von Arrays in C erwähnt: a[5] == 5[a]

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

1491
Dinah

Der C-Standard definiert den Operator [] wie folgt:

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

Daher wird a[5] ausgewertet, um:

*(a + 5)

und 5[a] wird ausgewertet zu:

*(5 + a)

a ist ein Zeiger auf das erste Element des Arrays. a[5] ist der Wert, der 5 Elemente von a entfernt ist, was dem Wert von *(a + 5) und von elementary school math we entspricht weiß, dass diese gleich sind (Addition ist kommutativ ).

1837
Mehrdad Afshari

Weil der Array-Zugriff in Form von Zeigern definiert wird. a[i] ist definiert als *(a + i), was kommutativ ist.

277
David Thornley

Ich denke, etwas wird von den anderen Antworten vermisst.

Ja, p[i] ist definitionsgemäß äquivalent zu *(p+i), das (weil der Zusatz kommutativ ist) äquivalent zu *(i+p) ist, was (wiederum durch die Definition des []-Operators) äquivalent zu i[p] ist.

(Und in array[i] wird der Arrayname implizit in einen Zeiger auf das erste Element des Arrays umgewandelt.)

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

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

In diesem Fall sprechen wir jedoch speziell von der Zeigerarithmetik, bei der ein Operand ein Zeiger und der andere eine Ganzzahl ist. (Ganzzahl + Ganzzahl ist eine andere Operation und Zeiger + Zeiger ist Unsinn.)

Die Beschreibung des +-Operators ( N1570 6.5.6) des C-Standards lautet:

Zusätzlich müssen entweder beide Operanden einen arithmetischen Typ haben oder ein Operand soll ein Zeiger auf einen vollständigen Objekttyp und der andere .__ sein. soll einen ganzzahligen Typ haben.

Es hätte genauso gut sagen können:

Zum Hinzufügen müssen entweder beide Operanden einen arithmetischen Typ haben oder die linke Operand soll ein Zeiger auf einen vollständigen Objekttyp und den Operanden right .__ sein. soll einen ganzzahligen Typ haben.

in diesem Fall wären sowohl i + p als auch i[p] unzulässig.

In C++ gibt es zwei überladene +-Operatoren, die als lose bezeichnet werden können:

pointer operator+(pointer p, integer i);

und

pointer operator+(integer i, pointer p);

von denen nur der 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 ist in der 1972 Benutzerreferenz auf B explizit erwähnt), die sie von BCPL erhielt (Handbuch vom 1967) ), die es vielleicht sogar aus früheren Sprachen (CPL? Algol?) bekommen hat.

Die Idee, dass die Indexierung von Arrays als Addition definiert wird und die Addition, auch eines Zeigers und einer Ganzzahl, kommutativ ist, geht viele Jahrzehnte zurück auf die Vorgängersprachen von C.

Diese Sprachen waren viel weniger stark typisiert als das moderne C. Insbesondere die Unterscheidung zwischen Zeigern und Ganzzahlen wurde oft ignoriert. (Frühe C-Programmierer verwendeten manchmal Zeiger als vorzeichenlose Ganzzahlen, bevor das Schlüsselwort unsigned zur Sprache hinzugefügt wurde.) Die Idee, die Addition nicht kommutativ zu machen, weil die Operanden unterschiedlichen Typs sind, ist den Designern dieser Sprachen wahrscheinlich nicht aufgefallen . Wenn ein Benutzer zwei "Dinge" hinzufügen wollte, ob diese "Dinge" ganze Zahlen, Zeiger oder etwas anderes sind, war es nicht die Aufgabe der Sprache, dies zu verhindern.

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

Wenn Sie C und/oder C++ so ändern, dass Sie den Zeiger links und die Ganzzahl rechts setzen müssen, wird möglicherweise vorhandener Code beschädigt, aber die tatsächliche Ausdruckskraft geht nicht verloren.

Nun haben wir arr[3] und 3[arr] genau dasselbe, obwohl die letztere Form niemals außerhalb von IOCCC erscheinen sollte.

201
Keith Thompson

Und natürlich

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

Der Hauptgrund dafür war, dass Computer in den 70er Jahren nicht viel Speicher hatten (64 KB waren sehr viel), als C entworfen wurde. Daher führte der C-Compiler nicht viel Syntaxprüfung durch. Daher wurde "X[Y]" ziemlich blind in "*(X+Y)" übersetzt. 

Dies erklärt auch die Syntax "+=" und "++". Alles in der Form "A = B + C" hatte die gleiche kompilierte Form. Wenn B das gleiche Objekt wie A war, stand eine Optimierung auf Baugruppenebene zur Verfügung. Aber der Compiler war nicht hell genug, um es zu erkennen, also musste der Entwickler (A += C). Wenn C1 war, war eine andere Optimierung auf Assembly-Ebene verfügbar, und der Entwickler musste es erneut explizit machen, da der Compiler dies nicht erkannte. (In letzter Zeit tun Compiler dies, sodass diese Syntax heutzutage weitgehend unnötig ist.)

190
James Curran

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

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, welches Bit eine Größe hat, die berücksichtigt werden muss.

52
user30364

Um die Frage wörtlich zu beantworten. Es ist nicht immer wahr, dass x == x

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

druckt

false
48
Peter Lawrey

Schöne Frage/Antworten.

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

Beachten Sie die folgenden Deklarationen:

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

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

23
PolyThinker

Ich finde nur heraus, dass diese hässliche Syntax "nützlich" sein könnte, oder zumindest sehr lustig zu spielen ist, wenn Sie sich mit einem Array von Indizes beschäftigen möchten, die sich auf Positionen in demselben Array beziehen. Es kann geschachtelte 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 Verwendungszweck dafür gibt, aber ich fand es trotzdem interessant :)

21

Für Hinweise in C haben wir

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

und auch

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

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

17
user1287577

Keine Antwort, sondern nur ein Anlass zum Nachdenken. Wenn in der Klasse der Index-/Indexoperator überladen ist, funktioniert der Ausdruck 0[x] nicht:

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

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

Da wir keinen Zugriff auf int class haben, ist dies nicht möglich:

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

Es hat eine sehr gute Erklärung in Ein TUTORIAL ZU ZEIGERN UND ARRAYS IN C Von Ted Jensen.

Ted Jensen erklärte es als:

In der Tat ist dies wahr, d. H. Wo immer a[i] geschrieben wird, kann es .__ sein. problemlos durch *(a + i) ersetzt. Der Compiler erstellt in beiden Fällen denselben Code. So sehen wir diesen Zeiger Arithmetik ist dasselbe wie die Indexierung von Arrays. Beide Syntax erzeugt das gleiche Ergebnis.

Dies sagt NICHT, dass Zeiger und Arrays sind das Gleiche, sie sind es nicht. Wir sagen nur das, um zu identifizieren Bei einem gegebenen Element eines Arrays haben wir die Wahl zwischen zwei Syntaxen, einer unter Verwendung der Array-Indizierung und der anderen unter Verwendung der Zeigerarithmetik, die ergeben identische Ergebnisse.

Nun zum letzten Ausdruck, Teil davon .. (a + i), ist ein einfacher Zusatz mit + Operator und die Regeln von C geben an, dass ein solcher Ausdruck .__ ist. kommutativ. Das heißt (a + i) ist identisch mit (i + a). So könnten wir *(i + a) genauso einfach schreiben wie *(a + i). Aber *(i + a) könnte von i[a] stammen! Von all dem kommt das Neugierige. Wahrheit, dass wenn:

char a[20];

schreiben

a[3] = 'x';

ist das gleiche wie das Schreiben

3[a] = 'x';
9
A.s. Bhullar

Ich weiß, dass die Frage beantwortet wurde, aber ich konnte nicht widerstehen, diese Erklärung zu teilen.

Ich erinnere mich an die Prinzipien des Compiler-Entwurfs Nehmen wir an, a ist ein int-Array und die Größe von int beträgt 2 Byte. Die Basisadresse für a beträgt 1000.

Wie funktioniert a[5] ->

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

So, 

Wenn der c-Code in 3-Adressen-Code unterteilt ist, wird 5[a] zu ->

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

Im Grunde weisen beide Anweisungen auf dieselbe Position im Speicher und daher a[5] = 5[a].

Diese Erklärung ist auch der Grund, warum negative Indizes in Arrays in C funktionieren.

wenn ich auf a[-5] zugreife, wird es mich geben

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

Das Objekt wird an Position 990 zurückgegeben.

6
Ajinkya Patil

In C-Arrays , arr[3] und 3[arr] sind identisch, und die entsprechenden Zeigernotierungen lauten *(arr + 3) bis *(3 + arr). Im Gegenteil, [arr]3 oder [3]arr ist nicht korrekt und führt zu Syntaxfehlern, da (arr + 3)* und (3 + arr)* keine gültigen Ausdrücke sind. Der Grund dafür ist, dass der Dereference-Operator vor der Adresse steht, die der Ausdruck liefert, nicht hinter der Adresse.

5
Krishan

in c Compiler 

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

es gibt verschiedene Möglichkeiten, auf ein Element in einem Array zu verweisen! (NICHT AT ALL WEIRD)

4
AVIK DUTTA

Ein bisschen Geschichte jetzt. BCPL hatte unter anderem einen ziemlich großen Einfluss auf die frühe Entwicklung von C. Wenn Sie ein Array in BCPL mit so etwas wie deklariert haben:

let V = vec 10

das reservierte 11 Wörter des Gedächtnisses wirklich nicht 10. Typischerweise war V das erste und enthielt die Adresse des unmittelbar folgenden Wortes. Im Gegensatz zu C ging der Name V an diesen Ort und ermittelte die Adresse des nullten Elements des Arrays. Daher Array-Indirektion in BCPL, ausgedrückt als

let J = V!5

musste wirklich J = !(V + 5) ausführen (mit BCPL-Syntax), da V abgerufen werden musste, um die Basisadresse des Arrays abzurufen. Somit waren V!5 und 5!V synonym. Als Anekdotenbeobachtung wurde WAFL (Warwick Functional Language) in BCPL geschrieben, und nach meinem besten Wissen verwendete ich eher die letztere Syntax als die erstere, um auf die als Datenspeicher verwendeten Knoten zuzugreifen. Zugegeben, das ist irgendwo zwischen 35 und 40 Jahren her, also ist meine Erinnerung etwas verrostet. :)

Die Innovation, auf das zusätzliche Wort des Speichers zu verzichten und den Compiler die Basisadresse des Arrays einfügen zu lassen, als es benannt wurde, kam später. Laut dem C History Paper geschah dies ungefähr zu der Zeit, als Strukturen zu C hinzugefügt wurden.

Beachten Sie, dass ! in BCPL sowohl ein unärer Präfixoperator als auch ein binärer Infixoperator war, und in beiden Fällen eine Indirektion ausführte. Nur dass die Binärform vor der Indirektion eine Addition der beiden Operanden enthielt. Angesichts der wortorientierten Natur von BCPL (und B) ergab dies tatsächlich einen großen Sinn. Die Einschränkung von "Zeiger und Ganzzahl" wurde in C erforderlich gemacht, als es Datentypen gewann, und sizeof wurde eine Sache.

1
dgnuff

In 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

Zeiger ist eine "Variable" 

array-Name ist ein "Mnemonic" oder "Synonym"

p++; ist gültig, aber a++ ist ungültig

a[2] ist gleich 2 [a], da der interne Vorgang für beide von diesen beiden Vorgängen ist 

"Zeigerarithmetik" intern berechnet als

*(a+3) entspricht *(3+a)

0
Jayghosh Wankar

Nun, dies ist eine Funktion, die nur aufgrund der Sprachunterstützung möglich ist.

Der Compiler interpretiert a[i] als *(a+i) und der Ausdruck 5[a] wird zu *(5+a) ausgewertet. Da die Addition kommutativ ist, stellt sich heraus, dass beide gleich sind. Daher wird der Ausdruck zu true ausgewertet.

0
Harsha J K