Was ist all gemeinsame undefinierte Verhalten, die ein C ++ Programmierer wissen sollten? [geschlossen]

StackOverflow https://stackoverflow.com/questions/367633

Frage

Was ist all gemeinsame undefinierte Verhalten, die ein C ++ Programmierer wissen sollten?

Sag mal, wie:

a[i] = i++;

War es hilfreich?

Lösung

Zeiger

  • Dereferenzierung einen NULL Zeiger
  • dereferenzieren einen Zeiger von einer „neuen“ Zuweisung von Größe Null
  • zurück
  • Verwenden von Zeigern auf Objekte, deren Lebensdauer beendet ist (zum Beispiel Stapel zugeordneten Objekte oder gelöschte Objekte)
  • dereferenzieren einen Zeiger, der noch nicht definitiv initialisiert
  • Performing Zeigerarithmetik, die ein Ergebnis außerhalb der Grenzen (entweder oben oder unten) eines Arrays ergibt.
  • Dereferenzieren den Zeiger an einer Stelle jenseits des Endes eines Arrays.
  • Konvertieren Zeiger auf Objekte inkompatibler Typen
  • memcpy Verwendung zu kopieren Puffer überlappende .

Pufferüberlauf

  • Lesen oder Schreiben auf ein Objekt oder eine Anordnung an einem Offset, dass negativ ist, oder über die Größe des Objekts (Stapel / Heapüberlauf)

Integer-Überläufe

  • Signed Integer-Überlauf
  • Auswerten eines Ausdrucks, die nicht mathematisch definiert ist
  • Linksverschiebung Werte durch einen negativen Betrag (rechts verschiebt sich durch negative Beträge Implementierung definiert)
  • Shifting-Wert um einen Betrag größer oder gleich die Anzahl der Bits in der Anzahl (z.B. int64_t i = 1; i <<= 72 undefiniert)

Typen, Guss und Konst

  • Casting einen numerischen Wert in einen Wert, der nicht durch den Zieltyp (entweder direkt oder über static_cast) dargestellt werden können,
  • Unter Verwendung einer automatischen Variablen, bevor es auf jeden Fall zugewiesen wurde (zum Beispiel int i; i++; cout << i;)
  • Verwenden Sie den Wert eines Objekts von anderem Typ als volatile oder sig_atomic_t beim Empfang eines Signals
  • Der Versuch, einen Stringliteral oder anderes konstantes Objekt während seiner Lebensdauer
  • zu ändern
  • verketten einen schmalen mit einem breiten Stringliteral im Vorlauf

Funktion und Vorlage

  • Nicht einen Wert von einer Wert-Rückkehr-Funktion (direkt oder aus einem Try-Block abfließende)
  • Rückkehr
  • Mehrere verschiedene Definitionen für die gleiche Einheit (Klasse, Schablone, Aufzählung, Inline-Funktion, statische Member-Funktion, etc.)
  • Endlosschleife in der Instanziierung von Templates
  • Aufruf eine Funktion unter Verwendung verschiedene Parameter oder Verknüpfung mit den Parametern und Verknüpfung, die die Funktion wie die Verwendung definiert ist.

OOP

  • Cascading destructions von Objekten mit statischer Speicherdauer
  • Das Ergebnis der Zuordnung zu teilweise überlappenden Objekten
  • Recursively eine Funktion bei der Initialisierung der statischen Objekte neu eingeben
  • virtuelle Funktion Gespräche führen zu reinen virtuellen Funktionen eines Objekts von seinem Konstruktor oder Destruktor
  • Mit Bezug auf nicht-statische Mitglieder von Objekten, die nicht konstruiert hat oder bereits zerstört worden

Die Quelldatei und Preprocessing

  • eine nicht leere Quelldatei, die mit einer Neuen-Zeile nicht endet, oder endet mit einem umgekehrten Schrägstrich (vor der C ++ 11)
  • A Backslash gefolgt von einem Zeichen, die nicht Teil des genannten Escape-Codes in einem Zeichen oder String-Konstante ist (dies ist die Implementierung definierte in C ++ 11).
  • Ein Überschreiten Implementierung Grenzen (Anzahl der verschachtelten Blöcke, die Anzahl der Funktionen in einem Programm, zur Verfügung Stapelspeicher ...)
  • Preprocessor numerische Werte, die nicht durch eine long int dargestellt werden
  • Präprozessordirektive auf der linken Seite einer funktionsähnlichen Makrodefinition
  • Dynamisch das definierte Token in einem #if Ausdruck zu erzeugen

einzureihen

  • Beim Ausstieg bei der Zerstörung eines Programms mit statischer Speicherdauer

Andere Tipps

Die Reihenfolge, Funktionsparameter ausgewertet werden, ist nicht spezifiziert Verhalten . (Dies wird nicht Ihr Programm zum Absturz bringen, explodiert oder Pizza bestellt ... im Gegensatz zu undefined Verhalten ).

Die einzige Voraussetzung ist, dass alle Parameter vollständig ausgewertet werden müssen, bevor die Funktion aufgerufen wird.


Dieses:

// The simple obvious one.
callFunc(getA(),getB());

Kann sein entspricht dies:

int a = getA();
int b = getB();
callFunc(a,b);

Oder diese:

int b = getB();
int a = getA();
callFunc(a,b);

Es kann entweder sein; es ist für den Compiler auf. Das Ergebnis kann Materie in Abhängigkeit von den Nebenwirkungen.

