Ist a^a oder aa undefiniertes Verhalten, wenn a nicht initialisiert ist?

Lesezeit: 10 Minuten

Benutzeravatar von David Heffernan
David Heffernan

Betrachten Sie dieses Programm:

#include <stdio.h>

int main(void)
{
    unsigned int a;
    printf("%u %u\n", a^a, a-a);
    return 0;
}

Ist es undefiniertes Verhalten?

Auf den ersten Blick, a ist eine nicht initialisierte Variable. Das deutet also auf undefiniertes Verhalten hin. Aber a^a und a-a gleich sind 0 für alle Werte von a, denke ich zumindest. Kann man argumentieren, dass das Verhalten wohldefiniert ist?

  • Ich würde erwarten, dass dies gut definiert ist, da der Wert von a unbekannt, aber fest ist und sich nicht ändern sollte. Die Frage ist, ob der Compiler den Platz für zuweisen würde a und anschließend aus dem dort liegenden Müll lesen. Wenn nicht, dann ist das Verhalten undefiniert.

    – Martin

    1. August 2014 um 6:35 Uhr

  • Hmm, solange die Variable nicht markiert ist volatile dann würde ich das als definiertes Verhalten akzeptieren. a ^= aist genau gleichbedeutend mit a = 0

    – Dateioffset

    1. August 2014 um 6:38 Uhr

  • @martin: Es ist nicht behoben. Der Wert darf sich ändern. Dies ist eine sehr praktische Überlegung. Eine Variable kann einem CPU-Register zugewiesen werden, aber solange sie nicht initialisiert ist (dh ihre effektive Wertlebensdauer hat noch nicht begonnen), kann dasselbe CPU-Register von einer anderen Variablen belegt werden. Die Änderungen in dieser anderen Variablen werden als “instabiler” Wert dieser nicht initialisierten Variablen angesehen. Das ist etwas, das ist häufig in der Praxis mit nicht initialisierten Variablen beobachtet.

    – AnT steht zu Russland

    1. August 2014 um 6:39 Uhr


  • @AndreyT das ist eine schöne Erklärung

    – Martin

    1. August 2014 um 6:45 Uhr

  • Egal, gefunden, mein Fehler: stackoverflow.com/questions/20300665/…, und es war tatsächlich für C.

    – Thomas

    1. August 2014 um 9:03 Uhr

Benutzeravatar von MM
MM

In C11:

  • Es ist gemäß 6.3.2.1/2 explizit undefiniert, wenn a hat nie seine Adresse genommen (unten zitiert)
  • Es könnte eine Trap-Darstellung sein (die beim Zugriff UB verursacht). 6.2.6.1/5:

Bestimmte Objektdarstellungen müssen keinen Wert des Objekttyps darstellen.

Unsigned ints können Trap-Darstellungen haben (z. B. wenn es 15 Präzisionsbits und 1 Paritätsbit hat, zugreifen a könnte einen Paritätsfehler verursachen).

6.2.4/6 sagt, dass der Anfangswert ist unbestimmt und die Definition davon unter 3.19.2 ist entweder ein nicht spezifizierter Wert oder eine Trap-Darstellung.

Weiter: in C11 6.3.2.1/2, wie von Pascal Cuoq hervorgehoben:

Wenn der lvalue ein Objekt mit automatischer Speicherdauer bezeichnet, das mit der Registerspeicherklasse hätte deklariert werden können (seine Adresse wurde nie übernommen), und dieses Objekt nicht initialisiert ist (nicht mit einem Initialisierer deklariert und vor der Verwendung keine Zuweisung durchgeführt wurde). ), ist das Verhalten undefiniert.

Dies hat keine Ausnahme für Zeichentypen, daher scheint diese Klausel die vorherige Diskussion zu ersetzen; zugreifen x ist sofort undefiniert, auch wenn keine Trap-Darstellungen existieren. Diese Klausel wurde zu C11 hinzugefügt zur Unterstützung von Itanium-CPUs, die tatsächlich einen Trap-Zustand für Register haben.


Systeme ohne Trap-Darstellungen: Aber was, wenn wir einwerfen &x; sodass der Einwand von 6.3.2.1/2 nicht mehr gilt und wir uns auf einem System befinden, von dem bekannt ist, dass es keine Fallendarstellungen hat? Dann ist der Wert ein unbestimmter Wert. Die Definition von unbestimmter Wert in 3.19.3 ist etwas vage, wird aber durch verdeutlicht DR 451die zu dem Schluss kommt:

  • Ein nicht initialisierter Wert kann unter den beschriebenen Bedingungen scheinbar seinen Wert ändern.
  • Jede Operation, die mit unbestimmten Werten ausgeführt wird, hat als Ergebnis einen unbestimmten Wert.
  • Bibliotheksfunktionen zeigen ein undefiniertes Verhalten, wenn sie für unbestimmte Werte verwendet werden.
  • Diese Antworten sind für alle Typen geeignet, die keine Trap-Darstellungen haben.

Unter diesem Beschluss int a; &a; int b = a - a; ergibt sich b noch unbestimmten Wert haben.

Beachten Sie, dass wir uns, wenn der unbestimmte Wert nicht an eine Bibliotheksfunktion übergeben wird, immer noch im Bereich des nicht spezifizierten Verhaltens (nicht des undefinierten Verhaltens) befinden. Die Ergebnisse können seltsam sein, z if ( j != j ) foo(); könnte foo rufen, aber die Dämonen müssen in der Nasenhöhle verborgen bleiben.

  • Angenommen, wir wüssten, dass es keine Trap-Werte gibt, könnten wir dann über definiertes Verhalten sprechen?

    – David Heffernan

    1. August 2014 um 6:40 Uhr

  • @DavidHeffernan Du könnte auch behandeln Sie den Zugriff auf unbestimmte Daten als UB, weil Ihr Compiler das auch tun könnte, selbst wenn es keine Trap-Werte gibt. Bitte sehen blog.frama-c.com/index.php?post/2013/03/13/…

    – Pascal Cuoq

    1. August 2014 um 6:48 Uhr

  • @Pascal Das verstehe ich jetzt. Das ist der letzte Absatz von Andreys Antwort.

    – David Heffernan

    1. August 2014 um 6:51 Uhr


  • @DavidHeffernan Die Beispiele gehen so weit wie 2 * j seltsam zu sein, was etwas schlimmer ist als das Bild in Andreys Antwort, aber Sie haben die Idee.

    – Pascal Cuoq

    1. August 2014 um 6:53 Uhr


  • Als der C89-Standard geschrieben wurde, wurde erwartet, dass Implementierungen viele Dinge spezifizieren würden, die der Standard nicht tat, und die Autoren des Standards sahen keinen Grund, alle Fälle zu beschreiben, in denen eine Aktion als auf Implementierungen definiert betrachtet werden sollte, die bestimmte Dinge spezifizieren ( zB die Tatsache, dass “unsigned int” keine Trap-Darstellungen hat), aber undefiniert bei Implementierungen, die dies nicht tun (zB wo das Lesen eines unbestimmten Bitmusters als “unsigned int” eine Trap-Darstellung ergeben könnte).

    – Superkatze

    26. September 2016 um 22:47 Uhr


AnT steht mit Russlands Benutzer-Avatar
AnT steht zu Russland

Ja, es ist ein undefiniertes Verhalten.

Erstens kann jede nicht initialisierte Variable eine “kaputte” (auch als “Falle” bezeichnete) Darstellung haben. Selbst ein einziger Versuch, auf diese Repräsentation zuzugreifen, löst undefiniertes Verhalten aus. Darüber hinaus können sogar Objekte von nicht einfangenden Typen (wie unsigned char) können immer noch spezielle plattformabhängige Zustände (wie NaT – Not-A-Thing – auf Itanium) annehmen, die als Manifestation ihres “unbestimmten Wertes” erscheinen könnten.

Zweitens ist nicht garantiert, dass eine nicht initialisierte Variable a hat stabil Wert. Zwei aufeinanderfolgende Zugriffe auf dieselbe nicht initialisierte Variable können vollständig gelesen werden anders Werte, weshalb auch wenn beide zugreift a - a “erfolgreich” sind (kein Trapping), ist das noch nicht garantiert a - a wird zu Null ausgewertet.

  • Haben Sie ein Zitat für diesen letzten Absatz? Wenn dem so ist, brauchen wir nicht einmal an Fallen zu denken.

    – David Heffernan

    1. August 2014 um 6:41 Uhr

  • @Matt McNabb: Nun, dies könnte ein Problem sein, das durch verschiedene Versionen der Sprachspezifikation unterschiedlich gelöst wurde. Aber die Auflösung für den DR#260 (open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm) stellt klar und deutlich fest, dass sich Variablen mit unbestimmten Werten beliebig “von selbst” ändern können.

    – AnT steht zu Russland

    1. August 2014 um 6:57 Uhr

  • @Matt McNabb: DR#451 bekräftigte im Wesentlichen dieselben Entscheidungen wie DR#260 sowohl im Oktober 2013 als auch im April 2014 open-std.org/Jtc1/sc22/WG14/www/docs/dr_451.htm . In der Antwort des Komitees für DR#451 heißt es ausdrücklich: „Dieser Standpunkt bekräftigt die Position von C99 DR260.“

    – AnT steht zu Russland

    1. August 2014 um 7:03 Uhr


  • @hyde Die nächste Darstellung einer Falle, die Sie möglicherweise zur Hand haben, ist das Signalisieren von NaNs. en.wikipedia.org/wiki/NaN#Signaling_NaN Andernfalls benötigen Sie einen Computer mit expliziten Paritätsbits, einen Computer mit Vorzeichengröße, bei dem -0 als Trap-Wert betrachtet wird, oder etwas ähnlich Exotisches.

    – Pascal Cuoq

    1. August 2014 um 7:24 Uhr


  • @chux: Nein. Es gibt nichts was einen einschränkt undefiniertes Verhalten zu “macht, was du denkst, aber wenn nicht, Fallen”. Es ist buchstäblich jedes Verhalten erlaubt.

    – Ben Voigt

    1. August 2014 um 22:53 Uhr


Wenn ein Objekt eine automatische Speicherdauer hat und seine Adresse nicht verwendet wird, führt der Versuch, es zu lesen, zu undefiniertem Verhalten. Wenn man die Adresse eines solchen Objekts nimmt und Zeiger vom Typ “unsigned char” verwendet, um dessen Bytes auszulesen, garantiert der Standard einen Wert vom Typ “unsigned char”, aber nicht alle Compiler halten sich diesbezüglich an den Standard . ARM GCC 5.1, zum Beispiel, wenn angegeben:

  #include <stdint.h>
  #include <string.h>
  struct q { uint16_t x,y; };
  volatile uint16_t zz;
  int32_t foo(uint32_t x, uint32_t y)
  {
    struct q temp1,temp2;
    temp1.x = 3;
    if (y & 1)
      temp1.y = zz;
    memmove(&temp2,&temp1,sizeof temp1);
    return temp2.y;
  }

generiert Code, der x zurückgibt, wenn y Null ist, selbst wenn x außerhalb des Bereichs 0-65535 liegt. Der Standard stellt klar, dass das Lesen von vorzeichenlosen Zeichen mit unbestimmtem Wert garantiert einen Wert im Bereich von ergibt unsigned charund das Verhalten von memmove ist als Äquivalent zu einer Folge von Lese- und Schreibvorgängen für Zeichen definiert. Daher sollte temp2 einen Wert haben, der über eine Folge von Zeichenschreibvorgängen darin gespeichert werden könnte, aber gcc entscheidet sich dafür, das memmove durch eine Zuweisung zu ersetzen und die Tatsache zu ignorieren, dass der Code die Adressen von temp1 und temp2 verwendet hat.

