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

1622

Wie Joel im Stack Overflow-Podcast Nr. 34 in der Programmiersprache C (auch bekannt als: K & R) hervorhebt, wird diese Eigenschaft von Arrays in C:a[5] == 5[a]

Joel sagt, dass es an der Zeigerarithmetik liegt, aber ich verstehe es immer noch nicht. Waruma[5] == 5[a] ?

Dinah
quelle
48
würde so etwas wie ein [+] auch funktionieren wie * (a ++) ODER * (++ a)?
Egon
45
@Egon: Das ist sehr kreativ, aber leider funktionieren Compiler nicht so. Der Compiler interpretiert a[1]als eine Reihe von Token, nicht als Zeichenfolgen: * ({Ganzzahlposition von} einem {Operator} + {Ganzzahl} 1) ist dasselbe wie * ({Ganzzahl} 1 {Operator} + {Ganzzahlposition von} a) aber ist nicht dasselbe wie * ({ganzzahlige Position} eines {Operators} + {Operator} +)
Dinah
11
Eine interessante zusammengesetzte Variante davon wird in Illogical Array Access dargestellt , wo Sie haben char bar[]; int foo[];und foo[i][bar]als Ausdruck verwendet werden.
Jonathan Leffler
5
@EldritchConundrum, warum kann der Compiler Ihrer Meinung nach nicht überprüfen, ob der linke Teil ein Zeiger ist? Ja, kann es. Es ist wahr, dass a[b]= *(a + b)für jedes gegebene aund b, aber es war die freie Wahl der Sprachdesigner, +für alle Typen kommutativ definiert zu werden. Nichts konnte sie daran hindern, zu verbieten, i + pwährend sie es erlaubten p + i.
Ach
13
@Andrey Man erwartet normalerweise +, kommutativ zu sein. Vielleicht besteht das eigentliche Problem darin, Zeigeroperationen arithmetisch zu machen, anstatt einen separaten Offset-Operator zu entwerfen.
Eldritch Conundrum

Antworten:

1924

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

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

Daher a[5]wird bewertet, um:

*(a + 5)

und 5[a]wird bewerten zu:

*(5 + a)

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

Mehrdad Afshari
quelle
325
Ich frage mich, ob es nicht mehr wie * ((5 * sizeof (a)) + a) ist. Tolle Erklärung.
John MacIntyre
92
@ Dinah: Aus Sicht des C-Compilers haben Sie Recht. Es wird keine Größe benötigt und die von mir erwähnten Ausdrücke sind DIE GLEICHEN. Der Compiler berücksichtigt jedoch sizeof bei der Erstellung des Maschinencodes. Wenn a ein int-Array ist, a[5]wird es zu etwas kompiliert, mov eax, [ebx+20]anstatt[ebx+5]
Mehrdad Afshari
12
@ Dinah: A ist eine Adresse, sagen wir 0x1230. Wenn a in einem 32-Bit-Int-Array war, dann ist a [0] bei 0x1230, a [1] bei 0x1234, a [2] bei 0x1238 ... a [5] bei x1244 usw. Wenn wir nur 5 hinzufügen 0x1230 erhalten wir 0x1235, was falsch ist.
James Curran
36
@ sr105: Dies ist ein Sonderfall für den Operator +, bei dem einer der Operanden ein Zeiger und der andere eine Ganzzahl ist. Der Standard besagt, dass das Ergebnis vom Typ des Zeigers ist. Der Compiler / muss / klug genug sein.
Aib
48
„von der Grundschule Mathematik wissen wir , das sind gleich“ - Ich verstehe , dass Sie zu vereinfachen, aber ich bin mit denen , die sich so fühlen sich über die Vereinfachung. Das ist nicht elementar *(10 + (int *)13) != *((int *)10 + 13). Mit anderen Worten, hier ist mehr los als Grundschularithmetik. Die Kommutativität hängt entscheidend davon ab, dass der Compiler erkennt, welcher Operand ein Zeiger ist (und auf welche Objektgröße). Anders ausgedrückt (1 apple + 2 oranges) = (2 oranges + 1 apple), aber (1 apple + 2 oranges) != (1 orange + 2 apples).
LarsH
288

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

