Beinhaltet die Initialisierung eine lvalue-to-rvalue-Konvertierung? Ist `int x = x;` UB?

Lesezeit: 12 Minuten

Beinhaltet die Initialisierung eine lvalue to rvalue Konvertierung Ist int x
Kerrek SB

Der C++-Standard enthält in 3.3.2, „Deklarationspunkt“ ein halbwegs berühmtes Beispiel für „überraschende“ Namenssuche:

int x = x;

Dies initialisiert x mit sich selbst, was (als primitiver Typ) ist nicht initialisiert und hat daher einen unbestimmten Wert (vorausgesetzt, es handelt sich um eine automatische Variable).

Ist das tatsächlich undefiniertes Verhalten?

Gemäß 4.1 „L-Wert-zu-r-Wert-Konvertierung“ ist es ein undefiniertes Verhalten, eine L-Wert-zu-r-Wert-Konvertierung für einen nicht initialisierten Wert durchzuführen. Tut die rechte Hand x diesen Umbau durchführen? Wenn ja, würde das Beispiel tatsächlich undefiniertes Verhalten haben?

  • Ich habe das Gefühl, dass das Verhalten ziemlich definiert ist. Der Wert von x wird sich nicht ändern. Der Wert von xist jedoch undefiniert.

    – Bingo

    18. Februar 2013 um 12:55 Uhr

  • @Bingo: Wenn du das denkst, kannst du ein aus dem Sprachstandard abgeleitetes Argument formulieren und es als Antwort posten? 🙂

    – Kerrek SB

    18. Februar 2013 um 12:56 Uhr

  • Ist es nicht dasselbe wie zu fragen, ob int y; int x = y; ist UB? [edit: Hm, probably no. This is about computing the value of an unitialized variable, not a default-initialized one]

    – Andy Prowl

    18. Februar 2013 um 13:22 Uhr


  • Ich frage mich, ob die Lebensdauer von x begann notwendigerweise in dem Moment, in dem es auf der rechten Seite bewertet wurde. Ist angegeben, dass der Teil auf der linken Seite (der meiner Meinung nach Speicher für x) wird vor dem Teil auf der rechten Seite sequenziert?

    – Andy Prowl

    18. Februar 2013 um 22:24 Uhr

  • @AndyProwl: Die automatische Speicherzuweisung ist überhaupt nicht sequenziert. Nur Ausdrucksauswertung ist etwas, das sequenziert werden kann. Eine Deklarationsanweisung ist kein Ausdruck.

    – Kerrek SB

    18. Februar 2013 um 23:14 Uhr


AKTUALISIEREN: Nach der Diskussion in den Kommentaren habe ich am Ende dieser Antwort weitere Beweise hinzugefügt.


Haftungsausschluss: Ich gebe zu, diese Antwort ist ziemlich spekulativ. Die aktuelle Formulierung des C++11-Standards hingegen scheint eine formellere Antwort nicht zuzulassen.


Im Zusammenhang mit diese Fragen und Antwortenhat sich herausgestellt, dass der C++11-Standard nicht formal spezifiziert, was Wertkategorien werden von jedem Sprachkonstrukt erwartet. Im Folgenden werde ich mich hauptsächlich darauf konzentrieren eingebaute Operatorenobwohl es um die Frage geht Initialisierer. Schließlich werde ich die Schlussfolgerungen, die ich für den Fall der Operatoren gezogen habe, auf den Fall der Initialisierer ausdehnen.

Bei eingebauten Operatoren finden sich trotz fehlender formaler Spezifikation (nicht normative) Hinweise in der Norm, dass die vorgesehen Spezifikation ist zu Lassen Sie prvalues ​​erwartet werden, wo immer ein Wert benötigt wird und wenn nicht anders angegeben.

Beispielsweise heißt es in einer Anmerkung in Absatz 3.10/1:

Die Diskussion jedes eingebauten Operators in Abschnitt 5 gibt die Kategorie des Werts an, den er liefert, und die Wertkategorien der Operanden, die er erwartet. Zum Beispiel, Die eingebauten Zuweisungsoperatoren erwarten, dass der linke Operand ein lvalue und der rechte Operand ein prvalue ist und liefern als Ergebnis einen lvalue. Benutzerdefinierte Operatoren sind Funktionen, und die Kategorien von Werten, die sie erwarten und zurückgeben, werden durch ihre Parameter und Rückgabetypen bestimmt

Abschnitt 5.17 zu Zuweisungsoperatoren hingegen erwähnt dies nicht. Die Möglichkeit, eine lvalue-to-rvalue-Konvertierung durchzuführen, wird jedoch erneut in einem Hinweis (Absatz 5.17/1) erwähnt:

Daher darf ein Funktionsaufruf nicht zwischengeschaltet werden die lvalue-zu-rvalue-Konvertierung und die Nebenwirkung, die mit jedem einzelnen zusammengesetzten Zuweisungsoperator verbunden ist

Wenn kein rvalue erwartet würde, wäre dieser Hinweis natürlich bedeutungslos.

Ein weiterer Beweis findet sich in 4/8, wie Johannes Schaub in den Kommentaren zu verlinkten Fragen und Antworten darauf hingewiesen hat:

Es gibt einige Kontexte, in denen bestimmte Konvertierungen unterdrückt werden. Beispielsweise wird die lvalue-zu-rvalue-Konvertierung nicht für den Operanden des unären &-Operators durchgeführt. Spezifische Ausnahmen sind in den Beschreibungen dieser Operatoren und Kontexte angegeben.

Dies scheint zu implizieren, dass die lvalue-zu-rvalue-Konvertierung für alle Operanden der integrierten Operatoren durchgeführt wird, sofern nicht anders angegeben. Das würde wiederum das bedeuten rvalues ​​werden als Operanden von integrierten Operatoren erwartet, sofern nicht anders angegeben.


VERMUTUNG:

Auch wenn die Initialisierung keine Zuweisung ist und daher Operatoren nicht in die Diskussion eintreten, ist mein Verdacht, dass dieser Bereich der Spezifikation von genau dem oben beschriebenen Problem betroffen ist.

Spuren, die diesen Glauben stützen, finden sich sogar in Abschnitt 8.5.2/5 über die Initialisierung von Verweise (für den der Wert des lvalue-Initialisierungsausdrucks nicht benötigt wird):

Die üblich lvalue-to-rvalue (4.1), array-to-pointer (4.2) und function-to-pointer (4.3) Standardkonvertierungen sind nicht erforderlich und werden daher unterdrückt, wenn solche direkten Bindungen an lvalues ​​vorgenommen werden.

Das Wort “üblich” scheint anzudeuten, dass bei der Initialisierung von Objekten, die nicht von einem Referenztyp sind, eine lvalue-zu-rvalue-Konvertierung angewendet werden soll.

Daher glaube ich, dass, obwohl Anforderungen an die Erwartungswertkategorie von Initialisierern schlecht spezifiziert sind (wenn nicht sogar vollständig fehlen), es aufgrund der bereitgestellten Beweise sinnvoll ist anzunehmen, dass die vorgesehen Spezifikation ist, dass:

Überall dort, wo ein Wert von einem Sprachkonstrukt benötigt wird, wird ein Prvalue erwartet, sofern nicht anders angegeben.

Unter dieser Annahme wäre in Ihrem Beispiel eine lvalue-to-rvalue-Konvertierung erforderlich, was zu undefiniertem Verhalten führen würde.


ZUSÄTZLICHE BEWEISE:

Nur um weitere Beweise zu liefern, die diese Vermutung stützen, lassen Sie uns davon ausgehen falschsodass für die Kopierinitialisierung tatsächlich keine lvalue-zu-rvalue-Konvertierung erforderlich ist, und betrachten Sie den folgenden Code (danke an jogojapan für den Beitrag):

int y;
int x = y; // No UB
short t;
int u = t; // UB! (Do not like this non-uniformity, but could accept it)
int z;
z = x; // No UB (x is not uninitialized)
z = y; // UB! (Assuming assignment operators expect a prvalue, see above)
       // This would be very counterintuitive, since x == y

Dieses ungleichmäßige Verhalten ergibt für mich nicht viel Sinn. Was meiner Meinung nach sinnvoller ist, ist, dass überall dort, wo ein Wert erforderlich ist, ein Prvalue erwartet wird.

Darüber hinaus ist, wie Jesse Good in seiner Antwort zu Recht feststellt, der Schlüsselabsatz des C++-Standards 8.5/16:

— Andernfalls ist der Anfangswert des Objekts, das initialisiert wird, der
(möglicherweise konvertierter) Wert des Initialisierungsausdrucks. Standardkonvertierungen (Klausel 4) werden verwendet, im Bedarfsfallum den Initialisierungsausdruck in die CV-unqualifizierte Version des Ziels zu konvertieren Art; Es werden keine benutzerdefinierten Konvertierungen berücksichtigt. Wenn die Konvertierung nicht durchgeführt werden kann, ist die Initialisierung falsch formatiert. [ Note:
An expression of type “cv1 T” can initialize an object of type “cv2 T”
independently of the cv-qualifiers cv1 and cv2.

However, while Jesse mainly focuses on the “if necessary” bit, I would also like to stress the word “type“. The paragraph above mentions that standard conversions will be used “if necessary” to convert to the destination type, but does not say anything about category conversions:

  1. Will category conversions be performed if needed?
  2. Are they needed?

For what concerns the second question, as discussed in the original part of the answer, the C++11 Standard currently does not specify whether category conversions are needed or not, because nowhere it is mentioned whether copy-initialization expects a prvalue as an initializer. Thus, a clear-cut answer is impossible to give. However, I believe I provided enough evidence to assume this to be the intended specification, so that the answer would be “Yes”.

As for the first question, it seems reasonable to me that the answer is “Yes” as well. If it were “No”, obviously correct programs would be ill-formed:

int y = 0;
int x = y; // y is lvalue, prvalue expected (assuming the conjecture is correct)

To sum it up (A1 = “Answer to question 1“, A2 = “Answer to question 2“):

          | A2 = Yes   | A2 = No |
 ---------|------------|---------|
 A1 = Yes |     UB     |  No UB  | 
 A1 = No  | ill-formed |  No UB  |
 ---------------------------------

If A2 is “No”, A1 does not matter: there’s no UB, but the bizarre situations of the first example (e.g. z = y giving UB, but not z = x even though x == y) show up. If A2 is “Yes”, on the other hand, A1 becomes crucial; yet, enough evidence has been given to prove it would be “Yes”.

Therefore, my thesis is that A1 = “Yes” and A2 = “Yes”, and we should have Undefined Behavior.


FURTHER EVIDENCE:

This defect report (courtesy of Jesse Good) proposes a change that is aimed at giving Undefined Behavior in this case:

[…] Außerdem 4.1 [conv.lval] Absatz 1 besagt, dass die Anwendung der lvalue-zu-rvalue-Konvertierung auf ein „object [that] ist nicht initialisiert“ führt zu undefiniertem Verhalten; dies sollte in Begriffen eines Objekts mit einem unbestimmten Wert umformuliert werden.

Insbesondere lautet der vorgeschlagene Wortlaut für Absatz 4.1:

Wenn eine lvalue-zu-rvalue-Konvertierung in einem nicht ausgewerteten Operanden oder einem Unterausdruck davon erfolgt (Abschnitt 5 [expr]) wird nicht auf den im referenzierten Objekt enthaltenen Wert zugegriffen. In allen anderen Fällen wird das Ergebnis der Umrechnung nach folgenden Regeln ermittelt:

— Wenn T (möglicherweise CV-qualifiziert) std::nullptr_t ist, ist das Ergebnis eine Nullzeigerkonstante (4.10 [conv.ptr]).

— Andernfalls, wenn der glvalue T einen Klassentyp hat, kopiert die Konvertierung ein temporäres vom Typ T aus dem glvalue und das Ergebnis der Konvertierung ist ein prvalue für das temporäre.

— Andernfalls, wenn das Objekt, auf das sich der glvalue bezieht, einen ungültigen Zeigerwert enthält (3.7.4.2 [basic.stc.dynamic.deallocation]3.7.4.3 [basic.stc.dynamic.safety]), ist das Verhalten implementierungsdefiniert.

— Andernfalls, wenn T ein (möglicherweise CV-qualifizierter) Zeichentyp ohne Vorzeichen ist (3.9.1 [basic.fundamental]), und das Objekt, auf das sich der glvalue bezieht, enthält einen unbestimmten Wert (5.3.4 [expr.new]8.5 [dcl.init]12.6.2 [class.base.init]) und dieses Objekt keine automatische Speicherdauer hat oder der glvalue der Operand eines unären &-Operators war oder an eine Referenz gebunden war, ist das Ergebnis ein nicht spezifizierter Wert. [Footnote: The value may be different each time the lvalue-to-rvalue conversion is applied to the object. An unsigned char object with indeterminate value allocated to a register might trap. —end footnote]

Andernfalls, wenn das Objekt, auf das sich der glvalue bezieht, einen unbestimmten Wert enthält, ist das Verhalten undefiniert.

— Andernfalls, wenn der glvalue den (möglicherweise CV-qualifizierten) Typ std::nullptr_t hat, ist das prvalue-Ergebnis eine Nullzeigerkonstante (4.10 [conv.ptr]). Andernfalls ist der Wert, der in dem durch glvalue angegebenen Objekt enthalten ist, das prvalue-Ergebnis.

  • Hm, in deinem Beitrag wird viel über “Operatoren” geredet, aber meine Frage hat nichts mit Operatoren zu tun …

    – Kerrek SB

    20. Februar 2013 um 23:52 Uhr

  • @KerrekSB: Ja, das ist mir bewusst. Deshalb habe ich meine Antwort als “Vermutung” markiert. Meine Vermutung ist, dass die Anforderungen an Wertkategorien für Operatoren ebenso wie für Initialisierer unspezifiziert gelassen wurden. Und da die beabsichtigte Spezifikation für Operatoren ist (EDIT: scheint zu sein), dass überall dort, wo ein Wert benötigt wird, ein Prvalue erwartet wird, sofern nicht anders angegeben, ist es IMO sinnvoll, die gleiche Annahme für Initialisierer zu treffen. Eine rein formale Antwort auf Ihre Frage kann leider nicht gegeben werden, da dem Standard selbst eine klar definierte Spezifikation fehlt.

    – Andy Prowl

    20. Februar 2013 um 23:56 Uhr


  • +1, eindeutig nützlich, obwohl ich nicht weiß, ob die Vermutung richtig ist.

    – Jogojapan

    22. Februar 2013 um 14:40 Uhr

  • @jogojapan: Danke. Ich auch nicht, weshalb ich es natürlich als Vermutung bezeichnet habe 😉 Allerdings macht es IMHO mehr Sinn, davon auszugehen, dass es wahr als falsch ist.

    – Andy Prowl

    22. Februar 2013 um 14:42 Uhr

  • Okay, gelöscht. Auch etwas verwandt ist Mängelanzeige 616 und die damit verbundenen Probleme, aber AFAICT deckt den Fall des OP nicht ab.

    – Jess Gut

    22. Februar 2013 um 23:46 Uhr

Eine implizite Konvertierungssequenz eines Ausdrucks e tippen T ist als äquivalent zu der folgenden Deklaration definiert, using t als Ergebnis der Konvertierung (Modulo-Wertkategorie, die abhängig von definiert wird T), 4p3 und 4p6

T t = e;

Die Auswirkung einer impliziten Konvertierung ist die gleiche wie die Durchführung der entsprechenden Deklaration und Initialisierung und die anschließende Verwendung der temporären Variablen als Ergebnis der Konvertierung.

In Klausel 4 liefert die Umwandlung eines Ausdrucks in einen Typ immer Ausdrücke mit einer bestimmten Eigenschaft. Zum Beispiel die Konvertierung von 0 zu int* ergibt einen Null-Zeigerwert und nicht nur einen beliebigen Zeigerwert. Auch die Wertekategorie ist eine spezifische Eigenschaft eines Ausdrucks und ihr Ergebnis ist wie folgt definiert

Das Ergebnis ist ein lvalue, wenn T ein lvalue-Referenztyp oder eine rvalue-Referenz auf den Funktionstyp (8.3.2) ist, ein xvalue, wenn T eine rvalue-Referenz auf den Objekttyp ist, und andernfalls ein prvalue.

Daher wissen wir das in int t = e;das Ergebnis der Konvertierungssequenz ist ein Prvalue, weil int ist ein Nicht-Referenztyp. Wenn wir also einen glvalue angeben, benötigen wir offensichtlich eine Konvertierung. 3.10p2 verdeutlicht das weiter, um keinen Zweifel zu lassen

Immer wenn ein glvalue in einem Kontext erscheint, in dem ein prvalue erwartet wird, wird der glvalue in einen prvalue umgewandelt; siehe 4.1, 4.2 und 4.3.

  • (Ich würde Ihnen gerne ein Kopfgeld geben, aber das Mindest-Kopfgeld, das ich geben kann, ist 300 — bin ich so geizig oder billig? :-))

    – Kerrek SB

    7. Juli 2013 um 18:05 Uhr

  • Kasse dieser Vorschlag.

    – Kerrek SB

    15. Juli 2013 um 22:12 Uhr

  • @kerrek Ich kenne diesen Vorschlag bereits. Es ist gut, dass sie klarere Regeln aufstellen, anstatt schwache englische umgangssprachliche Begriffe zu verwenden.

    – Johannes Schaub – litb

    15. Juli 2013 um 23:08 Uhr