Eine Möglichkeit zu haben, einen Compiler zu zwingen, eine Variable als einen beliebigen Wert ihres Typs zu betrachten, in Fällen, in denen ein solcher Wert gleichermaßen akzeptabel wäre, wäre hilfreich, aber der Standard gibt kein sauberes Mittel dafür an (außer zum Speichern eines bestimmten Werts, der funktionieren würde, aber oft unnötig langsam ist). Selbst Operationen, die eine Variable logisch dazu zwingen sollten, einen Wert zu halten, der als eine Kombination von Bits darstellbar wäre, können nicht zuverlässig auf allen Compilern funktionieren. Folglich kann für solche Variablen nichts Nützliches garantiert werden.

  • Um fair zu sein, gibt es einen oben verlinkten Mängelbericht über genau was Sie können mit einem unbestimmten Wert auskommen, und ein Teil der Entscheidung bestand darin, anzugeben, dass die Übergabe eines unbestimmten Werts an eine beliebige Bibliotheksfunktion UB ist. memmove ist eine Bibliotheksfunktion, die hier zutreffen würde.

    – BeeOnRope

    8. September 2017 um 3:36 Uhr


  • @BeeOnRope: Wenn die Autoren des Standards ein Mittel zum Auflösen unbestimmter Werte in im schlimmsten Fall nicht spezifizierte Werte aufgenommen hätten, wäre es vernünftig gewesen, die Verwendung solcher Mittel zu verlangen, bevor ansonsten unbestimmte Werte an Bibliotheksfunktionen übergeben werden. Angesichts des Fehlens solcher Mittel kann ich ihrer Entscheidung nur entnehmen, dass sie mehr daran interessiert sind, eine Sprache “einfach zu optimieren”, als ihre Nützlichkeit zu maximieren.

    – Superkatze

    8. September 2017 um 14:19 Uhr


  • @BeeOnRope: Ihre Begründung ist, dass das undefinierte Verhalten Compiler nicht daran hindern sollte, Verhalten zu definieren, wenn sie auf Prozessoren und Anwendungsfelder abzielen, in denen dies praktisch und nützlich wäre. Unabhängig davon, ob solche Entscheidungen des Ausschusses eine solche Wirkung haben sollten oder nicht, ist es leider offensichtlich, dass sie dies tun.

    – Superkatze

    8. September 2017 um 14:21 Uhr

  • Ich nehme an, ja, sie hätten eine Art von einführen können T std::freeze(T v) Methode, die einen “wackeligen” unbestimmten Wert in einen nicht spezifizierten, aber stabilen Wert verwandeln würde. Es hätte jedoch einen Nutzen “dritter Ordnung”: Die Verwendung eines unbestimmten Werts ist bereits obskur und wird sehr selten verwendet. Das Hinzufügen eines speziellen Konstrukts, nur um solche Werte zu festigen, scheint also nur weiter in das Kaninchenloch dessen zu gehen, was bereits eine obskure Ecke von ist der Standard, und es müsste in den zentralen Transformations-/Optimierungsphasen vieler Compiler unterstützt werden.

    – BeeOnRope

    8. September 2017 um 19:25 Uhr


  • @BeeOnRope: Die Möglichkeit, Werte einzufrieren, würde außerhalb der Situationen, in denen dies unerlässlich wäre, im Wesentlichen keine Kosten verursachen, und der Versuch, optimierten Code in seiner Abwesenheit zu debuggen, ist ein sicherer Weg in den Wahnsinn. Wenn einer schreibt foo=moo; if (foo < 100) bar(foo); und moo unerwartet von einem anderen Thread geändert wird, kann der Versuch zu diagnostizieren, wann und wo etwas schief gelaufen ist, im Wesentlichen unmöglich sein. Sagen können foo=moo; freeze(foo); if (foo < 100) bar(foo); und lassen Sie den Compiler auf einen Wert für festschreiben foo würde die Sache viel robuster machen.

    – Superkatze

    8. September 2017 um 22:47 Uhr

1417730cookie-checkIst a^a oder aa undefiniertes Verhalten, wenn a nicht initialisiert ist?

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

Privacy policy