Warum ist dies ein undefiniertes Verhalten?

Lesezeit: 5 Minuten

Benutzeravatar von ST3
ST3

Meine Antwort auf diese Frage war diese Funktion:

inline bool divisible15(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
    return x * 4008636143 <= 286331153;
}

Es funktionierte jedoch perfekt auf meiner Maschine mit dem VS2008-Compiler hier es geht gar nicht.

Hat jemand eine Idee, warum ich auf verschiedenen Compilern unterschiedliche Ergebnisse erhalte? unsigned Überlauf ist kein undefiniertes Verhalten.

Wichtiger Hinweis: Nach einigen Tests wurde bestätigt, dass es schneller ist, als den Rest der Division durch 15 zu nehmen. (Allerdings nicht bei allen Compilern)

  • Ist das schneller als (x % 15) == 0?

    – asveikau

    9. September 2013 um 20:57 Uhr

  • Es zeigt sich mir nicht als undefiniertes Verhalten? Es ist jedoch wahrscheinlich ein Integer-Überlauf.

    – PherricOxide

    9. September 2013 um 20:58 Uhr

  • Tut x * 4008636143 in ein int passen?

    – Andre

    9. September 2013 um 20:59 Uhr

  • @millimoose Nun … das sind ohne Vorzeichen int. Das Überlaufverhalten wird festgelegt.

    – Mystisch

    9. September 2013 um 21:04 Uhr

  • Aus Gründen der Lesbarkeit denke ich, dass dies besser geschrieben ist als return x * -(UINT_MAX / 15) <= (UINT_MAX / 15);, was das ganze Problem schön vermeidet. Ich war verwirrt, als ich versuchte, es zu verstehen, erkannte, dass es so umgeschrieben werden konnte, und verstand dann, warum es funktionierte 🙂 (Bearbeiten: Dies nimmt, wie Ihr Code, offensichtlich einen bestimmten Wert für an UINT_MAX.)

    Benutzer743382

    9. September 2013 um 22:38 Uhr


Benutzeravatar von Adam Rosenfield
Adam Rosenfield

Es ist kein undefiniertes Verhalten, es ist nur eine bahnbrechende Änderung im C-Sprachstandard zwischen C89 und C99.

In C89 passen ganzzahlige Konstanten wie 4008636143 nicht in eine int oder long int aber passen in ein unsigned int sind unsigniert, aber in C99 sind sie beides long int oder long long int (je nachdem, welcher der kleinste ist, der den Wert halten kann). Infolgedessen werden alle Ausdrücke mit 64 Bit ausgewertet, was zu einer falschen Antwort führt.

Visual Studio ist ein C89-Compiler und führt daher zum C89-Verhalten, aber dieser Ideone-Link kompiliert im C99-Modus.

Dies wird deutlicher, wenn Sie mit GCC kompilieren -Wall:

test.c: In function ‘divisible15’:
test.c:8:3: warning: this decimal constant is unsigned only in ISO C90

Aus C89 §3.1.3.2:

Der Typ einer Integer-Konstante ist der erste der entsprechenden Liste, in der ihr Wert dargestellt werden kann. Dezimalzahl ohne Suffix: int, long int, unsigned long int; Oktal oder Hexadezimal ohne Suffix: int, unsigned int, long int, unsigned long int; […]

C99 §6.4.4.1/5-6:

5) Der Typ einer ganzzahligen Konstante ist der erste der entsprechenden Liste, in der ihr Wert dargestellt werden kann.

Suffix | Decimal Constant | Octal or Hexadecimal Constant
-------+------------------+------------------------------
none   | int              | int
       | long int         | unsigned int
       | long long int    | long int
       |                  | unsigned long int
       |                  | long long int
       |                  | unsigned long long int
-------+------------------+------------------------------
[...]

6) Wenn eine ganzzahlige Konstante durch keinen Typ in ihrer Liste dargestellt werden kann, kann sie einen erweiterten ganzzahligen Typ haben, wenn der erweiterte ganzzahlige Typ ihren Wert darstellen kann. Wenn alle Typen in der Liste für die Konstante vorzeichenbehaftet sind, muss der erweiterte ganzzahlige Typ vorzeichenbehaftet sein. […]

