Was sind all die allgemeinen undefinierten Verhaltensweisen, die ein C++-Programmierer kennen sollte? [closed]

Lesezeit: 10 Minuten

Was sind all die allgemeinen undefinierten Verhaltensweisen die ein C Programmierer
jaraaj

Was sind all die allgemeinen undefinierten Verhaltensweisen, die ein C++-Programmierer kennen sollte?

Sagen Sie, wie:

a[i] = i++;

  • Bist du sicher. Das sieht gut definiert aus.

    – Martin York

    15. Dezember 2008 um 7:10 Uhr

  • 6.2.2 Bewertungsauftrag [expr.evaluation] in der Programmiersprache C ++ so sagen. Ich habe keine andere Referenz

    – jaraaj

    15. Dezember 2008 um 7:24 Uhr

  • Er hat recht.. hat sich gerade 6.2.2 in The C++ Programming Language angesehen und da steht v[i] = i++ ist undefiniert

    – Dancavallaro

    15. Dezember 2008 um 7:24 Uhr

  • Ich würde mir vorstellen, dass der Compiler das i ++ vor oder nach der Berechnung des Speicherplatzes von v ausführt[i]. Sicher, ich werde immer dort eingesetzt werden. aber es könnte entweder an v schreiben[i] oder V[i+1] je nach Reihenfolge der Operationen..

    – Evan Teran

    15. Dezember 2008 um 7:44 Uhr

  • Alles, was die Programmiersprache C++ sagt, ist: „Die Reihenfolge der Operationen von Teilausdrücken innerhalb eines Ausdrucks ist undefiniert. Insbesondere können Sie nicht davon ausgehen, dass der Ausdruck von links nach rechts ausgewertet wird.“

    – Dancavallaro

    15. Dezember 2008 um 8:14 Uhr

Zeiger

  • Dereferenzierung a NULL Zeiger
  • Dereferenzieren eines Zeigers, der von einer “neuen” Zuordnung der Größe Null zurückgegeben wird
  • Verwenden von Zeigern auf Objekte, deren Lebensdauer abgelaufen ist (z. B. Stapeln von zugewiesenen Objekten oder gelöschten Objekten)
  • Dereferenzieren eines noch nicht definitiv initialisierten Zeigers
  • Durchführen einer Zeigerarithmetik, die ein Ergebnis außerhalb der Grenzen (entweder über oder unter) eines Arrays liefert.
  • Dereferenzieren des Zeigers an einer Stelle hinter dem Ende eines Arrays.
  • Konvertieren von Zeigern auf Objekte inkompatibler Typen
  • Verwenden memcpy um überlappende Puffer zu kopieren.

Puffer läuft über

  • Lesen oder Schreiben in ein Objekt oder Array mit einem negativen Offset oder jenseits der Größe dieses Objekts (Stapel-/Heap-Überlauf)

Ganzzahlüberläufe

  • Vorzeichenbehafteter Integer-Überlauf
  • Auswerten eines Ausdrucks, der nicht mathematisch definiert ist
  • Linksverschiebung von Werten um einen negativen Betrag (Rechtsverschiebungen um negative Beträge sind implementierungsdefiniert)
  • Das Verschieben von Werten um einen Betrag, der größer oder gleich der Anzahl der Bits in der Zahl ist (z int64_t i = 1; i <<= 72 ist nicht definiert)

Typen, Cast und Const

  • Umwandeln eines numerischen Werts in einen Wert, der nicht durch den Zieltyp dargestellt werden kann (entweder direkt oder über static_cast)
  • Verwenden einer automatischen Variablen, bevor sie definitiv zugewiesen wurde (z. B. int i; i++; cout << i;)
  • Verwenden des Werts eines beliebigen Objekts vom Typ außer volatile oder sig_atomic_t beim Empfang eines Signals
  • Versuch, ein Zeichenfolgenliteral oder ein anderes konstantes Objekt während seiner Lebensdauer zu ändern
  • Verketten eines schmalen mit einem breiten Zeichenfolgenliteral während der Vorverarbeitung

Funktion und Vorlage

  • Keinen Wert von einer Wert zurückgebenden Funktion zurückgeben (direkt oder durch Abfließen von einem try-Block)
  • Mehrere unterschiedliche Definitionen für dieselbe Entität (Klasse, Vorlage, Enumeration, Inline-Funktion, statische Elementfunktion usw.)
  • Unendliche Rekursion bei der Instanziierung von Templates
  • Aufrufen einer Funktion mit unterschiedlichen Parametern oder Verknüpfung mit den Parametern und Verknüpfungen, für die die Funktion definiert ist.

Hoppla

  • Kaskadierende Zerstörungen von Objekten mit statischer Speicherdauer
  • Das Ergebnis der Zuweisung zu teilweise überlappenden Objekten
  • Rekursives erneutes Eingeben einer Funktion während der Initialisierung ihrer statischen Objekte
  • Virtuelle Funktionsaufrufe an rein virtuelle Funktionen eines Objekts von seinem Konstruktor oder Destruktor aus durchführen
  • Bezieht sich auf nichtstatische Mitglieder von Objekten, die nicht konstruiert oder bereits zerstört wurden

Quelldatei und Vorverarbeitung

  • Eine nicht leere Quelldatei, die nicht mit einem Zeilenumbruch oder mit einem umgekehrten Schrägstrich endet (vor C++11)
  • Ein umgekehrter Schrägstrich, gefolgt von einem Zeichen, das nicht Teil der angegebenen Escape-Codes in einer Zeichen- oder Zeichenfolgenkonstante ist (dies ist in C++11 implementierungsdefiniert).
  • Überschreitung der Implementierungsgrenzen (Anzahl verschachtelter Blöcke, Anzahl der Funktionen in einem Programm, verfügbarer Stapelplatz …)
  • Numerische Präprozessorwerte, die nicht durch a dargestellt werden können long int
  • Vorverarbeitungsanweisung auf der linken Seite einer funktionsähnlichen Makrodefinition
  • Dynamisches Generieren des definierten Tokens in a #if Ausdruck

Einzuordnen

  • Aufruf von exit während der Zerstörung eines Programms mit statischer Speicherdauer

  • Hm … NaN (x / 0) und Infinity (0 / 0) wurden von IEE 754 abgedeckt, wenn C++ später entworfen wurde, warum zeichnet es x / 0 als undefiniert auf?

    – neu123456

    5. April 2011 um 13:22 Uhr

  • Betreff: “Ein umgekehrter Schrägstrich, gefolgt von einem Zeichen, das nicht Teil der angegebenen Escape-Codes in einer Zeichen- oder Zeichenfolgenkonstante ist.” Das ist UB in C89 (§3.1.3.4) und C++03 (das C89 enthält), aber nicht in C99. C99 sagt, dass “das Ergebnis kein Token ist und eine Diagnose erforderlich ist” (§6.4.4.4). Vermutlich wird C++0x (das C89 enthält) dasselbe sein.

    – Adam Rosenfield

    7. Juni 2011 um 4:31 Uhr

  • Der C99-Standard enthält eine Liste undefinierter Verhaltensweisen in Anhang J.2. Es würde einige Arbeit erfordern, diese Liste an C++ anzupassen. Sie müssten die Verweise auf die richtigen C++-Klauseln anstatt auf die C99-Klauseln ändern, alles Unwichtige entfernen und auch prüfen, ob all diese Dinge wirklich sowohl in C++ als auch in C undefiniert sind. Aber es bietet einen Anfang.

    – Steve Jessop

    7. Juni 2011 um 8:13 Uhr

  • @ new123456 – nicht alle Gleitkommaeinheiten sind IEE754-kompatibel. Wenn C++ die IEE754-Konformität erfordert, müssten Compiler den Fall, in dem RHS null ist, über eine explizite Prüfung testen und behandeln. Indem das Verhalten undefiniert gemacht wird, kann der Compiler diesen Overhead vermeiden, indem er sagt: “Wenn Sie eine Nicht-IEE754-FPU verwenden, erhalten Sie kein IEEE754-FPU-Verhalten”.

    – SicherheitsMatt

    1. September 2012 um 14:18 Uhr

  • “Auswerten eines Ausdrucks, dessen Ergebnis nicht im Bereich der entsprechenden Typen liegt” …. ganzzahliger Überlauf ist für UNSIGNED ganzzahlige Typen wohldefiniert, nur nicht für vorzeichenbehaftete.

    – nacitar sevaht

    9. August 2013 um 20:06 Uhr

1647270016 969 Was sind all die allgemeinen undefinierten Verhaltensweisen die ein C Programmierer
Martin York

Die Reihenfolge, in der Funktionsparameter ausgewertet werden, ist nicht spezifiziert Verhalten. (Dies wird Ihr Programm nicht zum Absturz bringen, explodieren oder Pizza bestellen … im Gegensatz zu nicht definiert Verhalten.)

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


Dies:

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

Kann äquivalent dazu sein:

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

Oder dieses:

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

Es kann beides sein; es liegt am Compiler. Das Ergebnis kann abhängig von den Nebenwirkungen von Bedeutung sein.

  • Die Reihenfolge ist unspezifiziert, nicht undefiniert.

    – Rob Kennedy

    15. Dezember 2008 um 7:55 Uhr

  • Ich hasse diesen 🙂 Ich habe einen Tag Arbeit verloren, als ich einen dieser Fälle aufgespürt habe … jedenfalls habe ich meine Lektion gelernt und bin zum Glück nicht wieder gestürzt

    – Robert Gould

    15. Dezember 2008 um 8:29 Uhr

  • @Rob: Ich würde mit Ihnen über die Bedeutungsänderung hier streiten, aber ich weiß, dass das Standardkomitee bei der genauen Definition dieser beiden Wörter sehr wählerisch ist. Also werde ich es einfach ändern 🙂

    – Martin York

    15. Dezember 2008 um 8:34 Uhr

  • Bei diesem hatte ich Glück. Ich wurde davon gebissen, als ich auf dem College war und einen Professor hatte, der einen Blick darauf warf und mir mein Problem in etwa 5 Sekunden erklärte. Ich weiß nicht, wie viel Zeit ich sonst mit dem Debuggen verschwendet hätte.

    – Bill die Eidechse

    15. Dezember 2008 um 16:06 Uhr

1647270016 969 Was sind all die allgemeinen undefinierten Verhaltensweisen die ein C Programmierer
Martin York

Dem Compiler steht es frei, die Bewertungsteile eines Ausdrucks neu zu ordnen (unter der Annahme, dass die Bedeutung unverändert bleibt).

Aus 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;

Doppelt geprüfte Verriegelung. Und ein einfacher Fehler zu 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.

  • Was ist mit Sequenzpunkt gemeint?

    – jaraaj

    15. Dezember 2008 um 8:25 Uhr

  • en.wikipedia.org/wiki/Sequence_point

    – Martin York

    15. Dezember 2008 um 8:36 Uhr

  • Ooh … das ist böse, zumal ich gesehen habe, dass genau diese Struktur in Java empfohlen wird

    – Tom

    15. Dezember 2008 um 14:44 Uhr

  • Beachten Sie, dass einige Compiler das Verhalten in dieser Situation definieren. In VC++ 2005+ werden beispielsweise, wenn a flüchtig ist, die erforderlichen Speicherbarrieren eingerichtet, um eine Neuordnung von Anweisungen zu verhindern, sodass doppelt geprüfte Sperren funktionieren.

    – Sonnenfinsternis

    23. Juni 2009 um 16:11 Uhr

  • Martin York: // (c) wird garantiert nach (a) und (b) passieren Ist es das? Zugegebenermaßen wäre in diesem speziellen Beispiel das einzige Szenario, in dem es von Bedeutung sein könnte, wenn „i“ eine flüchtige Variable wäre, die einem Hardwareregister zugeordnet ist, und a[i] (alter Wert von ‘i’) wurde mit einem Alias ​​versehen, aber gibt es eine Garantie dafür, dass das Inkrement vor einem Sequenzpunkt erfolgt?

    – Superkatze

    14. August 2010 um 20:18 Uhr

Mein Favorit ist “Unendliche Rekursion bei der Instanziierung von Vorlagen”, weil ich glaube, dass dies die einzige ist, bei der das undefinierte Verhalten zur Kompilierzeit auftritt.

Was sind all die allgemeinen undefinierten Verhaltensweisen die ein C Programmierer
jaraaj

Zuweisung an eine Konstante nach dem Strippen constness verwenden const_cast<>:

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

1647270016 479 Was sind all die allgemeinen undefinierten Verhaltensweisen die ein C Programmierer
Gemeinschaft

neben undefiniertes Verhaltengibt es auch das ebenso fiese implementierungsdefiniertes Verhalten.

Undefiniertes Verhalten tritt auf, wenn ein Programm etwas tut, dessen Ergebnis nicht durch den Standard spezifiziert ist.

Implementierungsdefiniertes Verhalten ist eine Aktion eines Programms, dessen Ergebnis nicht durch den Standard definiert ist, die Implementierung jedoch dokumentieren muss. Ein Beispiel ist “Multibyte-Zeichenliterale” aus der Stack Overflow-Frage Gibt es einen C-Compiler, der dies nicht kompiliert?.

Implementierungsdefiniertes Verhalten beißt Sie nur, wenn Sie mit der Portierung beginnen (aber ein Upgrade auf eine neue Version des Compilers ist auch eine Portierung!)

1647270017 31 Was sind all die allgemeinen undefinierten Verhaltensweisen die ein C Programmierer
Peter Mortensen

Variablen dürfen in einem Ausdruck nur einmal aktualisiert werden (technisch einmal zwischen Sequenzpunkten).

int i =1;
i = ++i;

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

  • In der Tat wenigstens einmal zwischen zwei Sequenzpunkten.

    – Prasun Saurav

    12. August 2010 um 13:10 Uhr


  • @Prasoon: Ich glaube du meintest: maximal einmal zwischen zwei Sequenzpunkten. 🙂

    – Nawaz

    7. Juni 2011 um 9:24 Uhr


1002020cookie-checkWas sind all die allgemeinen undefinierten Verhaltensweisen, die ein C++-Programmierer kennen sollte? [closed]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy