(Warum) ist die Verwendung einer nicht initialisierten Variablen-Undefinierte Verhalten?
Wenn ich:
unsigned int x;
x -= x;
es ist klar, dass x
sollte null nach diesem Ausdruck, aber überall sehe ich, Sie sagen die Verhalten dieser code ist undefiniert, nicht bloß der Wert der x
(bis vor der Subtraktion).
Zwei Fragen:
-
Ist die Verhalten dieser code in der Tat undefiniert?
(E. g. Könnte die code-crash [oder schlechter], die auf einem kompatiblen system?) -
Wenn dem so ist, warum nicht C sagen, dass die Verhalten ist undefiniert, wenn es ist völlig klar, dass
x
sollte null sein hier?also, Was ist die Vorteil gegeben, indem Sie nicht definieren das Verhalten hier?
Klar, der compiler könnte verwenden Sie einfach was Müll Wert, den es als "handy" in der Variablen, und es funktionieren würde, wie gedacht... was ist falsch mit diesem Ansatz?
- möglich, Duplikat der Warum macht der C-standard lassen Sie die Verwendung von unbestimmten Variablen undefiniert?
- Was ist der Vorteil gegeben durch die Festlegung eines speziellen Fall für das Verhalten hier? Sicher, können alle unsere Programme und Bibliotheken, die größer und langsamer, da @Mehrdad vermeiden will, die Initialisierung einer variable in einer bestimmten und seltenen Fall.
- Wenn man sich die Antwort gibt, es ist die Beantwortung eine etwas andere Frage (warum C nicht zu initialisieren-Variablen), nicht, warum das Verhalten nicht definiert.
- Ich Stimme nicht mit, dass ein dupe. Unabhängig davon, ob der Wert, den es dauert, die OP erwartet, dass es zu null nach
x -= x
. Die Frage ist, warum der Zugriff auf nicht initialisierte Werte überhaupt ist UB. - Es ist interessant, dass die Aussage x=0; ist in der Regel umgewandelt xor x,x in der Montage. Es ist fast das gleiche wie das, was Sie hier zu tun versuchen, aber mit xor statt der Subtraktion.
- Es gibt auch Was passiert mit einem erklärt, nicht initialisierte variable, die in C -- hat es einen Wert, dessen akzeptierte Antwort auf jeden Fall funktioniert-Adresse UB.
- 'also, Was ist der Vorteil von nicht definieren das Verhalten hier? - Ich hätte gedacht, dass der Vorteil der standard-Auflistung nicht die Unendlichkeit der Ausdrücke mit Werten, verlassen Sie sich nicht auf eine oder mehrere Variablen eindeutig ist. Zur gleichen Zeit, @Paul, solche eine änderung der Norm nicht machen würde Programme und Bibliotheken, die alle größer.
- Ähnlich: stackoverflow.com/questions/25074180/...
- Sie sollten wahrscheinlich link, das andere für das eine, in Anbetracht dieser kam 2 Jahre früher.
- OK, habe eine Kommentar-link. Beide Fragen haben guten und etablierten Antworten, damit schließen als Duplikat, ist wohl nicht angebracht, obwohl vielleicht ein moderator machen könnte Zusammenführen.
- Ja, wir können wohl lassen als Sie ist, Sie sind nicht ganz Duplikate, denke ich.
- So dass unbestimmte Werte seltsam Verhalten erlauben können nützliche Optimierungen. Zum Beispiel, gegeben
uint16_t foo(void) {uint16_t result;
, gefolgt von verschiedenen Aussagen, von denen jede möglicherweise oder möglicherweise nicht Ergebnis schreiben und dannreturn result;}
es kann hilfreich sein, um den compiler zu haltenresult
in einer 32-bit-register und dann zurückkehren. Wenn alles speichert einen Wert als Ergebnis, der compiler wird dafür sorgen, den gespeicherten Wert 0..65535, aber wenn es nichts schreibtresult
ist, halten Sie die return-Wert innerhalb dieses Bereichs würde erfordern das hinzufügen einer extra-Anweisung. - Einer Ihrer typischen 4 Jahr später nicht sequiturs. Mein Kommentar war speziell auf das "- Ausdrücke mit den Werten, verlassen Sie sich nicht auf eine oder mehrere Variablen" - in diesem Fall
x - x
. Die Norm waren, um anzugeben, dassuint16_t foo(void) {uint16_t result; result -= result; return result;}
gibt 0 zurück, dies würde nicht konforme Programme und Bibliotheken größer. Wir haben keine Angst vor Buggys code produzieren größere binaries. Wir wollen, dass der compiler optimieren zu können konformen Programme nutzen zu undefiniertem Verhalten, und die Spezifikation nicht ändern würde, dass.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ja, das Verhalten ist undefiniert, aber aus anderen Gründen, als die meisten Menschen bewusst sind.
Erste ist, ein nicht initialisiertes Wert selbst nicht zu undefiniertem Verhalten, sondern der Wert ist einfach unbestimmt. Der Zugriff auf diese ist dann UB, wenn der Wert geschieht, um eine trap-Darstellung für den Typ. Vorzeichenlose Typen haben selten trap-Repräsentationen, so würden Sie relativ sicher auf dieser Seite.
Was macht, ist das Verhalten undefiniert ist eine zusätzliche Eigenschaft der Variablen, nämlich, dass es "gewesen sein könnte erklärt mit
register
" das ist seine Adresse niemals genommen. Solche Variablen werden speziell behandelt, da gibt es Architekturen, die echten CPU-Register, die eine Art von extra-Zustand der "nicht initialisierte" und nicht entsprechen, um einen Wert in den Typ der Domäne.Edit: relevanten Begriff der Norm 6.3.2.1p2:
Und deutlicher machen, den folgenden code ist legal unter allen Umständen:
a
undb
genommen werden, so ist Ihr Wert nurunbestimmt ist.
unsigned char
nie hat trap-Repräsentationendas unbestimmte Wert ist nur nicht angegeben ist, wird jeder Wert von
unsigned char
könntepassieren.
a
muss halten Sie den Wert0
.Edit2:
a
undb
haben unspezifische Werte:a
wird0
aber wie ist das garantiert den standard? Vor der Zuweisung wird der Wert vona
ist unbestimmt. Nicht, dass beinhalten, dass zwei Zugriffe könnten unterschiedliche Werte zurückgeben? Oder hat der C-standard garantiert, dass ein unbestimmter Wert gleich bleibt unbestimmt Wert zwischen zwei Zugriffen?0
.unsigned char
konstituierenden Teile geschrieben werden, die mit nicht-trap-Werte. Ist es erforderlich, dass Variablen, die nicht geschrieben werden muss, nicht-trap-Formen? Ich würde denken, dass ein compiler auf einer Maschine mit parity-Speicher überprüft (z.B. die original-IBM-PC) erlaubt sein sollte zu füllen undefinierten Speicher mit trap-Werte wurden wenn Sie so geneigt sind, so dass alle Holen würde auslösen einer Falle.int
könnte eine Falle sein. Wenn mit "auslösen der Falle" du meinst "wirft eine Umsetzung definiertes signal", dann ja, eine Implementierung umsetzen konnteint
so.unsigned
s können sicher trap-Repräsentationen. Kannst du zu dem Teil der Norm, die sagt so? Ich sehe in §6.2.6.2/1 das folgende: "Für unsigned integer-Typen andere als unsigned char, die bits der objectrepresentation sind in zwei Gruppen unterteilt werden: Wert bits and padding bits (es muss nicht jeder der letzteren). ... dies soll beknown als der Wert der Repräsentation. Die Werte von padding bits sind nicht spezifiziert. ⁴⁴ ⁾ " Mit dem Kommentar sagen: "⁴⁴⁾ Einige Kombinationen von padding bits generiert möglicherweise trap-Repräsentationen".unsigned char
, aber diese Antwort ist mitunsigned char
. Beachten Sie aber: eine streng konforme Programm berechnen kannsizeof(unsigned) * CHAR_BIT
und bestimmen, basierend aufUINT_MAX
, dass bestimmten Implementierungen kann unmöglich trap-Repräsentationen fürunsigned
. Nach diesem Programm hat auch die Entschlossenheit, Sie können dann fortfahren, genau das zu tun, was diese Antwort nicht mitunsigned char
.memcpy
eine Ablenkung, also wäre das nicht Ihr Beispiel noch gelten, wenn Sie wurden ersetzt durch*&a = *&b;
.unsigned char
und damitmemcpy
hilft, den für*&
ist weniger klar. Ich werde berichten, sobald diese zur Ruhe kommt.unsigned char
hat keine trap-Darstellung.unsigned char
garantiert.]uint16_t
wo jedes mögliche bit-Muster für das zugrunde liegende storage-würde definiert haben, Verhalten. Wenn solche Dinge nicht-trap-Repräsentationen, was sonst könnte Sie sein?uint16_t
Wert ist entweder eine gültige bit-Kombination oder eine trap-Darstellung auch in der Gegenwart nicht definiertes Verhalten, als Sie Recht. Wenn jedoch UB hebt alle anderen Anforderungen, dann kann es alles sein, einschließlich einer Nasen-Dämon Instanz.uint16_t
holding 65536), das würde bedeuten, dass der Akt des Lesens den lvalue auslösen würde, UB. Zu mir, das würde wiederum suggerieren, dass die lvalue hielt eine trap-Darstellung.&
Betreiber: "hätte erklärt mit der register-storage-Klasse (hatte nie seine Adresse genommen)"memcpy
Beispiel hätte UB.a -= a
würde immer noch zu einer indeterminite Wert, selbst wenn es nicht wahr ist UB. Tun Sie nicht einverstanden sind, und denke, ich bin off base gibt es?a -= a
Ergebnisse ina
noch unbestimmt (und nicht nur unspezifisch): unter diesem resultion, den scheinbaren Wert ist unspezifisch bei jeder Beobachtung (aka. "wabbelig")a=a; a -= a;
. Wenn die zweite Anweisung durchgeführt wurden isoliert, die zwei liesta
könnten, führen zu unterschiedlichen Werten, da auch nach dem ersten Lesen nichts wäre "set" der Wert vona
. Wenn ein Lesen einer Unbestimmten Wert garantiert ist, gibt einen beliebigen Wert, dann nacha=a;
,a
halten sollte, einige vielleicht unbekannte, aber nicht mehr Unbekannter Wert, also die entfernen-Rendite 0. Leider sind einige Compiler nicht erkennen, in irgendeiner Weise zwingt den compiler, um aus einem "wackligen" - Wert in ein brauchbares ein.uint32_t index = map[key]; if (index < numItems && values[index]=key) ItemFound(...) else ItemNotFound(,,.);
Wennkey
ist nicht in der Tabellemap[key]
zurückkehren konnte beliebiger nicht wackeliguint32_t
Wert und code wäre korrekt berichten, dass es nicht gefunden. Wennindex
zugewiesen bekommt einen wackligen Wert, obwohl, es gibt keinen Weg, um zu verhindern, dass eine out-of-bounds-array-fetch.C-standard gibt Compiler eine Menge Spielraum, um Optimierungen durchzuführen. Die Folgen dieser Optimierungen kann überraschend sein, wenn Sie davon ausgehen, eine naive Modell-Programme, wo nicht initialisierte Speicher wird eingestellt, um einige zufällige bit-Muster, und alle Operationen werden durchgeführt in der Reihenfolge, wie Sie geschrieben sind.
Hinweis: die folgenden Beispiele sind nur gültig, da
x
hat nie seine Adresse genommen, so ist es "registrieren-wie". Sie würde auch gültig sein, wenn der Typ vonx
hatte trap-Repräsentationen; dies ist selten der Fall für unsigned-Typen (es muss "verschwenden" mindestens ein bit an Speicher und muss dokumentiert werden), und unmöglich fürunsigned char
. Wennx
hatte eine signierte geben, dann könnte die Umsetzung definieren Sie die bit-Muster, das ist nicht eine Zahl, die zwischen -(2n-1-1) und 2n-1-1 als eine trap-Darstellung. Sehen Jens Gustedt Antwort.Compiler versuchen, zuweisen von Registern zu Variablen, denn Register sind schneller als Speicher. Da das Programm möglicherweise mehr Variablen als der Prozessor verfügt über Register, Compiler führen-register-Allokation, die dazu führt, unterschiedliche Variablen mit demselben register zu verschiedenen Zeiten. Betrachten Sie das Programm-fragment
Wenn in Zeile 3 ausgewertet wird,
x
ist noch nicht initialisiert, daher (Gründe der compiler) Zeile 3 muss irgendeine Art von Zufall, dass kann nicht passieren, wegen der anderen Bedingungen, dass der compiler nicht intelligent genug, um herauszufinden. Daz
nach der Linie 4 undx
ist nicht eingesetzt, bevor die Linie 5, die gleichen register verwendet werden kann für beide Variablen. Also dieses kleine Programm wird kompiliert, um die folgenden Operationen auf Registern:Den endgültigen Wert von
x
ist der Letzte Wert derr0
, und der endgültige Wert dery
ist der Letzte Wert derr1
. Diese Werte sind x = -3 und y = 4, und nicht 5 und 4 wie geschehen würde, wennx
wurde richtig initialisiert.Für eine aufwändigere Beispiel betrachten wir das folgende Codefragment:
Angenommen, dass der compiler erkennt, dass
condition
hat keine Nebenwirkung. Dacondition
nicht ändernx
, der compiler weiß, dass die ersten Durchlaufen der Schleife nicht möglich sein, den Zugriff aufx
da es nicht initialisiert, noch nicht. Deshalb ist die erste Ausführung der Schleife ist äquivalent zux = some_value()
gibt es keine Notwendigkeit zum testen der Bedingung. Der compiler kann diesen code kompilieren, als wenn Sie geschrieben hattenDem Weg dies kann modelliert werden innerhalb des Compilers ist zu beachten, dass jeder Wert je nach
x
hat was Wert ist praktisch solangex
ist nicht initialisiert. Denn das Verhalten, wenn eine nicht initialisierte variable ist nicht definiert, anstatt die variable lediglich mit einem unbestimmten Wert, den der compiler nicht brauchen, um zu halten verfolgen von jede spezielle mathematische Beziehung zwischen der was-ist-bequem-Werte. Damit der compiler analysieren kann den obigen code in dieser Art:x
nicht initialisiert ist, durch die Zeit, die-x
ausgewertet.-x
hat Undefiniertes Verhalten, also sein Wert ist unabhängig-ist-bequem.condition ? value : value
gilt, so dass dieser code kann vereinfacht werden zucondition; value
.Konfrontiert mit den code in deiner Frage, diese gleichen compiler analysiert, dass, wenn
x = - x
wird ausgewertet, der Wert von-x
ist, was-ist-bequem. So kann die Zuweisung optimiert werden entfernt.Habe ich noch nicht geschaut ein Beispiel für einen compiler, der sich so verhält, wie oben beschrieben, aber es ist die Art von Optimierung guter Compiler zu tun versuchen. Ich wäre nicht überrascht zu begegnen. Hier ist eine weniger plausible Beispiel eines Compilers, mit dem dein Programm stürzt ab. (Es kann nicht sein, dass man unglaubwürdig, wenn Sie, kompilieren Sie Ihr Programm in eine Art erweiterte debugging-Modus).
Diese hypothetische compiler ordnet jede variable in einem anderen Speicher Seite und stellt die Seite Attribute so, dass das Lesen von eine nicht initialisierte variable bewirkt, dass ein Prozessor Falle aufruft, die ein debugger. Jede Zuordnung zu einer variable macht Sie sicher, dass der memory-Seite zugeordnet ist normal. Dieser compiler nicht versuchen, führen Sie eine erweiterte Optimierung — es ist in einem debugging-Modus, soll zum Auffinden von bugs wie nicht initialisierten Variablen. Wenn
x = - x
ausgewertet wird, der rechten Seite bewirkt, dass eine Falle, und der debugger startet.x
hat einen nicht initialisierten Wert, sondern das Verhalten über den Zugang definiert werden würde, wenn x nicht registrieren müssen-wie Verhalten.x
, dann werden alle Operationen auf Sie könnte verzichtet werden, ob der Wert definiert war oder nicht. Wenn folgender code z.B.if (volatile1) x=volatile2; ... x = (x+volatile3) & 255;
wäre genauso glücklich mit einem Wert zwischen 0 und 255, diex
enthalten könnte, in dem Fall, wovolatile1
hatte, ergab null, ich würde denken, dass eine Umsetzung erlauben würde, dass die Programmierer das weglassen einer unnötigen schreibenx
angesehen werden sollte, da höhere Qualität, als ein Verhalten würde...Ja, das Programm Abstürzen. Es könnte zum Beispiel sein trap-Repräsentationen (bestimmtes bit-Muster, die nicht behandelt werden kann), die möglicherweise die Ursache für CPU-interrupt, der nicht behandelte, konnte das Programm zum Absturz bringen.
(Diese Erklärung gilt nur auf den Plattformen, wo
unsigned int
können trap-Repräsentationen, die selten ist, auf die echten Systeme; siehe Kommentar für Informationen und Verweise zu alternativen und vielleicht weitere häufige Ursachen, die dazu führen, den standard der aktuellen Formulierung.)NaN - NaN == NaN
, nicht null ist.unsigned
integer-Typen.(Diese Antwort-Adressen, C 1999. Für C 2011, siehe Jens Gustedt Antwort.)
C-standard nicht sagen, der Wert eines Objekts, der die automatische Speicherung Dauer, die nicht initialisiert ist Undefiniertes Verhalten. Die C 1999 standard sagt, in 6.7.8 10, "Wenn ein Objekt hat automatische Speicherdauer ist nicht explizit initialisiert, ist Ihr Wert unbestimmt." (Dieser Absatz geht auf um zu definieren, wie statische Objekte werden initialisiert, so dass die nur auf nicht initialisierte Objekte, die wir besorgt sind, sind automatische Objekte).
3.17.2 definiert "unbestimmte Wert" als "entweder eine unbestimmte Zahl oder eine trap-Darstellung". 3.17.3 definiert "unbekannter Wert" als "gültig" den Wert der betreffenden Art, wo diese Internationale Norm enthält keine Anforderungen auf, welcher Wert gewählt wird, in jedem Fall".
Also, wenn das nicht initialisierte
unsigned int x
verfügt über einen unbestimmten Wert, dannx -= x
produzieren muss null. Damit bleibt die Frage, ob es möglicherweise eine Falle sein Darstellung. Zugriff auf eine Falle Wert verursacht Undefiniertes Verhalten, pro 6.2.6.1 5.Einige Arten von Objekten, die möglicherweise trap-Repräsentationen, wie der signaling-NaNs der floating-point-zahlen. Aber unsigned-Ganzzahlen sind spezielle. Pro 6.2.6.2, die jeweils den Wert N bits eines unsigned int stellt eine Potenz von 2 ist, und jede Kombination von Wert-bits steht für einen der Werte von 0 bis 2N-1. Also vorzeichenlose Ganzzahlen können trap-Repräsentationen, die nur durch einige Werte in Ihrem padding-bits (wie ein parity-bit).
Wenn auf der Ziel-Plattform, ein unsigned int hat keine Polsterung-bits, dann wird eine nicht initialisierte unsigned int kann nicht eine trap-Darstellung, und über Ihren Wert nicht der schaltplanbearbeitung zu undefiniertem Verhalten.
x
hat eine trap-Darstellung, dannx -= x
könnte trap, nicht wahr? Trotzdem, +1 für den Hinweis auf vorzeichenlose Ganzzahlen mit keine zusätzlichen bits müssen definierte Verhalten -- es ist eindeutig das Gegenteil von den anderen Antworten und (nach dem Zitat) es scheint das zu sein, was der standard bedeutet.x
hat eine trap-Darstellung, dannx -= x
könnte Falle. Auch einfachx
als könnte der Wert trap. (Es ist sicher zu bedienenx
als lvalue; schreiben in ein Objekt nicht beeinflusst werden durch eine trap-Darstellung, die in es.)uint16_t foo(uint32_t x, uint32_t y, uint32_t z) { uint16_t q; if (x) q=x; return q; }
generiert code, die, wenn aufgerufen, von außen code, werden wieder alle 32 bits von z, wenn x ist null.foo()
wird umgewandelt in 32 bit unter der Annahme, dass die höheren bits der 32-bit-Position null sind, dann Hoppla. So ist es ein echtes problem, dass ich nicht denke der.Ja, es ist undefiniert. Der code kann zum Absturz führen. C sagt, das Verhalten ist undefiniert, weil es gibt keinen bestimmten Grund, um eine Ausnahme von der Allgemeinen Regel. Der Vorteil ist der gleiche Vorteil wie alle anderen Fälle von Undefiniertes Verhalten -- der compiler nicht zur Ausgabe spezieller code, um diese Arbeit zu machen.
Warum denkst du, dass das nicht passiert? Das ist genau der Ansatz. Der compiler ist nicht erforderlich, damit es funktioniert, aber es ist nicht erforderlich, um es scheitern.
x
könnte deklariert werden alsregister
, das ist, dass seine Adresse nie genommen. Ich weiß nicht, ob Sie sich dessen bewusst waren (wenn Sie versteckt hatten, ist es effektiv), aber eine richtige Antwort muss es erwähnen.uint16_t
halten können Werte außerhalb des range 0-65535. Wie wäre das zulässig, wenn solche Werte wurden nicht als trap-Repräsentationen?Für jede variable eines beliebigen Typs, die nicht initialisiert ist oder aus anderen Gründen besitzt einen unbestimmten Wert, das gilt für die code-Lesung Wert:
Sonst, wenn es keine trap-Repräsentationen, nimmt die variable einen unbestimmten Wert. Es gibt keine Garantie, dass dieser unspezifische Wert stimmt jedes mal, wenn die variable gelesen wird. Jedoch, es ist garantiert eine Falle Vertretung und deshalb ist es garantiert nicht aufrufen Undefiniertes Verhalten [3].
Den Wert kann man dann sicher verwendet werden, ohne dass ein Programm-Absturz, obwohl ein solcher code ist nicht portabel zu Systemen mit trap-Repräsentationen.
[1]: C11 6.3.2.1:
[2]: C11 6.2.6.1:
[3] C11:
unsigned char
ist ausgenommen von dieser aus obigen Gründen.stdint.h
sollte immer verwendet werden, statt der nativen Typen von C. Wegenstdint.h
erzwingt die 2-Komplement-und no padding bits. In anderen Worten, diestdint.h
Typen sind nicht erlaubt, werden voll der Mist.<stdint.h>
. Das optionale Exact-width integer types kein padding-bits, und ein Zweierkomplement-Darstellung. Der header hat auch erforderlich Minimum-Breite integer-Typen und Schnellste minimum-Breite integer-Typen nicht angegeben, werden keine Polsterung-bits, und ein Zweierkomplement-Darstellung.Während viele der Antworten konzentrieren sich auf Prozessoren, die Falle auf nicht initialisierte-register Zugriff, skurrile Verhaltensweisen, die entstehen können, sogar auf Plattformen, die keine solchen fallen, mit Compilern, die keine Besondere Anstrengung zu nutzen UB. Betrachten Sie den code:
einen compiler für eine Plattform wie den ARM, wo Sie alle Anweisungen anderen als
Lasten und Geschäfte betreiben, die auf 32-bit-Register vernünftigerweise Prozess der
code Sie in einer Weise entspricht:
Wenn entweder flüchtig liest sich die Ausbeute eines nicht-null-Wert, r0 geladen wird mit einem Wert im Bereich von 0...65535. Sonst wird es Ausbeute, was auch immer es stattfindet, wenn die Funktion aufgerufen wurde (d.h. der übergebene Wert in x), die möglicherweise nicht ein Wert im Bereich 0..65535. Der Standard fehlt jede Terminologie zu beschreiben, das Verhalten von Wert, dessen Typ uint16_t, aber deren Wert ist außerhalb des Bereichs von 0..65535, außer zu sagen, dass jede Handlung, die produzieren könnte ein solches Verhalten ruft UB.
uint16_t
, dass die variable vielleicht mal Lesen, wie 123 und manchmal 6553623). Wenn das Ergebnis ignoriert wird...register
, dann kann es zusätzliche bits, die das Verhalten potenziell nicht definiert. Das ist genau das, was du sagst, richtig?register
variable ist, dann hat der code Undefiniertes Verhalten -- auch wenn das nicht die Architektur, die Sie eigentlich abzielen. Versuchen Sie erneut es zu Lesen und lassen Sie mich wissen, wenn Sie nicht einverstanden sind.