C++
Unbekanntes Verhalten
Suche…
Bemerkungen
Wenn das Verhalten eines Konstrukts nicht spezifiziert ist, gibt der Standard einige Einschränkungen für das Verhalten vor, lässt jedoch einige Freiräume für die Implementierung, was nicht erforderlich ist, um zu dokumentieren, was in einer bestimmten Situation geschieht. Sie steht im Gegensatz zum implementierungsdefinierten Verhalten , bei dem die Implementierung erforderlich ist , um zu dokumentieren, was passiert, und undefiniertes Verhalten, bei dem alles passieren kann.
Reihenfolge der Initialisierung von Globals in der gesamten TU
Während in einer Übersetzungseinheit die Reihenfolge der Initialisierung von globalen Variablen festgelegt wird, ist die Reihenfolge der Initialisierung über die Übersetzungseinheiten nicht spezifiziert.
Also mit folgenden Dateien programmieren
foo.cpp
#include <iostream> int dummyFoo = ((std::cout << "foo"), 0);
bar.cpp
#include <iostream> int dummyBar = ((std::cout << "bar"), 0);
main.cpp
int main() {}
kann als Ausgabe produzieren:
foobar
oder
barfoo
Dies kann zu einem Fiasko für statische Initialisierung führen.
Wert einer Aufzählung außerhalb des Bereichs
Wenn eine bereichsabhängige Aufzählung in einen ganzzahligen Typ umgewandelt wird, der zu klein ist, um den Wert zu halten, ist der resultierende Wert nicht angegeben. Beispiel:
enum class E {
X = 1,
Y = 1000,
};
// assume 1000 does not fit into a char
char c1 = static_cast<char>(E::X); // c1 is 1
char c2 = static_cast<char>(E::Y); // c2 has an unspecified value
Wenn eine Ganzzahl in eine Aufzählung umgewandelt wird und der Wert der Ganzzahl außerhalb des Wertebereichs der Aufzählung liegt, ist der resultierende Wert nicht angegeben. Beispiel:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
};
Color c = static_cast<Color>(4);
Im nächsten Beispiel ist das Verhalten jedoch nicht unbestimmt, da der Quellwert innerhalb des Bereichs der Enumeration liegt, obwohl er nicht allen Enumeratoren entspricht:
enum Scale {
ONE = 1,
TWO = 2,
FOUR = 4,
};
Scale s = static_cast<Scale>(3);
Hier wird s
den Wert 3 haben und ungleich ONE
, TWO
und FOUR
.
Statischer Wurf aus falschem void * -Wert
Wenn ein void*
-Wert in einen Zeiger auf den Objekttyp T*
konvertiert wird, für T
jedoch nicht ordnungsgemäß ausgerichtet ist, ist der resultierende Zeigerwert nicht angegeben. Beispiel:
// Suppose that alignof(int) is 4
int x = 42;
void* p1 = &x;
// Do some pointer arithmetic...
void* p2 = static_cast<char*>(p1) + 2;
int* p3 = static_cast<int*>(p2);
Der Wert von p3
ist nicht angegeben, da p2
nicht auf ein Objekt vom Typ int
. Ihr Wert ist keine richtig ausgerichtete Adresse.
Ergebnis einiger reinterpret_cast-Konvertierungen
Das Ergebnis eines reinterpret_cast
von einem Funktionszeigertyp zu einem anderen oder eines Funktionsreferenztyps zu einem anderen ist nicht angegeben. Beispiel:
int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
Das Ergebnis eines reinterpret_cast
von einem Objektzeigertyp zu einem anderen oder von einem Objektreferenztyp zu einem anderen ist nicht angegeben. Beispiel:
int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value
Bei den meisten Compilern entsprach dies jedoch static_cast<char*>(static_cast<void*>(&x))
sodass der resultierende Zeiger p
auf das erste Byte von x
. Dies wurde zum Standardverhalten in C ++ 11. Weitere Informationen finden Sie unter Typ-Punning-Konvertierung .
Ergebnis einiger Zeigervergleiche
Wenn zwei Zeiger mit <
, >
, <=
oder >=
verglichen werden, ist das Ergebnis in den folgenden Fällen nicht angegeben:
Die Zeiger verweisen auf verschiedene Arrays. (Ein Nicht-Array-Objekt wird als Array der Größe 1 betrachtet.)
int x; int y; const bool b1 = &x < &y; // unspecified int a[10]; const bool b2 = &a[0] < &a[1]; // true const bool b3 = &a[0] < &x; // unspecified const bool b4 = (a + 9) < (a + 10); // true // note: a+10 points past the end of the array
Die Zeiger verweisen auf dasselbe Objekt, jedoch auf Mitglieder mit unterschiedlicher Zugriffskontrolle.
class A { public: int x; int y; bool f1() { return &x < &y; } // true; x comes before y bool f2() { return &x < &z; } // unspecified private: int z; };
Platz, der von einer Referenz belegt wird
Eine Referenz ist kein Objekt und im Gegensatz zu einem Objekt kann nicht garantiert werden, dass einige zusammenhängende Speicherbytes belegt werden. Der Standard lässt keine Angabe, ob eine Referenz überhaupt Speicherplatz benötigt. Eine Reihe von Merkmalen der Sprache verschwören, um die portierbare Überprüfung des von der Referenz belegten Speichers unmöglich zu machen:
- Wenn
sizeof
auf eine Referenz angewendet wird, wird die Größe des referenzierten Typs zurückgegeben, sodass keine Informationen darüber erhalten werden, ob die Referenz Speicherplatz belegt. - Arrays von Referenzen sind unzulässig, daher ist es nicht möglich, die Adressen zweier aufeinanderfolgender Elemente einer hypothetischen Referenz von Arrays zu untersuchen, um die Größe einer Referenz zu bestimmen.
- Wenn die Adresse eines Verweises verwendet wird, ist das Ergebnis die Adresse des Verweises, sodass wir keinen Verweis auf den Verweis selbst erhalten können.
- Wenn eine Klasse über ein Referenzelement verfügt, wird beim Versuch, die Adresse dieses
offsetof
mithilfe vonoffsetof
zu extrahieren, undefiniertes Verhaltenoffsetof
da eine solche Klasse keine Standardlayoutklasse ist. - Wenn eine Klasse über ein Referenzelement verfügt, ist die Klasse nicht mehr das Standardlayout. Daher wird versucht, auf alle Daten zuzugreifen, die zum Speichern der Referenzergebnisse verwendet werden, undefiniertes oder nicht angegebenes Verhalten.
In der Praxis kann in manchen Fällen eine Referenzvariable ähnlich wie eine Zeigervariable implementiert werden und somit dieselbe Menge an Speicher wie ein Zeiger einnehmen, während in anderen Fällen eine Referenz überhaupt keinen Platz beansprucht, da sie optimiert werden kann. Zum Beispiel in:
void f() {
int x;
int& r = x;
// do something with r
}
Der Compiler kann r
einfach als Alias für x
und alle Vorkommen von r
im Rest der Funktion f
durch x
ersetzen und keinen Speicherplatz für r
zuweisen.
Bewertungsreihenfolge von Funktionsargumenten
Wenn eine Funktion mehrere Argumente hat, ist nicht angegeben, in welcher Reihenfolge sie ausgewertet werden. Der folgende Code könnte x = 1, y = 2
oder x = 2, y = 1
drucken x = 2, y = 1
aber es ist nicht angegeben, welche.
int f(int x, int y) {
printf("x = %d, y = %d\n", x, y);
}
int get_val() {
static int x = 0;
return ++x;
}
int main() {
f(get_val(), get_val());
}
In C ++ 17 ist die Reihenfolge der Auswertung von Funktionsargumenten nicht spezifiziert.
Jedes Funktionsargument wird jedoch vollständig ausgewertet, und das aufrufende Objekt wird garantiert vor allen Funktionsargumenten ausgewertet.
struct from_int {
from_int(int x) { std::cout << "from_int (" << x << ")\n"; }
};
int make_int(int x){ std::cout << "make_int (" << x << ")\n"; return x; }
void foo(from_int a, from_int b) {
}
void bar(from_int a, from_int b) {
}
auto which_func(bool b){
std::cout << b?"foo":"bar" << "\n";
return b?foo:bar;
}
int main(int argc, char const*const* argv) {
which_func( true )( make_int(1), make_int(2) );
}
das muss drucken:
bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)
oder
bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)
Es kann keine bar
nach einer der make
oder from
's gedruckt werden und es wird möglicherweise nicht gedruckt:
bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)
oder ähnliches. Vor der C ++ 17 Druck bar
nach make_int
s legal war, wie es beide tun make_int
s zu tun , alle früheren from_int
s.
Status der meisten Standard-Bibliotheksklassen verschoben
Alle Standard-Bibliothekscontainer verbleiben nach dem Verschieben in einem gültigen, aber nicht angegebenen Zustand. Im folgenden Code enthält v2
nach der Verschiebung beispielsweise {1, 2, 3, 4}
ist jedoch nicht garantiert, dass v1
leer ist.
int main() {
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v2 = std::move(v1);
}
Für einige Klassen gibt es einen genau definierten Abzugsstatus. Der wichtigste Fall ist der von std::unique_ptr<T>
, der nach dem std::unique_ptr<T>
garantiert null ist.