Der Vollständigkeit halber hat C++03 tatsächlich ein undefiniertes Verhalten, wenn die Integer-Konstante zu groß ist, um in a zu passen long int. Aus C++03 §2.13.1/2:

Der Typ eines Integer-Literals hängt von seiner Form, seinem Wert und seinem Suffix ab. Wenn es dezimal ist und kein Suffix hat, hat es den ersten dieser Typen, in denen sein Wert dargestellt werden kann: int, long int; wenn der Wert nicht als a dargestellt werden kann long int, ist das Verhalten undefiniert. Wenn es oktal oder hexadezimal ist und kein Suffix hat, hat es den ersten dieser Typen, in denen sein Wert dargestellt werden kann: int, unsigned int, long int, unsigned
long int
. […]

Das C++11-Verhalten ist identisch mit C99, siehe C++11 §2.14.2/3.

Um sicherzustellen, dass sich der Code konsistent verhält, wenn er entweder als C89, C99, C++03 oder C++11 kompiliert wird, besteht die einfache Lösung darin, die Konstante 4008636143 unsigniert zu machen, indem ihr das Suffix mit hinzugefügt wird u wie 4008636143u.

  • +1; als Nachtrag wurden in C++ Dezimalkonstanten ohne Suffix immer signiert (sogar in C++03).

    – Avakar

    9. September 2013 um 21:20 Uhr

Benutzeravatar von Mats Petersson
Matt Petersson

Da Sie verwenden int Konstanten kann der Compiler den Überlauf “benutzen”, um den Code abzukürzen. Wenn Sie wie unten mit U ändern, “funktioniert”.

inline bool divisible15(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
    return x * 4008636143u <= 286331153u;
}

testen mit:

#include <iostream>


inline bool divisible15a(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
//    return x * 4008636143 <= 286331153;
    return x * 4008636143u <= 286331153;
}

inline bool divisible15b(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
//    return x * 4008636143 <= 286331153;
    return x * 4008636143 <= 286331153;
}


int main()
{
    for(unsigned int i = 0; i < 100; i++)
    {
    if (divisible15a(i))
    {
        std::cout << "a:" << i << std::endl;
    }
    if (divisible15b(i))
    {
        std::cout << "b:" << i << std::endl;
    }
    }
}

Ausgabe:

a:0
b:0
a:15
a:30
a:45
a:60
a:75
a:90

Code:

_Z12divisible15aj:
.LFB1192:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %eax
    imull   $-286331153, %eax, %eax
    cmpl    $286331153, %eax
    setbe   %al
    popq    %rbp
    ret

_Z12divisible15bj:
.LFB1193:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %edx
    movl    $4008636143, %eax
    imulq   %rdx, %rax
    cmpq    $286331153, %rax
    setle   %al
    popq    %rbp
    ret

Also, ja, ich stimme der Analyse von Carl/Adam zu, dass es nicht in ein 32-Bit-Int passt, also zu befördert wird long oder long long.

  • Standard-Integer-Promotions sorgen bereits dafür, dass sich das korrekt verhält, oder? Und die eine Konstante, die zu groß ist, wird von selbst „vergrößert“, IIRC?

    – Karl Norum

    9. September 2013 um 21:07 Uhr


  • Es scheint, dass sich zumindest g ++ 4.6.3 zwischen der Verwendung von ‘u’ und der Nichtverwendung unterschiedlich verhält. Kann natürlich ein Bug sein.

    – Mats Petersson

    9. September 2013 um 21:09 Uhr

  • klirren auch. Noch etwas nachdenken.

    – Karl Norum

    9. September 2013 um 21:10 Uhr


  • @CarlNorum: Aha, ist das Thema ohne das uwird das Literal als a interpretiert signed longund somit x wird befördert signed long zu?

    – Oliver Charlesworth

    9. September 2013 um 21:12 Uhr

  • Oder long longja.

    – Karl Norum

    9. September 2013 um 21:13 Uhr

1412930cookie-checkWarum ist dies ein undefiniertes Verhalten?

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

Privacy policy