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
C ++ 03

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 von offsetof zu extrahieren, undefiniertes Verhalten offsetof 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());
}
C ++ 17

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

C ++ 11

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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow