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


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

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

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

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

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

2 60

2 ответа:

это не неопределенное поведение, это просто разрывное изменение в стандарте языка 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:

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

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

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

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

поведение C++11 идентично C99, см. c++11 §2.14.2 / 3.

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

если вы используете int константы, компилятор может "использовать" переполнение не определено для быстрого доступа к коду. Если вы измените с 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    6331153, %eax
    setbe   %al
    popq    %rbp
    ret

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

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