David Thornley
quelle
42
Arrays werden nicht als Zeiger definiert, aber der Zugriff darauf ist möglich.
Leichtigkeitsrennen im Orbit
5
Ich würde hinzufügen "so ist es gleich *(i + a), was geschrieben werden kann als i[a]".
Jim Balter
4
Ich würde vorschlagen, dass Sie das Zitat aus dem Standard wie folgt einfügen: 6.5.2.1: 2 Ein Postfix-Ausdruck, gefolgt von einem Ausdruck in eckigen Klammern [], ist eine tiefgestellte Bezeichnung eines Elements eines Array-Objekts. Die Definition des Indexoperators [] ist, dass E1 [E2] identisch ist mit (* ((E1) + (E2))). Aufgrund der Konvertierungsregeln, die für den Operator binär + gelten, bezeichnet E1 [E2] das E2-te Element von, wenn E1 ein Array-Objekt (äquivalent ein Zeiger auf das Anfangselement eines Array-Objekts) und E2 eine Ganzzahl ist E1 (von Null aus zählen).
Vality
Um genauer zu sein: Arrays zerfallen in Zeiger, wenn Sie darauf zugreifen.
12431234123412341234123
Nitpick: Es macht keinen Sinn zu sagen, dass " *(a + i)kommutativ ist". Da *(a + i) = *(i + a) = i[a]jedoch die Addition kommutativ ist.
Andreas Rejbrand
231

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

Ja, p[i]ist per Definition äquivalent zu *(p+i), was (weil Addition kommutativ ist) äquivalent zu ist *(i+p), was (wiederum nach der Definition des []Operators) äquivalent zu ist i[p].

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

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

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

In diesem Fall handelt es sich jedoch speziell um eine Zeigerarithmetik, bei der ein Operand ein Zeiger und der andere eine Ganzzahl ist. (Ganzzahl + Ganzzahl ist eine andere Operation, und Zeiger + Zeiger ist Unsinn.)

In der Beschreibung des +Bedieners nach C-Standard ( N1570 6.5.6) heißt es:

Außerdem müssen entweder beide Operanden einen arithmetischen Typ haben, oder ein Operand muss ein Zeiger auf einen vollständigen Objekttyp sein, und der andere muss einen ganzzahligen Typ haben.

Es hätte genauso gut sagen können:

Außerdem müssen entweder beide Operanden einen arithmetischen Typ haben, oder der linke Operand muss ein Zeiger auf einen vollständigen Objekttyp sein, und der rechte Operand muss einen ganzzahligen Typ haben.

in diesem Fall beides i + pund i[p]wäre illegal.

In C ++ - Begriffen haben wir wirklich zwei Sätze überladener +Operatoren, die lose beschrieben werden können als:

pointer operator+(pointer p, integer i);

und

pointer operator+(integer i, pointer p);

davon ist nur der erste wirklich notwendig.

Warum ist es so?

C ++ hat diese Definition von C geerbt, das sie von B erhalten hat (die Kommutativität der Array-Indizierung wird in der Benutzerreferenz von 1972 zu B ausdrücklich erwähnt ), die sie von BCPL (Handbuch vom 1967) erhalten hat, was sie möglicherweise sogar erhalten hat frühere Sprachen (CPL? Algol?).

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

Diese Sprachen waren viel weniger stark typisiert als das moderne C. Insbesondere wurde die Unterscheidung zwischen Zeigern und ganzen Zahlen oft ignoriert. (Frühe C-Programmierer verwendeten manchmal Zeiger als vorzeichenlose Ganzzahlen, bevor das unsignedSchlüsselwort zur Sprache hinzugefügt wurde.) Die Idee, das Hinzufügen nicht kommutativ zu machen, weil die Operanden unterschiedlichen Typs sind, wäre den Designern dieser Sprachen wahrscheinlich nicht in den Sinn gekommen. Wenn ein Benutzer zwei "Dinge" hinzufügen wollte, unabhängig davon, ob es sich bei diesen "Dingen" um Ganzzahlen, Zeiger oder etwas anderes handelt, lag es nicht an der Sprache, dies zu verhindern.

Und 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 sein könnte).

Wenn Sie C und / oder C ++ so ändern, dass der Zeiger links und die Ganzzahl rechts gesetzt werden müssen, wird möglicherweise vorhandener Code beschädigt, es kommt jedoch nicht zu einem Verlust der tatsächlichen Ausdruckskraft.

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

Keith Thompson
quelle
12
Fantastische Beschreibung dieser Eigenschaft. Aus einer hochrangigen Sicht halte ich es für 3[arr]ein interessantes Artefakt, sollte aber selten oder nie verwendet werden. Die akzeptierte Antwort auf diese Frage (< stackoverflow.com/q/1390365/356> ), die ich vor einiger Zeit gestellt habe, hat meine Meinung zur Syntax geändert. Obwohl es technisch oft keinen richtigen und falschen Weg gibt, diese Dinge zu tun, beginnen diese Arten von Funktionen Sie dabei, auf eine Weise zu denken, die von den Implementierungsdetails getrennt ist. Diese andere Denkweise hat Vorteile, die teilweise verloren geht, wenn Sie sich auf die Implementierungsdetails konzentrieren.
Dinah
3
Addition ist kommutativ. Es wäre seltsam, wenn der C-Standard es anders definieren würde. Deshalb könnte man nicht so einfach sagen: "Außerdem müssen entweder beide Operanden einen arithmetischen Typ haben, oder der linke Operand soll ein Zeiger auf einen vollständigen Objekttyp sein, und der rechte Operand soll einen ganzzahligen Typ haben." - Das würde für die meisten Leute, die Dinge hinzufügen, keinen Sinn ergeben.
Iheanyi
9
@iheanyi: Addition ist normalerweise kommutativ - und es werden normalerweise zwei Operanden desselben Typs benötigt. Mit der Zeigeraddition können Sie einen Zeiger und eine Ganzzahl hinzufügen, jedoch nicht zwei Zeiger. Meiner Meinung nach ist dies bereits ein hinreichend seltsamer Sonderfall, bei dem es keine erhebliche Belastung wäre, wenn der Zeiger der linke Operand sein müsste. (Einige Sprachen verwenden "+" für die Verkettung von Zeichenfolgen; das ist sicherlich nicht kommutativ.)
Keith Thompson
3
@ Supercat, das ist noch schlimmer. Das würde bedeuten, dass manchmal x + 1! = 1 + x. Dies würde die assoziative Eigenschaft der Addition vollständig verletzen.
Iheanyi
3
@iheanyi: Ich denke, Sie meinten kommutatives Eigentum; Addition ist bereits nicht assoziativ, da bei den meisten Implementierungen (1LL + 1U) -2! = 1LL + (1U-2). In der Tat würde die Änderung einige Situationen assoziativ machen, die derzeit nicht sind, z. B. 3U + (UINT_MAX-2L) wäre gleich (3U + UINT_MAX) -2. Am besten wäre es jedoch, wenn die Sprache neue unterschiedliche Typen für heraufstufbare Ganzzahlen und "umhüllende" algebraische Ringe hinzufügt, so dass das Hinzufügen von 2 zu a, ring16_tdas 65535 enthält ring16_t, unabhängig von der Größe vonint ein a mit dem Wert 1 ergibt .
Supercat
196

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 viel), so dass der C-Compiler nicht viel Syntaxprüfung durchführte. 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 jedoch dasselbe Objekt wie A war, war eine Optimierung auf Baugruppenebene verfügbar. Aber der Compiler war nicht hell genug, um es zu erkennen, also musste der Entwickler ( A += C). In ähnlicher Weise Cwar 1eine andere Optimierung auf Assembly-Ebene verfügbar, und der Entwickler musste sie erneut explizit angeben, da der Compiler sie nicht erkannte. (In jüngerer Zeit tun dies Compiler, daher sind diese Syntaxen heutzutage weitgehend unnötig.)

James Curran
quelle
127
Eigentlich ergibt das falsch; Der erste Term "ABCD" [2] == 2 ["ABCD"] ergibt true oder 1 und 1! = 'C': D
Jonathan Leffler
8
@ Jonathan: Die gleiche Mehrdeutigkeit führte zur Bearbeitung des Originaltitels dieses Beitrags. Sind wir die gleichen Noten mathematische Äquivalenz, Codesyntax oder Pseudocode? Ich argumentiere mit mathematischer Äquivalenz, aber da wir über Code sprechen, können wir uns nicht entziehen, dass wir alles in Bezug auf die Codesyntax anzeigen.
Dinah
19
Ist das nicht ein Mythos? Ich meine, dass die Operatoren + = und ++ erstellt wurden, um den Compiler zu vereinfachen? Einige Codes werden mit ihnen klarer, und es ist eine nützliche Syntax, unabhängig davon, was der Compiler damit macht.
Thomas Padron-McCarthy
6
+ = und ++ haben einen weiteren signifikanten Vorteil. Wenn die linke Seite während der Auswertung eine Variable ändert, wird die Änderung nur einmal durchgeführt. a = a + ...; werde es zweimal tun.
Johannes Schaub - litb
8
Nein - "ABCD" [2] == * ("ABCD" + 2) = * ("CD") = "C". Das Dereferenzieren eines Strings gibt Ihnen ein Zeichen, keinen Teilstring
MSalters
55

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

Sie können einem Zeiger nur eine Ganzzahl hinzufügen, Sie können nicht zwei Zeiger zusammenfü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.

user30364
quelle
1
In den Kommentaren der akzeptierten Antwort wird darüber ziemlich ausführlich gesprochen. Ich habe diese Konversation in der Bearbeitung auf die ursprüngliche Frage verwiesen, aber Ihr direkt berechtigtes Anliegen der Größe von nicht direkt angesprochen. Ich bin mir nicht sicher, wie ich das am besten in SO machen soll. Soll ich das orig noch einmal bearbeiten? Frage?
Dinah
50

Die Frage wörtlich beantworten. Das stimmt nicht immerx == 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
Peter Lawrey
quelle
27
Eigentlich ist ein "Nan" nicht gleich sich selbst: cout << (a[5] == a[5] ? "true" : "false") << endl;ist false.
TrueY
8
@TrueY: Er hat das speziell für den NaN-Fall angegeben (und speziell x == xist das nicht immer wahr). Ich denke das war seine Absicht. Er ist also technisch korrekt (und möglicherweise, wie man so sagt, die beste Art von richtig!).
Tim
3
Die Frage bezieht sich auf C, Ihr Code ist kein C-Code. Es gibt auch ein NANIn <math.h>, das besser ist als 0.0/0.0, weil 0.0/0.0UB ist, wenn __STDC_IEC_559__nicht definiert ist (Die meisten Implementierungen definieren nicht __STDC_IEC_559__, aber bei den meisten Implementierungen 0.0/0.0funktionieren sie immer noch)
12431234123412341234123
26

Ich finde nur heraus, dass diese hässliche Syntax "nützlich" sein kann oder zumindest sehr viel Spaß macht, wenn Sie sich mit einem Array von Indizes befassen möchten, die sich auf Positionen in demselben Array beziehen. 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 dafür keinen Anwendungsfall in echtem Code gibt, aber ich fand es trotzdem interessant :)

Frédéric Terrazzoni
quelle
Wenn Sie sehen i[a][a][a], denken Sie, ich bin entweder ein Zeiger auf ein Array oder ein Array eines Zeigers auf ein Array oder ein Array ... und aist ein Index. Wenn Sie sehen a[a[a[i]]], denken Sie, dass a ein Zeiger auf ein Array oder ein Array und iein Index ist.
12431234123412341234123
1
Beeindruckend! Es ist eine sehr coole Verwendung dieser "dummen" Funktion. Könnte bei einigen Problemen im algorithmischen Wettbewerb nützlich sein))
Serge Breusov
26

Schöne Frage / Antwort.

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

Beachten Sie die folgenden Erklärungen:

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

In a.outbefindet sich das Symbol aan einer Adresse, die der Anfang des Arrays ist, und das Symbol pbefindet sich an einer Adresse, an der ein Zeiger gespeichert ist, und der Wert des Zeigers an dieser Speicherstelle ist der Anfang des Arrays.

PolyThinker
quelle
2
Nein, technisch sind sie nicht gleich. Wenn Sie b als int * const definieren und es auf ein Array verweisen lassen, ist es immer noch ein Zeiger, was bedeutet, dass b in der Symboltabelle auf einen Speicherort verweist, in dem eine Adresse gespeichert ist, die wiederum auf die Position des Arrays verweist .
PolyThinker
4
Sehr guter Punkt. Ich erinnere mich an einen sehr bösen Fehler, als ich ein globales Symbol in einem Modul als char s [100] definierte und es als externes Zeichen deklarierte. in einem anderen Modul. Nachdem alles miteinander verbunden war, verhielt sich das Programm sehr seltsam. Weil das Modul, das die externe Deklaration verwendet, die Anfangsbytes des Arrays als Zeiger auf char verwendet hat.
Giorgio
1
Ursprünglich war in Cs Großeltern-BCPL ein Array ein Zeiger. Das heißt, was Sie beim Schreiben erhalten haben (ich habe nach C transkribiert), int a[10]war ein Zeiger namens 'a', der auf genügend Speicher für 10 Ganzzahlen an anderer Stelle zeigte. Somit hatten a + i und j + i dieselbe Form: Fügen Sie den Inhalt einiger Speicherstellen hinzu. Tatsächlich denke ich, dass BCPL typenlos war, also waren sie identisch. Die Skalierung der Typgröße galt nicht, da BCPL rein wortorientiert war (auch auf Maschinen mit Wortadresse).
Dave
Ich denke, der beste Weg, um den Unterschied zu verstehen, ist der Vergleich int*p = a;mit int b = 5; In letzterem sind "b" und "5" beide ganze Zahlen, aber "b" ist eine Variable, während "5" ein fester Wert ist. In ähnlicher Weise sind "p" und "a" beide Adressen eines Zeichens, aber "a" ist ein fester Wert.
James Curran
20

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].

user1055604
quelle
15

Keine Antwort, sondern nur ein Denkanstoß. Wenn die Klasse einen überladenen Index- / Indexoperator hat, 0[x]funktioniert der Ausdruck 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 die Klasse int haben , ist dies nicht möglich:

class int
{
   int operator[](const Sub&);
};
Ajay
quelle
2
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
Ben Voigt
1
Haben Sie tatsächlich versucht, es zu kompilieren? Es gibt eine Reihe von Operatoren, die nicht außerhalb der Klasse implementiert werden können (dh als nicht statische Funktionen)!
Ajay
3
Ups, du hast recht. " operator[]soll eine nicht statische Elementfunktion mit genau einem Parameter sein." Ich war mit dieser Einschränkung vertraut operator=und glaubte nicht, dass sie zutraf [].
Ben Voigt
1
Wenn Sie die Definition des []Operators ändern , wird sie natürlich nie wieder gleichwertig sein. Wenn sie a[b]gleich ist *(a + b)und Sie dies ändern, müssen Sie auch überladen int::operator[](const Sub&);und intsind keine Klasse ...
Luis Colorado
7
Das ... ist nicht ... C.
MD XF
11

Es hat eine sehr gute Erklärung in A TUTORIAL ON POINTERS AND ARRAYS IN C. von Ted Jensen.

Ted Jensen erklärte es als:

In der Tat ist dies wahr, dh wo immer man schreibt a[i], kann es durch ersetzt werden*(a + i) ohne Probleme durch ersetzt werden. Tatsächlich erstellt der Compiler in beiden Fällen denselben Code. Wir sehen also, dass Zeigerarithmetik dasselbe ist wie Array-Indizierung. Jede Syntax führt zum gleichen Ergebnis.

Dies bedeutet NICHT, dass Zeiger und Arrays dasselbe sind, sie sind es nicht. Wir sagen nur, dass wir zur Identifizierung eines bestimmten Elements eines Arrays die Wahl zwischen zwei Syntaxen haben, eine mit Array-Indizierung und die andere mit Zeigerarithmetik, die identische Ergebnisse liefern.

Das Betrachten dieses letzten Ausdrucks, ein Teil davon (a + i), ist eine einfache Ergänzung unter Verwendung des Operators +, und die Regeln von C besagen, dass ein solcher Ausdruck kommutativ ist. Das heißt (a + i) ist identisch mit (i + a). So konnten wir *(i + a)genauso einfach schreiben wie *(a + i). Hätte *(i + a)aber kommen können i[a]! Aus all dem ergibt sich die merkwürdige Wahrheit, dass wenn:

char a[20];

Schreiben

a[3] = 'x';

ist das gleiche wie schreiben

3[a] = 'x';
Als Bhullar
quelle
4
a + i ist KEINE einfache Addition, da es sich um eine Zeigerarithmetik handelt. Wenn die Größe des Elements von a 1 (char) ist, dann ist es genau wie Integer +. Wenn es sich jedoch (z. B.) um eine Ganzzahl handelt, entspricht dies möglicherweise einem + 4 * i.
Alex Brown
@AlexBrown Ja, es ist eine Zeigerarithmetik, weshalb Ihr letzter Satz falsch ist, es sei denn, Sie setzen zuerst 'a' als a (char *) (vorausgesetzt, ein int ist 4 Zeichen). Ich verstehe wirklich nicht, warum so viele Leute am tatsächlichen Wertergebnis der Zeigerarithmetik hängen bleiben. Der gesamte Zweck der Zeigerarithmetik besteht darin, die zugrunde liegenden Zeigerwerte zu abstrahieren und den Programmierer über die zu manipulierenden Objekte nachdenken zu lassen, anstatt über Adresswerte.
jschultz410
8

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

Ich erinnere mich an die Prinzipien des Compiler-Designs. Nehmen wir an, es ahandelt sich um ein intArray mit einer Größe von int2 Bytes und einer Basisadresse von a1000.

Wie a[5]wird es funktionieren ->

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

Damit,

In ähnlicher Weise 5[a]wird ->, wenn der c-Code in 3-Adressen-Code zerlegt wird, ->

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

Im Grunde genommen zeigen beide Anweisungen auf dieselbe Stelle im Speicher und damit auf a[5] = 5[a] .

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

dh wenn ich darauf zugreife a[-5], werde ich es geben

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

Es wird mir Objekt an Position 990 zurückgeben.

Ajinkya Patil
quelle
6

In C-Arrays sind arr[3]und 3[arr]gleich, und ihre äquivalenten Zeigernotationen sind *(arr + 3)zu *(3 + arr). Aber im Gegenteil [arr]3oder [3]arrist 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 Dereferenzierungsoperator vor der vom Ausdruck angegebenen Adresse und nicht nach der Adresse stehen sollte.

