it-swarm.com.de

Ist (int32_t) 255 << 24 undefiniertes Verhalten in gcc (C++ 11)?

In C++ 11 gemäß de.cppreference.com ,

Für vorzeichenbehaftete und nicht negative a ist der Wert von a << b a * 2b wenn es im Rückgabetyp darstellbar ist, Ansonsten ist das Verhalten undefiniert.

Mein Verständnis ist das seit 255 * 224 nicht als int32_t darstellbar ist, ergibt die Auswertung von (int32_t) 255 << 24 ein undefiniertes Verhalten. Ist das korrekt? Kann das Compilerabhängig sein? Es ist eine IP16-Umgebung, wenn das wichtig ist.

Hintergrund: Dies kommt von einem Argument, das ich habe mit einem Benutzer bei arduino.stackexchange.com. Ihm zufolge "gibt es überhaupt nichts Undefiniert darüber":

sie stellen fest, dass ein Großteil der Bitverschiebung "Implementierung definiert" ist. Sie können also Kapitel und Vers aus den Spezifikationen nicht zitieren. Du musst gehen zur GCC-Dokumentation, da dies der einzige Ort ist, der .__ sagen kann. Sie, was tatsächlich passiert . gnu.org/software/gnu-c-manual/gnu-c-manual.html#Bit-Shifting - es ist nur "undefined" für einen negativen Verschiebungswert.


Edit: Aus den bisherigen Antworten scheint es, als würde ich den C++ 11-Standard lesen. Dann ist der Schlüssel meiner Frage, ob Dieser Ausdruck ein undefiniertes Verhalten in gcc auslöst. Wie davmac es in seinem Kommentar formuliert, frage ich "ob GCC, eine Implementierung, ein Verhalten definiert, obwohl es durch den Sprachstandard nicht definiert ist".

Aus dem gcc-Handbuch, mit dem ich verlinkt habe, scheint es in der Tat definiert zu sein, obwohl ich den Wortlaut dieses Handbuchs eher nach einem Tutorial klingt als nach einem "Sprachgesetz". Aus der Antwort von PSkocik (und Kanes Kommentar zu dieser Antwort) scheint es stattdessen, dass es undefiniert ist. Ich habe also immer noch Zweifel.

Ich denke, mein Traum wäre, eine klare Aussage in einer Dokumentation von gcc Zu haben, die besagt, dass 1) gcc kein Verhalten .__ definiert, das im Standard explizit undefiniert ist, oder 2) gcc dies definiert Verhalten von Version XX.XX und verpflichtet sich, es in allen nachfolgenden Versionen definiert zu behalten.

Edit 2: PSkocik hat seine Antwort gelöscht, was ich sehr schade finde, dait interessante Informationen lieferte. Aus seiner Antwort, Kanes Kommentar zu ... der Antwort und meinen eigenen Experimenten:

  1. (int32_t)255<<24 erzeugt einen Laufzeitfehler, wenn mit clang und -fsanitize=undefined kompiliert wird.
  2. derselbe Code erzeugt bei g ++ selbst bei -fsanitize=undefined keinen Fehler.
  3. (int32_t)256<<24 gibt einen Laufzeitfehler aus, wenn mit g++ -std=c++11 -fsanitize=undefined kompiliert wird.

Punkt 2 stimmt mit der Interpretation überein, dass gcc im C++ 11-Modus Die Linksverschiebung weiter als den Standard definiert. Gemäß Punkt 3 kann diese Definition nur die C++ 14-Definition sein. Punkt 3 ist jedoch inkonsistent mit der Idee, dass das referenzierte Handbuch eine vollständige Definition von << in gcc (C++ 11-Modus) ist, wie dieses Handbuch vorsiehtkeine Andeutung, dass (int32_t)256<<24 undefiniert sein könnte.

9
Edgar Bonet

Das hat sich mit der Zeit geändert, und das aus gutem Grund. Gehen wir also die Geschichte durch. Beachten Sie, dass in allen Fällen das einfache Ausführen von static_cast<int>(255u << 24) immer ein definiertes Verhalten war. Vielleicht tun Sie das einfach und umgehen alle Probleme.


Der ursprüngliche C++ 11 Wortlaut lautete:

Der Wert von E1 << E2 Ist E1 Linksverschobene E2 Bitpositionen; frei gewordene Bits werden mit Nullen gefüllt. Wenn E1 Einen vorzeichenlosen Typ hat, ist der Wert des Ergebnisses E1×2E2. Reduzieren Sie das Modulo um eins mehr als den im Ergebnistyp darstellbaren Maximalwert. Wenn andernfalls E1 Einen vorzeichenbehafteten Typ und einen nicht negativen Wert hat und E1×2E2 Im Ergebnistyp darstellbar ist, ist dies der resultierende Wert. Andernfalls ist das Verhalten undefiniert.

255 << 24 Ist undefiniertes Verhalten in C++ 11, da der resultierende Wert als 32-Bit-Ganzzahl mit Vorzeichen nicht darstellbar ist und zu groß ist.

Dieses undefinierte Verhalten verursacht einige Probleme, da constexpr muss undefiniertes Verhalten diagnostiziert - und daher führten einige gängige Ansätze zum Festlegen von Werten zu schwerwiegenden Fehlern. Daher CWG 1457 :

Der derzeitige Wortlaut von 8.8 [expr.shift] Absatz 2 macht es undefiniert, die negativste Ganzzahl eines bestimmten Typs durch Linksverschiebung einer (vorzeichenbehafteten) 1 in das Vorzeichenbit zu erstellen, obwohl dies nicht ungewöhnlich ist und funktioniert korrekt auf den meisten (Zweierkomplement-) Architekturen [...] Folglich kann diese Technik nicht in einem konstanten Ausdruck verwendet werden, der eine erhebliche Menge an Code zerstört.

