Почему это поведение undefined?

Мой ответ на этот вопрос был этой функцией:

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

Он отлично работал на моей машине с компилятором VS2008, однако здесь он вообще не работает.

Есть ли у кого-то идея, почему у меня разные результаты для разных компиляторов? unsigned переполнение не является undefined.

Важное замечание: после некоторого теста было подтверждено, что он быстрее, чем оставление деления на 15. (Однако не для всех компиляторов)

Ответ 1

Это не Undefined Поведение, это просто потрясающее изменение в стандарте языка C между C89 и C99.

В C89 целочисленные константы, такие как 4008636143, которые не помещаются в int или long int, но вписываются в unsigned int, не имеют знака, но на C99 они либо long int, либо long long int ( в зависимости от того, какой из них является наименьшим, который может удерживать значение). В результате все выражения оцениваются с использованием 64 бит, что приводит к неправильному ответу.

Visual Studio является компилятором C89 и поэтому приводит к поведению C89, но эта ссылка Ideone компилируется в режиме C99.

Это становится более очевидным, если вы скомпилируете GCC с помощью -Wall:

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

Из C89 §3.1.3.2:

Тип целочисленной константы является первым из соответствующих список, в котором может быть представлено его значение. Unsuffixed decimal: int, long int, unsigned long int; unsuffixed восьмеричный или шестнадцатеричный: int, unsigned int, long int, unsigned long int; [...]

C99 §6.4.4.1/5-6:

5) Тип целочисленной константы является первым из соответствующего списка, в котором его значение может быть представленным.

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) Если целочисленная константа не может быть представлена ​​каким-либо типом в ее списке, она может иметь расширенный целочисленный тип, если расширенный целочисленный тип может представлять его значение. Если все типы в списке для константы подписаны, расширенный целочисленный тип должен быть подписан. [...]

Для полноты, С++ 03 действительно имеет Undefined Поведение, когда целочисленная константа слишком велика, чтобы соответствовать в long int. Из С++ 03 §2.13.1/2:

Тип целочисленного литерала зависит от его формы, значения и суффикса. Если он десятичен и не имеет суффикса, он имеет первый из этих типов, в котором может быть представлено его значение: int, long int; если значение не может быть представлено как long int, поведение undefined. Если он восьмеричный или шестнадцатеричный и не имеет суффикса, он имеет первый из этих типов, в котором может быть представлено его значение: int, unsigned int, long int, unsigned long int. [...]

Поведение С++ 11 идентично C99, см. С++ 11 §2.14.2/3.

Чтобы гарантировать постоянство поведения кода при компиляции как C89, C99, С++ 03 и С++ 11, простое исправление состоит в том, чтобы сделать константу 4008636143 неподписанной, суффиктируя ее с помощью u как 4008636143u.

Ответ 2

Поскольку вы используете константы int, компилятор может "использовать" переполнение undefined, чтобы сократить код. Если вы модифицируете U, как показано ниже, он "работает".

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

тестирование с помощью:

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

Вывод:

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

код:

_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

Итак, да, я согласен с анализом Carl/Adam, что он не вписывается в 32-битный int, поэтому его повышают до long или long long.