59 votes

Pourquoi est-ce un comportement indéfini?

Ma réponse à cette question était à cette fonction:

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

Il a parfaitement fonctionné sur ma machine avec VS2008 compilateur, cependant , ici, il ne fonctionne pas du tout.

Est-ce quelqu'un a une idée, pourquoi j'obtiens des résultats différents sur différents compilateurs? unsigned de dépassement n'est pas un comportement indéfini.

Remarque importante: après quelques test, il a été confirmé qu'il est plus rapide que de prendre le reste de la division par 15. (Mais pas sur tous les compilateurs)

98voto

Adam Rosenfield Points 176408

Ce n'est pas un Comportement Indéfini, c'est juste une modification de rupture dans la norme du langage C entre C89 et C99.

En C89, les constantes entières comme 4008636143 qui ne rentrent pas dans un int ou long int mais ne fit en unsigned int ne sont pas signés, mais en C99, ils sont soit long int ou long long int (selon celle qui est la plus petite qui peut contenir la valeur). En conséquence, les expressions sont évaluées à l'aide de 64 bits, ce qui entraîne une mauvaise réponse.

Visual Studio est un C89 compilateur et donc les résultats dans le C89 comportement, mais que Ideone lien est en train de compiler en C99 mode.

Cela devient encore plus évident si l'on compile avec GCC en utilisant -Wall:

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

De C89 §3.1.3.2:

Le type d'une constante entière est la première de l'correspondant liste dans laquelle sa valeur peut être représentée. Unsuffixed décimal: int, long int, unsigned long int; unsuffixed octal ou hexadécimal: int, unsigned int, long int, unsigned long int; [...]

C99 §6.4.4.1/5-6:

5) Le type d'une constante entière est le premier de la liste correspondante dans lequel sa valeur peut être représentée.

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) Si une constante entière ne peut pas être représenté par n'importe quel type dans sa liste, il peut avoir une étendue de type entier, si l'étendue de type entier peut représenter sa valeur. Si toutes les types dans la liste de la constante sont signés, l'étendue de type entier doit être signé. [...]

Pour être complet, C++03 réellement n'avez pas défini de Comportement pour quand la constante entière est trop gros pour rentrer dans un long int. À Partir De C++03 §2.13.1/2:

Le type d'un littéral entier dépend de sa forme, de la valeur, et le suffixe. Si c'est décimal et n'a pas de suffixe, il a le premier de ces types dans lequel sa valeur peut être représentée: int, long int; si la valeur ne peut pas être représenté en tant que long int, le comportement est indéfini. Si c'est octal ou hexadécimal et n'a pas de suffixe, il a l' la première de ces types dans lequel sa valeur peut être représentée: int, unsigned int, long int, unsigned long int. [...]

Le C++11 comportement est identique à C99, voir C++11 §2.14.2/3.

Pour s'assurer que le code se comporte de manière uniforme lors de la compilation comme C89, C99, C++03, et de C++11, la seule solution est de faire de la constante 4008636143 non signé en suffixant il avec u comme 4008636143u.

9voto

Mats Petersson Points 70074

Puisque vous utilisez int des constantes, le compilateur peut "utiliser" le débordement est pas défini au raccourci le code. Si vous modifiez avec U comme ci-dessous, il "fonctionne".

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

test avec:

#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;
    }
    }
}

Sortie:

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

Donc, oui, je suis d'accord avec Carl/Adam de l'analyse qu'il ne rentre pas dans un int 32-bits, donc promu long ou long long.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X