Nehmen Sie die Adresse eines One-Past-The-End-Array-Elements per Index: legal nach dem C ++ - Standard oder nicht?

77

Ich habe schon mehrmals festgestellt, dass der folgende Code vom C ++ - Standard nicht zugelassen wird:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

Ist &array[5]legaler C ++ - Code in diesem Zusammenhang?

Ich möchte eine Antwort mit Bezug auf den Standard, wenn möglich.

Es wäre auch interessant zu wissen, ob es den C-Standard erfüllt. Und wenn es kein Standard-C ++ ist, warum wurde die Entscheidung getroffen, es anders zu behandeln als array + 5oder &array[4] + 1?

Zan Lynx
quelle
7
@Brian: Nein, es müssten nur Grenzen überprüft werden, ob die Laufzeit erforderlich ist, um den Fehler abzufangen . Um dies zu vermeiden, kann der Standard einfach "nicht erlaubt" sagen. Es ist undefiniertes Verhalten vom Feinsten. Sie dürfen dies nicht tun, und die Laufzeit und der Compiler müssen Ihnen nicht mitteilen , ob Sie dies tun.
Jalf
Ok, nur um ein bisschen zu verdeutlichen, denn der Titel hat mich in die Irre geführt: Ein Zeiger hinter dem Ende eines Arrays ist nicht außerhalb der Grenzen. Außerhalb der Grenzen sind Zeiger im Allgemeinen nicht zulässig, aber der Standard ist bei One-Past-the-End-Zeigern viel milder. Möglicherweise möchten Sie den Titel bearbeiten, wenn Sie speziell nach One-Past-the-End-Zeigern fragen. Wenn Sie allgemein über Zeiger außerhalb der Grenzen Bescheid wissen möchten , sollten Sie Ihr Beispiel bearbeiten. ;)
Jalf
Er fragt nicht nach Hinweisen einer Vergangenheit im Allgemeinen. Er fragt nach der Verwendung des Operators &, um den Zeiger zu erhalten.
Matthew Flaschen
1
@ Matthew: Aber die Antwort darauf hängt davon ab, wohin dieser Zeiger zeigt. Sie dürfen die Adresse im One-Past-The-End-Fall verwenden, jedoch nicht in einem Out-of-Bound-Fall.
Jalf
Abschnitt 5.3.1.1 Unärer Operator '*': 'Das Ergebnis ist ein Wert, der sich auf das Objekt oder die Funktion bezieht'. Abschnitt 5.2.1 Subskription Der Ausdruck E1 [E2] ist (per Definition) identisch mit * ((E1) + (E2)). Durch meine Lektüre des Standards hier. Der resultierende Zeiger wird nicht entfrischt. (siehe vollständige Erklärung unten)
Martin York

Antworten:

40

Ihr Beispiel ist legal, aber nur, weil Sie keinen Out-of-Bound-Zeiger verwenden.

Lassen Sie uns zuerst Zeiger außerhalb der Grenzen behandeln (weil ich Ihre Frage ursprünglich so interpretiert habe, bevor ich bemerkte, dass das Beispiel stattdessen einen One-Past-The-End-Zeiger verwendet):

Im Allgemeinen dürfen Sie nicht einmal einen Zeiger außerhalb der Grenzen erstellen . Ein Zeiger muss auf ein Element innerhalb des Arrays oder auf ein Element nach dem Ende zeigen . Nirgendwo sonst.

Der Zeiger darf nicht einmal existieren, was bedeutet, dass Sie ihn offensichtlich auch nicht dereferenzieren dürfen.

Hier ist, was der Standard zu diesem Thema zu sagen hat:

5,7: 5:

Wenn ein Ausdruck mit einem integralen Typ zu einem Zeiger hinzugefügt oder von diesem subtrahiert wird, hat das Ergebnis den Typ des Zeigeroperanden. Wenn der Zeigeroperand auf ein Element eines Array-Objekts zeigt und das Array groß genug ist, zeigt das Ergebnis auf ein Element, das vom ursprünglichen Element versetzt ist, sodass die Differenz der Indizes der resultierenden und ursprünglichen Array-Elemente dem integralen Ausdruck entspricht. Mit anderen Worten, wenn der Ausdruck P auf das i-te Element eines Array-Objekts zeigt, zeigen die Ausdrücke (P) + N (äquivalent N + (P)) und (P) -N (wobei N den Wert n hat) zu den i + n-ten bzw. i-n-ten Elementen des Array-Objekts, sofern sie existieren. Wenn der Ausdruck P auf das letzte Element eines Array-Objekts zeigt, Der Ausdruck (P) +1 zeigt eins nach dem letzten Element des Array-Objekts. Wenn der Ausdruck Q eins nach dem letzten Element eines Array-Objekts zeigt, zeigt der Ausdruck (Q) -1 auf das letzte Element des Array-Objekts . Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen.Andernfalls ist das Verhalten nicht definiert .

(Hervorhebung von mir)

Dies gilt natürlich für Operator +. Um ganz sicher zu gehen, sagt der Standard Folgendes über Array-Subskription:

5.2.1: 1:

Der Ausdruck E1[E2]ist identisch (per Definition) mit*((E1)+(E2))

Natürlich gibt es eine offensichtliche Einschränkung: Ihr Beispiel zeigt keinen Zeiger außerhalb der Grenzen. Es wird ein Zeiger "Eins nach dem Ende" verwendet, der anders ist. Der Zeiger darf existieren (wie oben angegeben), aber der Standard sagt, soweit ich sehen kann, nichts darüber aus, ihn zu dereferenzieren. Der nächste, den ich finden kann, ist 3.9.2: 3:

[Hinweis: Beispielsweise wird davon ausgegangen, dass die Adresse nach dem Ende eines Arrays (5.7) auf ein nicht verwandtes Objekt des Elementtyps des Arrays verweist, das sich möglicherweise an dieser Adresse befindet. - Endnote]

Was meiner Meinung nach impliziert, dass Sie es rechtlich dereferenzieren können, aber das Ergebnis des Lesens oder Schreibens an den Ort ist nicht spezifiziert.

Vielen Dank an ilproxyil für die Korrektur des letzten Teils hier und die Beantwortung des letzten Teils Ihrer Frage:

  • array + 5Dereferenziert eigentlich nichts, sondern erstellt einfach einen Zeiger auf einen nach dem Ende von array.
  • &array[4] + 1Dereferenzen array+4(was absolut sicher ist), nimmt die Adresse dieses l-Werts und fügt dieser Adresse eine hinzu, was zu einem One-Past-the-End-Zeiger führt (aber dieser Zeiger wird nie dereferenziert.
  • &array[5] dereferenziert Array + 5 (was meines Erachtens legal ist und zu "einem nicht verwandten Objekt des Elementtyps des Arrays" führt, wie oben erwähnt) und nimmt dann die Adresse dieses Elements, was ebenfalls legal genug zu sein scheint.

Sie machen also nicht ganz dasselbe, obwohl in diesem Fall das Endergebnis dasselbe ist.

jalf
quelle
4
& array [5] zeigt auf eine Vergangenheit. Dies ist jedoch kein legaler Weg, um diese Adresse zu erhalten.
Matthew Flaschen
5
Der letzte Satz ist falsch. "Array + 5" und "& Array [4] + 1" dereferenzieren NICHT nach dem Ende, während Array [5] dies tut. (Ich nehme auch an, Sie meinten & array [5], aber der Kommentar bleibt bestehen). Die ersten beiden zeigen einfach eins nach dem Ende.
user83255
2
@jalf diese Anmerkung - ich denke, es möchte nur sagen, dass "a + sizeof a" gleichermaßen für "& b" gültig ist, wenn b direkt nach dem Array "a" zugewiesen wird, und dass die resultierenden Adressen gleichermaßen "auf" dasselbe zeigen Objekt. Nicht mehr. Denken Sie daran, dass alle Notizen informativ (nicht normativ) sind: Wenn es so grundlegende wichtige Fakten wie das Vorhandensein von Objekten nach einem Array-Objekt geben würde, die sich am Ende des Endes befinden, müsste eine solche Regel normativ gemacht werden
Johannes Schaub - litb
2
Für ANSI C (C89 / C90) ist dies die richtige Antwort. Wenn Sie dem Standard bis zum Buchstaben folgen, ist & array [5] technisch ungültig, während array + 5 gültig ist, obwohl so ziemlich jeder Compiler für beide Ausdrücke den gleichen Code erzeugt. C99 aktualisiert den Standard, um & array [5] explizit zuzulassen. Siehe meine Antwort für alle Details.
Adam Rosenfield
2
@jalf, auch der gesamte Text, in dem sich die Notiz befindet, beginnt mit "Wenn sich ein Objekt vom Typ T an einer Adresse A befindet ..." <- Das heißt "Der folgende Text setzt voraus, dass sich ein Objekt an Adresse A befindet." Ihr Zitat sagt also nicht (und kann unter dieser Bedingung auch nicht), dass sich immer ein Objekt an der Adresse A befindet.
Johannes Schaub - litb
44

Ja, es ist legal. Aus dem C99-Standardentwurf :

§6.5.2.1, Absatz 2:

Ein Postfix-Ausdruck, gefolgt von einem Ausdruck in eckigen Klammern, []ist eine tiefgestellte Bezeichnung eines Elements eines Array-Objekts. Die Definition des Index - Operator [] ist , dass E1[E2]identisch ist (*((E1)+(E2))). Aufgrund der Konvertierungsregeln, die für den Binäroperator gelten +, bezeichnet if, wenn E1es sich um ein Array-Objekt (äquivalent einen Zeiger auf das Anfangselement eines Array-Objekts) und E2eine Ganzzahl handelt, E1[E2]das E2-te Element von E1(Zählen von Null).

§6.5.3.2, Absatz 3 (Schwerpunkt Mine):

Der unäre &Operator gibt die Adresse seines Operanden an. Wenn der Operand den Typ '' type '' hat, hat das Ergebnis den Typ '' Zeiger auf den Typ ''. Wenn der Operand das Ergebnis eines unären *Operators ist, werden weder dieser Operator noch der &Operator ausgewertet, und das Ergebnis ist so, als ob beide weggelassen worden wären, außer dass die Einschränkungen für die Operatoren weiterhin gelten und das Ergebnis kein Wert ist. In ähnlicher Weise wird, wenn der Operand das Ergebnis eines []Operators ist, weder der Operator & noch der Unary *, der durch den Operator impliziert []wird, ausgewertet, und das Ergebnis ist so, als ob der &Operator entfernt und der []Operator in einen +Operator geändert worden wäre. Andernfalls ist das Ergebnis ein Zeiger auf das Objekt oder die Funktion, die durch seinen Operanden festgelegt sind.

§6.5.6, Absatz 8:

Wenn ein Ausdruck mit einem ganzzahligen Typ zu einem Zeiger hinzugefügt oder von diesem subtrahiert wird, hat das Ergebnis den Typ des Zeigeroperanden. Wenn der Zeigeroperand auf ein Element eines Array-Objekts zeigt und das Array groß genug ist, zeigt das Ergebnis auf ein Element, das vom ursprünglichen Element versetzt ist, sodass die Differenz der Indizes der resultierenden und ursprünglichen Array-Elemente dem ganzzahligen Ausdruck entspricht. Mit anderen Worten, wenn der Ausdruck Pauf das i-te Element eines Array-Objekts zeigt, zeigen die Ausdrücke (P)+N(äquivalent N+(P)) und (P)-N(wo Nder Wert ist n) jeweils auf das i+n-te und i−n-te Element des Array-Objekts, sofern sie vorhanden sind existieren. Darüber hinaus, wenn der AusdruckPzeigt auf das letzte Element eines Array-Objekts, der Ausdruck (P)+1zeigt eins nach dem letzten Element des Array-Objekts, und wenn der Ausdruck Qeins nach dem letzten Element eines Array-Objekts zeigt, zeigt der Ausdruck (Q)-1auf das letzte Element des Array-Objekts. Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen. Andernfalls ist das Verhalten undefiniert. Wenn das Ergebnis eins nach dem letzten Element des Array-Objekts zeigt, darf es nicht als Operand eines unären *Operators verwendet werden, der ausgewertet wird.

Beachten Sie, dass der Standard Zeigern ausdrücklich erlaubt, ein Element über das Ende des Arrays hinaus zu zeigen, sofern sie nicht dereferenziert sind . Nach 6.5.2.1 und 6.5.3.2 ist der Ausdruck &array[5]äquivalent zu &*(array + 5), was äquivalent zu ist (array+5), was eins nach dem Ende des Arrays zeigt. Dies führt nicht zu einer Dereferenzierung (bis 6.5.3.2), ist also legal.

Adam Rosenfield
quelle
1
Er fragte ausdrücklich nach C ++. Dies ist die Art von subtilem Unterschied, auf den man sich beim Portieren zwischen beiden nicht verlassen kann.
Matthew Flaschen
3
Er fragte nach beiden: "Es wäre auch interessant zu wissen, ob es dem C-Standard entspricht."
CB Bailey
2
@Matthew Flaschen: Der C ++ - Standard enthält den C-Standard als Referenz. Anhang C.2 enthält eine Liste von Änderungen (Inkompatibilitäten zwischen ISO C und ISO C ++), und keine der Änderungen bezieht sich auf diese Klauseln. Daher ist & array [5] in C und C ++ legal.
Adam Rosenfield
12
Der C-Standard ist eine normative Referenz im C ++ - Standard. Dies bedeutet, dass Bestimmungen im C-Standard, auf die der C ++ - Standard verweist, Teil des C ++ - Standards sind. Dies bedeutet nicht, dass alles in der C-Norm gilt. Insbesondere Anhang C ist informativ und nicht normativ. Nur weil in diesem Abschnitt kein Unterschied hervorgehoben wird, bedeutet dies nicht, dass die C-Version für C ++ gilt.
CB Bailey
1
@ Adam: Danke, dass du darauf hingewiesen hast. Ich war mir der Geschichte von C nie ganz sicher. Bezüglich C ++ 0x in Bezug auf C99, meinem geringen Wissen über die Änderungen in C99, bin ich mir ziemlich sicher, dass C ++ weiterhin auf C89 / 90 verweist und von Fall zu Fall die "wünschenswerten" Änderungen von C99 heraussucht . Diese Frage / Antwort ist ein gutes Beispiel dafür. Ich würde sagen, dass C ++ weiterhin den no "lvalue-to-rvalue" verwendet, daher kein undefiniertes Verhalten, anstatt den Wortlaut "& * == no-op" zu integrieren.
Richard Corden
17

Es ist legal.

Nach Angaben der gcc - Dokumentation für C ++ , &array[5]ist legal. Sowohl in C ++ als auch in C können Sie das Element sicher nach dem Ende eines Arrays adressieren - Sie erhalten einen gültigen Zeiger. So &array[5]wie ein Ausdruck ist legal.

Es ist jedoch immer noch ein undefiniertes Verhalten, zu versuchen, Zeiger auf nicht zugewiesenen Speicher zu dereferenzieren, selbst wenn der Zeiger auf eine gültige Adresse zeigt. Der Versuch, den durch diesen Ausdruck erzeugten Zeiger zu dereferenzieren, ist also immer noch ein undefiniertes Verhalten (dh illegal), obwohl der Zeiger selbst gültig ist.

In der Praxis stelle ich mir jedoch vor, dass dies normalerweise keinen Absturz verursachen würde.

Bearbeiten: Übrigens ist dies im Allgemeinen die Art und Weise, wie der end () - Iterator für STL-Container implementiert wird (als Zeiger auf one-past-the-end). Dies ist also ein ziemlich guter Beweis dafür, dass die Praxis legal ist.

Bearbeiten: Oh, jetzt sehe ich, dass Sie nicht wirklich fragen, ob das Halten eines Zeigers auf diese Adresse legal ist, aber ob genau diese Art, den Zeiger zu erhalten, legal ist. Ich werde mich den anderen Antwortenden zuwenden.

Tyler McHenry
quelle
4
Ich würde sagen, Sie haben Recht, wenn und nur wenn die C ++ - Spezifikation nicht besagt, dass & * als No-Op behandelt werden muss. Ich würde mir vorstellen, dass es das wahrscheinlich nicht sagt.
Tyler McHenry
8
er Seite Sie verweisen (richtig) sagt , dass es legal ist , zeigen eine über das Ende. & array [5], technisch zuerst Dereferenzen (Array + 5), dann erneut referenzieren. Technisch ist es also so: (& * (Array + 5)). Glücklicherweise sind Compiler klug genug zu wissen, dass & * zu nichts berücksichtigt werden kann. Sie sind jedoch nicht haben , das zu tun, also ich würde sagen , es UB ist.
Evan Teran
4
@Evan: Da ist noch mehr. Lesen Sie die letzte Zeile des Kernproblems 232: std.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#232 . Das letzte Beispiel dort sieht einfach falsch aus - aber sie erklären deutlich, dass die Unterscheidung in der Konvertierung von "Wert zu Wert" liegt, die in diesem Fall nicht stattfindet.
Richard Corden
@ Richard: Interessant, scheint es eine Debatte zu diesem Thema zu geben. Ich würde sogar darüber einig , dass es sollte :-P zugelassen werden.
Evan Teran
2
Es ist die gleiche Art von undefiniertem Verhalten wie das "Referenz-auf-NULL" -Ding, über das immer wieder diskutiert wurde, und wo anscheinend alle die Antwort "Es ist undefiniertes Verhalten"
Johannes Schaub - am
10

Ich glaube, dass dies legal ist, und es hängt von der Umwandlung von "Wert zu Wert" ab. Die letzte Zeile der Kernausgabe 232 enthält Folgendes:

Wir waren uns einig, dass der Ansatz im Standard in Ordnung zu sein scheint: p = 0; * p; ist nicht von Natur aus ein Fehler. Eine Umwandlung von lWert in rWert würde zu einem undefinierten Verhalten führen

Obwohl dies ein etwas anderes Beispiel ist, zeigt es, dass das '*' nicht zu einer Umwandlung von lWert in rWert führt. Da der Ausdruck der unmittelbare Operand von '&' ist, der einen lWert erwartet, wird das Verhalten definiert.

Richard Corden
quelle
+1 für den interessanten Link. Ich bin mir immer noch nicht sicher, ob ich damit einverstanden bin, dass p = 0; * p; ist gut definiert, da ich nicht davon überzeugt bin, dass '*' für einen Ausdruck gut definiert ist, dessen Wert kein Zeiger auf ein tatsächliches Objekt ist.
CB Bailey
Eine Aussage, die ein Ausdruck ist, ist legal und bedeutet, diesen Ausdruck zu bewerten. * p ist ein Ausdruck, der undefiniertes Verhalten hervorruft. Alles, was die Implementierung tut, entspricht dem Standard (einschließlich E-Mail an Ihren Chef oder Herunterladen von Baseballstatistiken).
David Thornley
1
Beachten Sie, dass der Status dieses Problems immer noch "Entwurf" ist und es (noch) nicht in den Standard geschafft hat, zumindest die Entwurfsversionen von C ++ 11 und C ++ 14, die ich finden konnte.
Musiphil
Der Vorschlag "leere Werte" wurde nie in einen veröffentlichten Standard übernommen
MM
Die Hinweise in der Ausgabe beziehen sich ausdrücklich auf das &*a[n]Problem: "Ebenso sollte das Dereferenzieren eines Zeigers auf das Ende eines Arrays zulässig sein, solange der Wert nicht verwendet wird." Leider ist dieses Problem seit 2003 nicht mehr aktuell und eine Lösung ist noch nicht im Standard enthalten.
Pablo Halpern
9

Ich glaube nicht, dass es illegal ist, aber ich glaube, dass das Verhalten von & array [5] undefiniert ist.

  • 5.2.1 [Ausdruck] E1 [E2] ist (per Definition) identisch mit * ((E1) + (E2))

  • 5.3.1 [expr.unary.op] unary * operator ... das Ergebnis ist ein Wert, der sich auf das Objekt oder die Funktion bezieht, auf die der Ausdruck verweist.

Zu diesem Zeitpunkt haben Sie ein undefiniertes Verhalten, da der Ausdruck ((E1) + (E2)) nicht auf ein Objekt zeigte und der Standard angibt, wie das Ergebnis aussehen soll, es sei denn, dies ist der Fall.

  • 1.3.12 [defns.undefined] Undefiniertes Verhalten kann auch erwartet werden, wenn in dieser Internationalen Norm die Beschreibung einer expliziten Definition des Verhaltens weggelassen wird.

Wie an anderer Stelle erwähnt, array + 5und &array[0] + 5gültig sind und auch Arten definiert über das Ende des Array einen Zeiger einen zu erhalten.

CB Bailey
quelle
Der entscheidende Punkt ist: "Das Ergebnis von '*' ist ein Wert". Soweit ich das beurteilen kann, wird es nur dann zu UB, wenn Sie für dieses Ergebnis eine Konvertierung von Wert zu Wert haben.
Richard Corden
1
Ich würde behaupten, dass das Ergebnis von '*' nur in Bezug auf das Objekt definiert ist, auf das der Ausdruck angewendet wird, auf den der Operator angewendet wird. Dann ist es undefiniert - durch Auslassung - was das Ergebnis ist, wenn der Ausdruck kein a hat Wert, der sich tatsächlich auf ein Objekt bezieht. Es ist jedoch alles andere als klar.
CB Bailey
7

Zusätzlich zu den obigen Antworten werde ich auf den Operator hinweisen und kann für Klassen überschrieben werden. Selbst wenn es für PODs gültig war, ist es wahrscheinlich keine gute Idee, dies für ein Objekt zu tun, von dem Sie wissen, dass es nicht gültig ist (ähnlich wie das Überschreiben des Operators & () an erster Stelle).

Todd Gardner
quelle
4
+1, wenn Operator & in die Diskussion einbezogen wird, auch wenn Experten empfehlen, ihn niemals zu überschreiben, da einige STL-Container davon abhängen, dass ein Zeiger in das Element zurückgegeben wird. Es ist eines dieser Dinge, die in den Standard aufgenommen wurden, bevor sie es besser wussten.
David Rodríguez - Dribeas
4

Das ist legal:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

Abschnitt 5.2.1 Abonnement Der Ausdruck E1 [E2] ist (per Definition) identisch mit * ((E1) + (E2))

Damit können wir also sagen, dass array_end auch äquivalent ist:

int *array_end = &(*((array) + 5)); // or &(*(array + 5))

Abschnitt 5.3.1.1 Unärer Operator '*': Der unäre * Operator führt eine Indirektion durch: Der Ausdruck, auf den er angewendet wird, muss ein Zeiger auf einen Objekttyp oder ein Zeiger auf einen Funktionstyp sein, und das Ergebnis ist ein Wert, der sich auf das Objekt bezieht oder Funktion, auf die der Ausdruck zeigt. Wenn der Typ des Ausdrucks "Zeiger auf T" ist, ist der Typ des Ergebnisses "T". [Hinweis: Ein Zeiger auf einen unvollständigen Typ (außer cv void) kann dereferenziert werden. Der so erhaltene Wert kann auf begrenzte Weise verwendet werden (zum Beispiel zum Initialisieren einer Referenz); Dieser Wert darf nicht in einen Wert umgewandelt werden, siehe 4.1. - Endnote]

Der wichtige Teil des oben genannten:

'Das Ergebnis ist ein Wert, der sich auf das Objekt oder die Funktion bezieht.'

Der unäre Operator '*' gibt einen l-Wert zurück, der sich auf den int bezieht (keine Referenzierung). Der unäre Operator '&' erhält dann die Adresse des Wertes.

Solange es keine De-Referenzierung eines Zeigers außerhalb der Grenzen gibt, wird die Operation vollständig vom Standard abgedeckt und das gesamte Verhalten wird definiert. Nach meiner Lektüre ist das oben Gesagte völlig legal.

Die Tatsache, dass viele der STL-Algorithmen vom genau definierten Verhalten abhängen, ist eine Art Hinweis, den das Normungsgremium bereits hat, und ich bin sicher, dass es etwas gibt, das dies explizit abdeckt.

Der Kommentarbereich unten enthält zwei Argumente:

(Bitte lesen Sie: aber es ist lang und wir beide enden trollisch)

Argument 1

Dies ist aufgrund von Abschnitt 5.7 Absatz 5 illegal

Wenn ein Ausdruck mit einem integralen Typ zu einem Zeiger hinzugefügt oder von diesem subtrahiert wird, hat das Ergebnis den Typ des Zeigeroperanden. Wenn der Zeigeroperand auf ein Element eines Array-Objekts zeigt und das Array groß genug ist, zeigt das Ergebnis auf ein Element, das vom ursprünglichen Element versetzt ist, sodass die Differenz der Indizes der resultierenden und ursprünglichen Array-Elemente dem integralen Ausdruck entspricht. Mit anderen Worten, wenn der Ausdruck P auf das i-te Element eines Array-Objekts zeigt, zeigen die Ausdrücke (P) + N (äquivalent N + (P)) und (P) -N (wobei N den Wert n hat) zu den i + n-ten bzw. i - n-ten Elementen des Array-Objekts, sofern sie existieren. Wenn der Ausdruck P auf das letzte Element eines Array-Objekts zeigt, zeigt der Ausdruck (P) +1 außerdem eins nach dem letzten Element des Array-Objekts. und wenn der Ausdruck Q eins nach dem letzten Element eines Array-Objekts zeigt, zeigt der Ausdruck (Q) -1 auf das letzte Element des Array-Objekts. Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen. Andernfalls ist das Verhalten undefiniert.

Und obwohl der Abschnitt relevant ist; Es zeigt kein undefiniertes Verhalten. Alle Elemente in dem Array, über die wir sprechen, befinden sich entweder innerhalb des Arrays oder eines nach dem Ende (was im obigen Absatz gut definiert ist).

Argument 2:

Das zweite Argument, das unten dargestellt wird, ist: *ist der De-Referenz-Operator.
Und obwohl dies ein gebräuchlicher Begriff ist, der verwendet wird, um den Operator '*' zu beschreiben; Dieser Begriff wird im Standard bewusst vermieden, da der Begriff "De-Reference" in Bezug auf die Sprache und die Bedeutung für die zugrunde liegende Hardware nicht genau definiert ist.

Obwohl der Zugriff auf den Speicher über das Ende des Arrays hinaus definitiv undefiniertes Verhalten ist. Ich bin nicht davon überzeugt, dass der unary * operatorZugriff auf den Speicher (Lesen / Schreiben in den Speicher) in diesem Zusammenhang (nicht in einer vom Standard definierten Weise) erfolgt. In diesem Zusammenhang (wie im Standard definiert (siehe 5.3.1.1)) gibt die unary * operatorRückgabe a lvalue referring to the object. Nach meinem Verständnis der Sprache ist dies kein Zugriff auf den zugrunde liegenden Speicher. Das Ergebnis dieses Ausdrucks wird dann sofort vom unary & operatorOperator verwendet, der die Adresse des Objekts zurückgibt, auf das der lvalue referring to the object.

Viele andere Verweise auf Wikipedia und nicht kanonische Quellen werden vorgestellt. All das finde ich irrelevant. C ++ wird durch den Standard definiert .

Fazit:

Ich möchte zugeben, dass es viele Teile des Standards gibt, die ich möglicherweise nicht berücksichtigt habe und die möglicherweise beweisen, dass meine obigen Argumente falsch sind. NON sind unten angegeben. Wenn Sie mir eine Standardreferenz zeigen, die dies zeigt, ist dies UB. ich werde

  1. Hinterlasse die Antwort.
  2. Setzen Sie alle Kappen ein, das ist dumm und ich bin falsch, dass alle lesen.

Dies ist kein Argument:

Nicht alles auf der ganzen Welt ist durch den C ++ - Standard definiert. Öffne deinen Geist.

Martin York
quelle
2
Nach wem? Nach welcher Passage? Der *führt eine Dereferenzierung durch. Es ist der Dereferenzierungsoperator. Das macht es. Die Tatsache, dass Sie dann einen neuen Zeiger auf den resultierenden Wert (using &) erhalten, ist wohl irrelevant. Sie können nicht einfach eine Sequenz von Bewertungen präsentieren, die Semantik des endgültigen Ausdrucks präsentieren und so tun, als ob die Zwischenschritte nicht stattgefunden hätten (oder dass die Regeln der Sprache nicht für jeden zutreffen).
Leichtigkeitsrennen im Orbit
3
Ich zitiere aus derselben Passage: the result is an lvalue referring to the object or function to which the expression points.Es ist klar, dass, wenn kein solches Objekt existiert, für diesen Operator kein Verhalten definiert ist. Ihre nachfolgende Aussage is returning a lvalue referring to the int (no de-refeference)macht für mich keinen Sinn. Warum denkst du, dass dies keine Dereferenzierung ist?
Leichtigkeitsrennen im Orbit
2
It returns a reference to what is being pointed at.Was ist das, wenn nicht eine Dereferenzierung? Die Passage besagt, dass *eine Indirektion durchgeführt wird, und die Indirektion von Zeiger zu Pointee wird als Dereferenzierung bezeichnet. Ihr Argument behauptet im Wesentlichen, dass Zeiger und Verweise dasselbe sind oder zumindest implizit verknüpft sind, was einfach nicht wahr ist. int x = 0; int* ptr = &x; int& y = *x;Hier dereferenziere ich x. Ich muss nicht verwenden y, damit das wahr ist.
Leichtigkeitsrennen im Orbit
2
@LokiAstari: Ich habe eine Frage, was bedeutet "Dereferenzierung" Ihrer Meinung nach, wenn nicht "den unären *Operator aufrufen, der einen Wert zurückgibt, der sich auf das Objekt bezieht, auf das der Ausdruck zeigt"? (Beachten Sie, dass der nachfolgende Satz des Standards diesen Prozess als Dereferenzierung in der C ++ 11-Spezifikation bezeichnet.)
Mooing Duck
2
@LokiAstari: Ich habe dir vor Tagen und Tagen Referenzen gezeigt ; Sie weigern sich einfach aus irgendeinem Grund anzuerkennen, dass sie existieren. Wie Sie dieses Verhalten rechtfertigen können, ist mir ein Rätsel, aber Sie müssen derjenige sein, der trollt.
Leichtigkeitsrennen im Orbit
2

Arbeitsentwurf ( n2798 ):

"Das Ergebnis des Operators unary & ist ein Zeiger auf seinen Operanden. Der Operand muss ein Wert oder eine qualifizierte ID sein. Wenn der Typ des Ausdrucks im ersten Fall" T "ist, ist der Typ des Ergebnisses" Zeiger auf T. "" (S. 103)

Array [5] ist keine qualifizierte ID, soweit ich das beurteilen kann (die Liste befindet sich auf S. 87). Der nächste scheint ein Bezeichner zu sein, während ein Array ein Bezeichner ist, ist dies bei einem Array [5] nicht der Fall. Es ist kein l-Wert, weil "Ein l-Wert bezieht sich auf ein Objekt oder eine Funktion" (S. 76). Array [5] ist offensichtlich keine Funktion und es wird nicht garantiert, dass es sich auf ein gültiges Objekt bezieht (da Array + 5 hinter dem zuletzt zugewiesenen Array-Element steht).

Natürlich kann es in bestimmten Fällen funktionieren, aber es ist nicht gültig C ++ oder sicher.

Hinweis: Es ist zulässig, eine hinzuzufügen, um eine über das Array hinaus zu erhalten (S. 113):

"Wenn der Ausdruck P [ein Zeiger] auf das letzte Element eines Array-Objekts zeigt, zeigt der Ausdruck (P) +1 eins nach dem letzten Element des Array-Objekts, und wenn der Ausdruck Q eins nach dem letzten Element eines Array-Objekt, der Ausdruck (Q) -1 zeigt auf das letzte Element des Array-Objekts. Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Array-Objekts zeigen oder eines nach dem letzten Element des Array-Objekts, die Auswertung darf keinen Überlauf erzeugen "

Es ist jedoch nicht legal, dies mit & zu tun.

Matthew Flaschen
quelle
1
Ich habe dich gestimmt, weil du richtig bist. Es gibt keine Garantie dafür, dass sich das Objekt am Ort nach dem Ende befindet. Die Person, die Sie herabgestuft hat, hat Sie wahrscheinlich missverstanden (Sie scheinen zu sagen, dass sich ein Array-Index-Op überhaupt auf kein Objekt bezieht). Ich denke hier ist eine interessante Sache: Es ist ein Wert, aber es bezieht sich auch nicht auf ein Objekt. Und so ist hier ein Widerspruch zu dem, was der Standard sagt. Und so ergibt sich ein undefiniertes Verhalten :) Dies hängt auch mit diesem zusammen: open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232
Johannes Schaub - litb
2
@jalf, in der Notiz steht "das könnte sich an dieser Adresse befinden". Es ist nicht garantiert, dass es eine gibt :)
Johannes Schaub - litb
1
Der Standard besagt, dass das Ergebnis von op * ein l-Wert sein muss, aber er sagt nur, was dieser l-Wert ist, wenn der Operand ein Zeiger ist, der tatsächlich auf ein Objekt zeigt. Das würde (bizarrerweise) bedeuten, dass die Implementierung, wenn einer nach dem Ende nicht zufällig auf ein geeignetes Objekt zeigte, einen geeigneten Wert von irgendwo anders finden und diesen verwenden müsste. Das würde & Array [Größe des Arrays] wirklich durcheinander bringen!
CB Bailey
3
"Ich denke jedoch immer noch, dass es kein l-Wert ist. Da es kein Objekt gibt, das garantiert in Array [5] ist, kann Array [5] nicht legal / auf / auf ein Objekt verweisen." <- Genau deshalb denke ich, dass es undefiniertes Verhalten ist: Es beruht auf einem Verhalten, das nicht explizit durch den Standard spezifiziert ist, und fällt daher unter 1.3.12 [defns.undefined]
Johannes Schaub - litb
1
litb, fair genug. Nehmen wir an, es ist / nicht definitiv / ein Wert und somit / definitiv nicht / 100% sicher.
Matthew Flaschen
2

