Betrachten Sie dieses Programm:
#include <stdio.h>
int main(void)
{
unsigned int a;
printf("%u %u\n", a^a, a-a);
return 0;
}
Ist es undefiniertes Verhalten?
Auf den ersten Blick a
ist eine nicht initialisierte Variable. Das deutet also auf undefiniertes Verhalten hin. Aber a^a
und a-a
sind 0
für alle Werte gleich a
, zumindest denke ich, dass dies der Fall ist. Ist es möglich zu argumentieren, dass das Verhalten gut definiert ist?
c
undefined-behavior
David Heffernan
quelle
quelle
a
den dort befindlichen Müll zuweisen und anschließend daraus lesen würde . Wenn nicht, ist das Verhalten undefiniert.volatile
ist, würde ich das als definiertes Verhalten akzeptieren.a ^= a
, ist genau gleichbedeutend mita = 0
Antworten:
In C11:
a
seine Adresse nie vergeben wurde ( siehe unten).Vorzeichenlose Ints können Trap-Darstellungen haben (z. B. wenn 15 Präzisionsbits und 1 Paritätsbit vorhanden sind, kann der Zugriff
a
einen Paritätsfehler verursachen).6.2.4 / 6 besagt, dass der Anfangswert unbestimmt ist und die Definition unter 3.19.2 entweder ein nicht spezifizierter Wert oder eine Trap-Darstellung ist .
Weiter: in C11 6.3.2.1/2, wie von Pascal Cuoq ausgeführt:
Dies hat keine Ausnahme für Zeichentypen, daher scheint diese Klausel die vorhergehende Diskussion zu ersetzen. Der Zugriff
x
ist sofort undefiniert, auch wenn keine Trap-Darstellungen vorhanden sind. Diese Klausel wurde zu C11 hinzugefügt, um Itanium-CPUs zu unterstützen, die tatsächlich einen Trap-Status für Register haben.Systeme ohne Trap-Darstellungen: Aber was ist, wenn wir einwerfen
&x;
, damit der Einwand von 6.3.2.1/2 nicht mehr gilt und wir uns auf einem System befinden, von dem bekannt ist, dass es keine Trap-Darstellungen hat? Dann ist der Wert ein nicht spezifizierter Wert . Die Definition des nicht spezifizierten Wertes in 3.19.3 ist etwas vage, wird jedoch durch DR 451 klargestellt , was zu folgendem Schluss führt:Bei dieser Auflösung
int a; &a; int b = a - a;
ergibt sichb
immer noch ein unbestimmter Wert.Beachten Sie, dass wir uns immer noch im Bereich eines nicht spezifizierten Verhaltens (nicht undefinierten Verhaltens) befinden, wenn der unbestimmte Wert nicht an eine Bibliotheksfunktion übergeben wird. Die Ergebnisse mögen seltsam sein, z. B.
if ( j != j ) foo();
könnte man foo nennen, aber die Dämonen müssen in der Nasenhöhle gefangen bleiben.quelle
2 * j
seltsam, was etwas schlimmer ist als das Bild in Andreys Antwort, aber Sie haben die Idee.Ja, es ist undefiniertes Verhalten.
Erstens kann jede nicht initialisierte Variable eine "kaputte" (auch als "Trap" bezeichnete) Darstellung haben. Schon ein einziger Versuch, auf diese Darstellung zuzugreifen, löst ein undefiniertes Verhalten aus. Darüber hinaus können auch Objekte nicht einfangender Typen (wie
unsigned char
) noch spezielle plattformabhängige Zustände (wie NaT - Not-A-Thing - auf Itanium) annehmen, die als Manifestation ihres "unbestimmten Wertes" erscheinen könnten.Zweitens kann nicht garantiert werden, dass eine nicht initialisierte Variable einen stabilen Wert hat. Zwei sequentielle Zugriffe auf dieselbe nicht initialisierte Variable können völlig unterschiedliche Werte lesen , weshalb selbst wenn beide Zugriffe
a - a
"erfolgreich" sind (kein Überfüllen), immer noch nicht garantiert werden kann, dassa - a
sie auf Null ausgewertet werden.quelle
Wenn ein Objekt eine automatische Speicherdauer hat und seine Adresse nicht verwendet wird, führt der Versuch, es zu lesen, zu einem undefinierten Verhalten. Wenn die Adresse eines solchen Objekts verwendet wird und Zeiger vom Typ "vorzeichenloses Zeichen" zum Auslesen der Bytes verwendet werden, wird vom Standard garantiert, dass ein Wert vom Typ "vorzeichenloses Zeichen" erhalten wird, aber nicht alle Compiler halten sich in dieser Hinsicht an den Standard . ARM GCC 5.1, zum Beispiel, wenn angegeben:
#include <stdint.h> #include <string.h> struct q { uint16_t x,y; }; volatile uint16_t zz; int32_t foo(uint32_t x, uint32_t y) { struct q temp1,temp2; temp1.x = 3; if (y & 1) temp1.y = zz; memmove(&temp2,&temp1,sizeof temp1); return temp2.y; }
generiert Code, der x zurückgibt, wenn y Null ist, auch wenn x außerhalb des Bereichs 0-65535 liegt. Der Standard macht deutlich, dass vorzeichenlose Zeichenlesevorgänge mit unbestimmtem Wert garantiert einen Wert im Bereich von ergeben
unsigned char
und das Verhalten vonmemmove
als einer Folge von Zeichenlese- und -schreibvorgängen äquivalent definiert ist. Daher sollte temp2 einen Wert haben, der über eine Folge von Zeichenschreibvorgängen darin gespeichert werden könnte, aber gcc beschließt, den memmove durch eine Zuweisung zu ersetzen und die Tatsache zu ignorieren, dass der Code die Adresse von temp1 und temp2 angenommen hat.Es wäre hilfreich, ein Mittel zu haben, um einen Compiler zu zwingen, eine Variable als einen beliebigen Wert seines Typs zu betrachten, wenn ein solcher Wert gleichermaßen akzeptabel wäre, aber der Standard gibt kein sauberes Mittel dafür an (speichern) zum Speichern eines bestimmten Wertes, der funktionieren würde, aber oft unnötig langsam ist). Selbst Operationen, die eine Variable logisch zwingen sollten, einen Wert zu halten, der als eine Kombination von Bits darstellbar wäre, können nicht für alle Compiler verwendet werden. Folglich kann für solche Variablen nichts Nützliches garantiert werden.
quelle
memmove
ist eine Bibliotheksfunktion, die hier gelten würde.T std::freeze(T v)
Methode einführen können , die einen "wackeligen" unbestimmten Wert in einen nicht spezifizierten, aber stabilen Wert verwandelt. Es hätte jedoch Nützlichkeit "dritter Ordnung": Die Verwendung eines unbestimmten Werts ist bereits unklar und wird nur sehr selten verwendet. Das Hinzufügen eines speziellen Konstrukts, um solche Werte nur zu verfestigen, scheint also nur weiter unten im Kaninchenbau einer bereits dunklen Ecke zu liegen der Standard, und es müsste in den Kerntransformations- / Optimierungsphasen vieler Compiler unterstützt werden.foo=moo; if (foo < 100) bar(foo);
undmoo
unerwartet von einem anderen Thread geändert wird, kann es im Wesentlichen unmöglich sein, zu diagnostizieren, wann und wo etwas schief gelaufen ist. In der Lage zu sein, zu sagenfoo=moo; freeze(foo); if (foo < 100) bar(foo);
und den Compiler auf einen Wert fürfoo
festlegen zu lassen, würde die Dinge viel robuster machen.