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 + 5
oder &array[4] + 1
?
c++
c
standards
language-lawyer
Zan Lynx
quelle
quelle
Antworten:
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:
(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:
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:
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 + 5
Dereferenziert eigentlich nichts, sondern erstellt einfach einen Zeiger auf einen nach dem Ende vonarray
.&array[4] + 1
Dereferenzenarray+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.
quelle
Ja, es ist legal. Aus dem C99-Standardentwurf :
§6.5.2.1, Absatz 2:
§6.5.3.2, Absatz 3 (Schwerpunkt Mine):
§6.5.6, Absatz 8:
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.quelle
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.
quelle
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:
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.
quelle
&*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.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.
Wie an anderer Stelle erwähnt,
array + 5
und&array[0] + 5
gültig sind und auch Arten definiert über das Ende des Array einen Zeiger einen zu erhalten.quelle
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).
quelle
Das ist legal:
int array[5]; int *array_begin = &array[0]; int *array_end = &array[5];
Damit können wir also sagen, dass array_end auch äquivalent ist:
int *array_end = &(*((array) + 5)); // or &(*(array + 5))
Der wichtige Teil des oben genannten:
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
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 * operator
Zugriff 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 dieunary * operator
Rückgabe alvalue 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 vomunary & operator
Operator verwendet, der die Adresse des Objekts zurückgibt, auf das derlvalue 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
Dies ist kein Argument:
quelle
*
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).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 Aussageis returning a lvalue referring to the int (no de-refeference)
macht für mich keinen Sinn. Warum denkst du, dass dies keine Dereferenzierung ist?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 ichx
. Ich muss nicht verwendeny
, damit das wahr ist.*
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.)Arbeitsentwurf ( n2798 ):
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):
Es ist jedoch nicht legal, dies mit & zu tun.
quelle
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;
quelle
Es sollte aus folgenden Gründen ein undefiniertes Verhalten sein:
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ärebegin (array) + size
, würde eine Implementierung, die im Falle eines nicht gebundenen Zugriffs eine Ausnahme auslöst, nicht mehr dem Standard entsprechen.Es ist unmöglich, diese Ausbeute zu erzielen,
end (array)
wenn das Array kein Array, sondern ein beliebiger Auflistungstyp ist.quelle
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.
quelle
&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).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
quelle
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.
quelle