Der Compiler ist frei, um die Auswertung Teile eines Ausdrucks neu zu ordnen (vorausgesetzt, die Bedeutung unverändert ist).

Von der ursprünglichen Frage:

a[i] = i++;

// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)

// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:

int rhs  = i++;
int lhs& = a[i];
lhs = rhs;

// or
int lhs& = a[i];
int rhs  = i++;
lhs = rhs;

Double Checked Verriegelung. Und man leicht Fehler machen.

A* a = new A("plop");

// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'

// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.

// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        a = new A("Plop");  // (Point A).
    }
}
a->doStuff();

// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
//           Remember (c) has been done thus 'a' is not NULL.
//           But the memory has not been initialized.
//           Thread 2 now executes doStuff() on an uninitialized variable.

// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        A* tmp = new A("Plop");  // (Point A).
        a = tmp;
    }
}
a->doStuff();

// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.

Mein Favorit ist „Unendliche Rekursion in der Instanziierung von Templates“ weil ich glaube, es ist die einzige, in dem das nicht definiertes Verhalten bei der Kompilierung auftritt.

Zuordnung zu einem konstanten nach dem Strippen constness mit const_cast<>:

const int i = 10; 
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined

Neben nicht definiertes Verhalten , gibt es auch die ebenso bösen Implementierung definiert Verhalten .

undefiniertes Verhalten tritt auf, wenn ein Programm etwas tut, das Ergebnis davon ist nicht von der Norm festgelegt.

Implementierung definierte Verhalten ist eine Aktion, die von einem Programm mit dem Ergebnis, nicht durch den Standard definiert, aber die Umsetzung erforderlich ist, zu dokumentieren. Ein Beispiel dafür ist „Multibyte Zeichenliterale“, von Stack Overflow Frage Gibt es einen C-Compiler, der dies zu kompilieren fehlschlägt? .

Implementierung definierte Verhalten, das Sie nur beißt, wenn Sie anfangen zu portieren (aber auf eine neue Version der Compiler-Upgrade wird auch die Portierung!)

Variablen darf nur in einem Ausdruck einmal aktualisiert werden (technisch einmal zwischen Sequenzpunkten).

int i =1;
i = ++i;

// Undefined. Assignment to 'i' twice in the same expression.

Ein grundlegendes Verständnis der verschiedenen Umweltgrenzen. Die vollständige Liste ist in Abschnitt 5.2.4.1 der C-Spezifikation. Hier sind ein paar;

  • 127 Parameter in einer Funktion de fi nition
  • 127 Argumente in einem Funktionsaufruf
  • 127 Parameter in einem Makro De fi nition
  • 127 Argumente in einem Makroaufruf
  • 4095 Zeichen in einer logischen Quellenleitung
  • 4095 Zeichen in einer Zeichenkette wörtliche oder breite Stringliteral (nach Verkettung)
  • 65535 Bytes in einem Objekt (in einer gehosteten Umgebung nur)
  • 15nesting Ebene für #include fi les
  • 1023 Fall Etiketten für einen Schalter Erklärung (mit Ausnahme derjenigen, für anynested switch-Anweisungen)

Ich war eigentlich ein wenig überrascht, an der Grenze von 1023 Fall Etikett für eine switch-Anweisung, ich kann forsee dass ziemlich easially für generierten Code / lex / Parser überschritten wird.

Wenn diese Grenzen überschritten werden, müssen Sie nicht definiertes Verhalten (Abstürze, Sicherheitslücken, etc ...).

Richtig, ich weiß, das von der C-Spezifikation, aber C ++ teilt dieser Grund unterstützt.

memcpy Verwendung zwischen überlappenden Speicherbereiche zu kopieren. Zum Beispiel:

char a[256] = {};
memcpy(a, a, sizeof(a));

Das Verhalten ist nicht definiert, nach dem C-Standard, der von der C ++ 03 Standard-subsumiert wird.

7.21.2.1 Die Memcpy Funktion

  

Überblick

     

1 / # include void * memcpy (void * restrict s1, const   void * beschränken s2, size_t n);

     

Beschreibung

     

2 / Die Memcpy Funktion   Kopien n Zeichen aus dem anvisierten Objekt von s2 in das Objekt   auf dem von s1. Wenn das Kopieren zwischen Objekten erfolgt, die sich überlappen,   das Verhalten ist nicht definiert. Returns 3 Die Memcpy Funktion gibt die   Wert von s1.

7.21.2.2 Die memmove Funktion

  

Überblick

     

1 # include void * memmove (void * s1, const void * s2, size_t   n);

     

Beschreibung

     

2 Die memmove Funktion kopiert n Zeichen aus dem anvisierten Objekt   von s2 in das Objekt, auf das durch s1. Das Kopieren erfolgt, als ob die   n Zeichen von dem Objekt, auf das durch s2 werden zuerst in ein kopiertes   temporäre Array von n Zeichen, die nicht die Objekte nicht überlappen,   durch s1 und s2 gezeigt wird, und dann die n Zeichen aus dem temporären   Array kopiert werden, in das Objekt durch s1 spitz. Returns

     

3 Die memmove Funktion gibt den Wert von s1.

Die einzige Art, für die C ++ eine Größe gewährleistet ist char. Und die Größe ist 1. Die Größe aller anderen Typen ist plattformabhängig.

Namespace-Level-Objekte in einer anderen Übersetzungseinheiten sollten abhängen nie für die Initialisierung aufeinander, weil ihre Initialisierungsreihenfolge nicht definiert ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top