dies ist kein undefiniertes Verhalten. Sie kennen nur seine spezifischen Werte nicht, weil es keine Initialisierung gibt. Wenn die Variable ein globaler und eingebauter Typ ist, wird sie vom Compiler auf den richtigen Wert initialisiert. Wenn die Variable lokal ist, wird sie vom Compiler nicht initialisiert, also werden alle Variablen für Sie selbst initialisiert, verlassen Sie sich nicht auf den Compiler.

  • im Falle des automatischen Typs ist es ein Fehler. ` Variable ‘auto x’ mit ‘auto’-Typ, die in ihrem eigenen Initialisierer verwendet wird `

    – Arpit

    18. Februar 2013 um 12:56 Uhr

  • @Arpit: Es gibt keine auto in der Frage (und das bedeutet nicht “automatisch”!).

    – Kerrek SB

    18. Februar 2013 um 12:58 Uhr


  • Oh! Ich betrachte nur die automatische Variable für den automatischen Typ. mein Fehler

    – Arpit

    18. Februar 2013 um 13:00 Uhr

  • @Arpit: auto ist kein Typ. Es ist ein Schlüsselwort.

    – Kerrek SB

    18. Februar 2013 um 13:25 Uhr

  • @KerrekSB Sei nicht so ernst.:) Ich weiß, es ist ein Typbezeichner.

    – Arpit

    18. Februar 2013 um 13:28 Uhr

Das Verhalten ist nicht undefiniert. Die Variable ist nicht initialisiert und behält den zufälligen Wert, mit dem nicht initialisierte Werte beginnen. Ein Beispiel aus dem Clan’g-Testanzug:

int test7b(int y) {
  int x = x; // expected-note{{variable 'x' is declared here}}
  if (y)
    x = 1;
  // Warn with "may be uninitialized" here (not "is sometimes uninitialized"),
  // since the self-initialization is intended to suppress a -Wuninitialized
  // warning.
  return x; // expected-warning{{variable 'x' may be uninitialized when used here}}
}

Was Sie darin finden können clang/test/Sema/uninit-Variablen.c Tests für diesen Fall explizit.

991600cookie-checkBeinhaltet die Initialisierung eine lvalue-to-rvalue-Konvertierung? Ist `int x = x;` UB?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy