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]
?
c
arrays
pointers
pointer-arithmetic
Dinah
quelle
quelle
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} +)char bar[]; int foo[];
undfoo[i][bar]
als Ausdruck verwendet werden.a[b]
=*(a + b)
für jedes gegebenea
undb
, aber es war die freie Wahl der Sprachdesigner,+
für alle Typen kommutativ definiert zu werden. Nichts konnte sie daran hindern, zu verbieten,i + p
während sie es erlaubtenp + i
.+
, kommutativ zu sein. Vielleicht besteht das eigentliche Problem darin, Zeigeroperationen arithmetisch zu machen, anstatt einen separaten Offset-Operator zu entwerfen.Antworten:
Der C-Standard definiert den
[]
Operator wie folgt:a[b] == *(a + b)
Daher
a[5]
wird bewertet, um:und
5[a]
wird bewerten zu:a
ist ein Zeiger auf das erste Element des Arrays.a[5]
ist der Wert, der 5 Elemente weiter entfernt ista
, was derselbe ist*(a + 5)
, und aus der Grundschulmathematik wissen wir, dass diese gleich sind (Addition ist kommutativ ).quelle
a[5]
wird es zu etwas kompiliert,mov eax, [ebx+20]
anstatt[ebx+5]
*(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)
.Weil der Array-Zugriff in Form von Zeigern definiert wird.
a[i]
ist definiert als gemeint*(a + i)
, was kommutativ ist.quelle
*(i + a)
, was geschrieben werden kann alsi[a]
".*(a + i)
kommutativ ist". Da*(a + i) = *(i + a) = i[a]
jedoch die Addition kommutativ ist.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 isti[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:Es hätte genauso gut sagen können:
in diesem Fall beides
i + p
undi[p]
wäre illegal.In C ++ - Begriffen haben wir wirklich zwei Sätze überladener
+
Operatoren, die lose beschrieben werden können als:und
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
unsigned
Schlü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]
und3[arr]
meinen wir genau dasselbe, obwohl die letztere Form niemals außerhalb des IOCCC erscheinen sollte .quelle
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.ring16_t
das 65535 enthältring16_t
, unabhängig von der Größe vonint
ein a mit dem Wert 1 ergibt .Und natürlich
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 WeiseC
war1
eine 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.)quelle
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.
quelle
Die Frage wörtlich beantworten. Das stimmt nicht immer
x == x
druckt
quelle
cout << (a[5] == a[5] ? "true" : "false") << endl;
istfalse
.x == x
ist 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!).NAN
In<math.h>
, das besser ist als0.0/0.0
, weil0.0/0.0
UB ist, wenn__STDC_IEC_559__
nicht definiert ist (Die meisten Implementierungen definieren nicht__STDC_IEC_559__
, aber bei den meisten Implementierungen0.0/0.0
funktionieren sie immer noch)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!
Natürlich bin ich mir ziemlich sicher, dass es dafür keinen Anwendungsfall in echtem Code gibt, aber ich fand es trotzdem interessant :)
quelle
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 ... unda
ist ein Index. Wenn Sie sehena[a[a[i]]]
, denken Sie, dass a ein Zeiger auf ein Array oder ein Array undi
ein Index ist.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:
In
a.out
befindet sich das Symbola
an einer Adresse, die der Anfang des Arrays ist, und das Symbolp
befindet sich an einer Adresse, an der ein Zeiger gespeichert ist, und der Wert des Zeigers an dieser Speicherstelle ist der Anfang des Arrays.quelle
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).int*p = a;
mitint 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.Für Zeiger in C haben wir
und auch
Daher ist es wahr, dass
a[5] == 5[a].
quelle
Keine Antwort, sondern nur ein Denkanstoß. Wenn die Klasse einen überladenen Index- / Indexoperator hat,
0[x]
funktioniert der Ausdruck nicht:Da wir keinen Zugriff auf die Klasse int haben , ist dies nicht möglich:
quelle
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
operator[]
soll eine nicht statische Elementfunktion mit genau einem Parameter sein." Ich war mit dieser Einschränkung vertrautoperator=
und glaubte nicht, dass sie zutraf[]
.[]
Operators ändern , wird sie natürlich nie wieder gleichwertig sein. Wenn siea[b]
gleich ist*(a + b)
und Sie dies ändern, müssen Sie auch überladenint::operator[](const Sub&);
undint
sind keine Klasse ...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:
quelle
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
a
handelt sich um einint
Array mit einer Größe vonint
2 Bytes und einer Basisadresse vona
1000.Wie
a[5]
wird es funktionieren ->Damit,
In ähnlicher Weise
5[a]
wird ->, wenn der c-Code in 3-Adressen-Code zerlegt wird, ->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 gebenEs wird mir Objekt an Position 990 zurückgeben.
quelle
In C-Arrays sind
arr[3]
und3[arr]
gleich, und ihre äquivalenten Zeigernotationen sind*(arr + 3)
zu*(3 + arr)
. Aber 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 Dereferenzierungsoperator vor der vom Ausdruck angegebenen Adresse und nicht nach der Adresse stehen sollte.quelle
in c Compiler
Es gibt verschiedene Möglichkeiten, auf ein Element in einem Array zu verweisen! (Überhaupt nicht seltsam)
quelle
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:
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
musste wirklich tun
J = !(V + 5)
(unter Verwendung der BCPL-Syntax), da es notwendig war, V abzurufen, um die Basisadresse des Arrays zu erhalten. SoV!5
und5!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, undsizeof
wurde zu einer Sache.quelle
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 als5[a]
ausgewertet*(5+a)
. Da die Addition kommutativ ist, stellt sich heraus, dass beide gleich sind. Daher ergibt der Ausdruck zutrue
.quelle
In C.
Zeiger ist eine "Variable"
Der Array-Name ist eine "Mnemonik" oder ein "Synonym".
p++;
ist gültig, abera++
ungültiga[2]
ist gleich 2 [a], da die interne Operation für beide ist"Zeigerarithmetik" intern berechnet als
*(a+3)
gleich*(3+a)
quelle
Zeigertypen
1) Zeiger auf Daten
2) const Zeiger auf Daten
3) const Zeiger auf const Daten
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...
quelle