Was ist all gemeinsame undefinierte Verhalten, die ein C ++ Programmierer wissen sollten? [geschlossen]
-
21-08-2019 - |
Frage
Was ist all gemeinsame undefinierte Verhalten, die ein C ++ Programmierer wissen sollten?
Sag mal, wie:
a[i] = i++;
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
odersig_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 const
ness 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.