Dies war ein Defekt gegenüber C++ 11. Technisch gesehen würde ein konformer C++ 11-Compiler alle Fehlerberichte implementieren, und es wäre daher richtig zu sagen, dass dies in C++ 11 nicht undefiniertes Verhalten ist; Das Verhalten für 255 << 24 in C++ 11 ist als -16777216 definiert.

Der Wortlaut nach dem Defekt ist in C++ 14 zu sehen:

Der Wert von E1 << E2 Ist E1 Linksverschobene E2 Bitpositionen; frei gewordene Bits werden mit Nullen gefüllt. Wenn E1 Einen vorzeichenlosen Typ hat, ist der Wert des Ergebnisses E1×2E2. Reduzieren Sie das Modulo um eins mehr als den im Ergebnistyp darstellbaren Maximalwert. Andernfalls, wenn E1 Einen vorzeichenbehafteten Typ und einen nicht negativen Wert hat und E1×2E2 Im entsprechenden vorzeichenlosen Typ des Ergebnistyps darstellbar ist, dann ist der in den Ergebnistyp umgewandelte Wert der resultierende Wert; Andernfalls ist das Verhalten undefiniert.

In C++ 17 wurden keine Änderungen am Wortlaut/Verhalten vorgenommen.

In C++ 20 ist der Wortlaut aufgrund des Signed Integer are Two's Complement (und des Wording Paper ) stark vereinfacht :

Der Wert von E1 << E2 Ist der eindeutige Wert, der mit E1×2E2 Modulo 2N Kongruent ist, wobei N der Bereichsexponent des Ergebnistyps ist.

255 << 24 Hat immer noch definiertes Verhalten in C++ 20 (mit demselben resultierenden Wert), es ist nur so, dass die Spezifikation, wie wir dorthin gelangen, viel einfacher wird, weil die Sprache die Tatsache nicht umgehen muss Die Darstellung für vorzeichenbehaftete ganze Zahlen wurde implementierungsdefiniert.

8
Barry

"Undefiniertes Verhalten ist etwas, für das der Standard keine Anforderungen stellt." was bedeutet, dass es sogar das erwartete/korrekte Verhalten sein kann. Dieser C++ - Standard gibt in Bezug auf Schichtoperatoren an.

8.5.7 Shift-Operatoren [expr.shift] (C++ - Standardentwurf N4713)

  1. Die Operanden müssen vom Typ der integralen oder nicht begrenzten Aufzählung sein und integrale Promotionen werden ausgeführt. Der Typ des Ergebnisses ist der des beförderten linken Operanden. Das Verhalten ist nicht definiert, wenn der rechte Operand negativ ist oder größer oder gleich der Bitlänge des beförderten linken Operanden ist. Andernfalls, wenn E1 einen vorzeichenbehafteten Typ und einen nicht negativen Wert hat und E1×2E2 im entsprechenden vorzeichenlosen Typ des Ergebnistyps darstellbar ist, ist der in den Ergebnistyp konvertierte Wert der resultierende Wert. Andernfalls ist das Verhalten nicht definiert. 

Wie @rustyx anmerkt, "Der Wortlaut" E1×2E2 ist in dem entsprechenden vorzeichenlosen Typ des Ergebnistyps "darstellbar" ist C++ 14 . Noch immer UB in C++ 11 leider. " 

3
P.W

Der Compiler GNU C definiert die Links/Rechts-Verschiebungen vollständig wie in manual beschrieben:

GCC unterstützt nur zwei komplementäre Integer-Typen, und alle Bitmuster sind normale Werte.

. . .

Als Erweiterung der C-Sprache verwendet GCC den in C99 und C11 angegebenen Spielraum nicht nur, um bestimmte Aspekte des signierten << als undefiniert zu behandeln. -fsanitize=shift (und -fsanitize=undefined) werden jedoch solche Fälle diagnostizieren. Sie werden auch diagnostiziert, wenn konstante Ausdrücke erforderlich sind.

Das stimmt also mit Ihren Befunden überein - der Code entspricht dem, was Sie erwarten, nur ein Überlauf über die verfügbaren Bits (einschließlich des Vorzeichenbits) wird diagnostiziert.

Was den Compiler GNU C++ anbelangt, scheint die Dokumentation wirklich fehlt . Wir können nur vermuten, dass durch Verschieben die Verschiebung in G ++ genauso funktioniert wie in GCC, obwohl zumindest der Sanitizer die Sprachunterschiede zu kennen scheint:

-fsanitize=shift 

Mit dieser Option können Sie überprüfen, ob das Ergebnis eines Schichtvorgangs nicht undefiniert ist. Was genau als undefiniert betrachtet wird, unterscheidet sich geringfügig zwischen C und C++ sowie zwischen ISO C90 und C99 usw.

3
rustyx

Für C++ - Compiler vor C++ 14, wenn Sie eine Funktion wie die folgende hätten:

// check if the input is still positive,
// after a suspicious shift

bool test(int input)
{
    if (input > 0) 
    {
        input = input << 24;

        // how can this become negative in C++11,
        // unless you are relying on UB?

        return (input > 0); 
    }
    else 
    {
        return false;
    }
}

dann hätte ein optimierender Compiler es ändern können:

bool test(int input)
{
    // unless you are relying on UB, 
    // input << 24 must fit into an int,
    // and input is positive in that branch

    return (input > 0);
}

Und alle sind glücklich, weil Sie in Ihren Release-Builds eine Beschleunigung von Nice erhalten.

Ich kenne jedoch keine Compiler, die solche Optimierungen für Left-Shifts durchführen, obwohl Zusätze häufig wegoptimiert werden, wie in diesem Beispiel zu sehen ist.

0
Groo