Auch wenn es legal ist, warum von der Konvention abweichen? Array + 5 ist sowieso kürzer und meiner Meinung nach besser lesbar.

Bearbeiten: Wenn Sie möchten, dass es symmetrisch ist, können Sie schreiben

int* array_begin = array; 
int* array_end = array + 5;
rlbond
quelle
Ich denke, dass der Stil, den ich in der Frage verwende, symmetrischer aussieht: die Array-Deklaration und die Start- / End-Zeiger, oder manchmal übergebe ich diese direkt an eine STL-Funktion. Deshalb benutze ich es anstelle der kürzeren Version.
Zan Lynx
Um symmetrisch zu sein, müsste es Array_begin = Array + 0 sein. array_end = array + 5; Wie ist das für eine lange verspätete Kommentarantwort?
Zan Lynx
Es könnte ein Weltrekord sein :)
Rlbond
1

Es sollte aus folgenden Gründen ein undefiniertes Verhalten sein:

  1. Der Versuch, auf Elemente außerhalb der Grenzen zuzugreifen, führt zu undefiniertem Verhalten. Daher verbietet der Standard nicht, dass eine Implementierung in diesem Fall eine Ausnahme auslöst (dh eine Implementierung, die die Grenzen überprüft, bevor auf ein Element zugegriffen wird). Wenn dies & (array[size])definiert wäre begin (array) + size, würde eine Implementierung, die im Falle eines nicht gebundenen Zugriffs eine Ausnahme auslöst, nicht mehr dem Standard entsprechen.

  2. Es ist unmöglich, diese Ausbeute zu erzielen, end (array)wenn das Array kein Array, sondern ein beliebiger Auflistungstyp ist.