Krishan
quelle
6

in c Compiler

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

Es gibt verschiedene Möglichkeiten, auf ein Element in einem Array zu verweisen! (Überhaupt nicht seltsam)

AVIK DUTTA
quelle
5

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 folgenden Elementen deklariert haben:

let V = vec 10

das ordnete tatsächlich 11 Wörter des Speichers zu, 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 nahm die Adresse des nullten Elements des Arrays auf. Daher Array-Indirektion in BCPL, ausgedrückt als

let J = V!5

musste wirklich tun J = !(V + 5)(unter Verwendung der BCPL-Syntax), da es notwendig war, V abzurufen, um die Basisadresse des Arrays zu erhalten. So V!5und5!V waren auch. Als anekdotische Beobachtung wurde WAFL (Warwick Functional Language) in BCPL geschrieben, und nach bestem Wissen verwendete ich eher die letztere Syntax als die erstere für den Zugriff auf die als Datenspeicher verwendeten Knoten. Zugegeben, das ist irgendwo vor 35 bis 40 Jahren, also ist meine Erinnerung ein wenig verrostet. :) :)

Die Neuerung, auf das zusätzliche Speicherwort zu verzichten und den Compiler die Basisadresse des Arrays einfügen zu lassen, als es benannt wurde, kam später. Laut dem C-Geschichtspapier geschah dies ungefähr zu dem Zeitpunkt, als Strukturen zu C hinzugefügt wurden.

Beachten Sie, dass !BCPL in beiden Fällen sowohl ein unärer Präfixoperator als auch ein binärer Infixoperator war und in beiden Fällen eine Indirektion durchführte. nur dass die binäre Form eine Addition der beiden Operanden vor der Indirektion enthielt. Angesichts der wortorientierten Natur von BCPL (und B) war dies tatsächlich sehr sinnvoll. Die Einschränkung von "Zeiger und Ganzzahl" wurde in C notwendig, als es Datentypen gewann, und sizeofwurde zu einer Sache.

dgnuff
quelle
1

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 wird als 5[a]ausgewertet *(5+a). Da die Addition kommutativ ist, stellt sich heraus, dass beide gleich sind. Daher ergibt der Ausdruck zu true.

Harsha JK
quelle
Obwohl redundant, ist dies klar, präzise und kurz.
Bill K
0

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"

Der Array-Name ist eine "Mnemonik" oder ein "Synonym".

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

a[2] ist gleich 2 [a], da die interne Operation für beide ist

"Zeigerarithmetik" intern berechnet als

*(a+3) gleich *(3+a)

Jayghosh Wankar
quelle
-4

Zeigertypen

1) Zeiger auf Daten

int *ptr;

2) const Zeiger auf Daten

int const *ptr;

3) const Zeiger auf const Daten

int const *const ptr;

und die Arrays sind vom Typ (2) aus unserer Liste.
Wenn Sie ein Array gleichzeitig definieren, wird eine Adresse in diesem Zeiger initialisiert.
Da wir wissen, dass wir den const-Wert in unserem Programm nicht ändern oder modifizieren können, wird beim Kompilieren ein FEHLER ausgelöst Zeit

Der Hauptunterschied, den ich gefunden habe, ist ...

Wir können den Zeiger durch eine Adresse neu initialisieren, aber nicht den gleichen Fall mit einem Array.

======
und zurück zu Ihrer Frage ...
a[5]ist nichts, aber *(a + 5)
Sie können es leicht verstehen, indem Sie
a die Adresse (die Leute nennen sie als Basisadresse) genau wie einen (2) Zeigertyp in unserer Liste enthalten
[]- dieser Operator kann durch Zeiger austauschbar *.

so endlich...

a[5] == *(a +5) == *(5 + a) == 5[a] 
Jeet Parikh
quelle