JohnB
quelle
0

C ++ Standard, 5.19, Absatz 4:

Ein Adresskonstantenausdruck ist ein Zeiger auf einen l-Wert .... Der Zeiger muss explizit mit dem Operator unary & ... oder mit einem Ausdruck vom Typ array (4.2) ... erstellt werden. Der Subskriptionsoperator [] ... kann bei der Erstellung eines Adresskonstantenausdrucks verwendet werden, auf den Wert eines Objekts darf jedoch mit diesen Operatoren nicht zugegriffen werden. Wenn der Subskriptionsoperator verwendet wird, muss einer seiner Operanden ein integraler konstanter Ausdruck sein.

Sieht für mich so aus, als wäre & array [5] legal C ++, da es sich um einen Ausdruck mit konstanter Adresse handelt.

David Thornley
quelle
1
Ich bin mir nicht sicher, ob es sich bei der ursprünglichen Frage unbedingt um ein Array mit statischem Speicher handelt. Auch wenn ich mich frage, ob & array [5] kein Adresskonstantenausdruck ist, gerade weil er nicht auf einen Wert verweist, der ein Objekt bezeichnet?
CB Bailey
Ich denke nicht, dass es wichtig ist, ob das Array statisch oder stapelweise zugewiesen ist.
David Thornley
Dies ist der Fall, wenn Sie auf 5.19 verweisen. Der Teil, mit dem Sie sich befasst haben, sagt "... ein Objekt mit statischer Speicherdauer, ein Zeichenfolgenliteral oder eine Funktion. ...". Dies bedeutet, dass Sie 5.19 nicht verwenden können, um über die Gültigkeit dieser Ausdrücke nachzudenken, wenn Ihr Ausdruck ein vom Stapel zugewiesenes Array enthält.
CB Bailey
Ihr Zitat besagt, dass wenn dies &array[5]legal ist und sich auf ein Array mit statischer Speicherdauer bezieht, dies eine Adresskonstante wäre. (Vergleiche &array[99]zum Beispiel, kein Text in diesem Absatz unterscheidet zwischen diesen beiden Fällen).
MM
-1

Wenn Ihr Beispiel KEIN allgemeiner, sondern ein spezifischer Fall ist, ist dies zulässig. Sie können legal , AFAIK, einen über den zugewiesenen Speicherblock hinaus verschieben. Dies funktioniert jedoch nicht für einen generischen Fall, dh wenn Sie versuchen, auf Elemente zuzugreifen, die um 1 vom Ende eines Arrays entfernt sind.

Gerade gesucht C-Faq: Link-Text

Aditya Sehgal
quelle
Die oberste Antwort lautet "es ist legal" und ich sage auch das Gleiche. Warum dann die Abstimmung runter :). Stimmt etwas mit meiner Antwort nicht?
Aditya Sehgal
-2

Es ist vollkommen legal.

Die Vektor <> Template-Klasse aus der STL macht genau das, wenn Sie myVec.end () aufrufen: Sie erhalten einen Zeiger (hier als Iterator), der ein Element nach dem Ende des Arrays zeigt.

Codymanix
quelle
Dies geschieht jedoch über Zeigerarithmetik, nicht durch Bilden eines Verweises auf Past-the-End und anschließendes Anwenden des Adressoperators auf diesen Wert.
Ben Voigt