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

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

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

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

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

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


person ST3    schedule 09.09.2013    source источник
comment
Это быстрее, чем (x % 15) == 0?   -  person asveikau    schedule 10.09.2013
comment
Это не показывает мне неопределенное поведение? Это, вероятно, целочисленное переполнение.   -  person PherricOxide    schedule 10.09.2013
comment
@asveikau зависит от оптимизации компилятора   -  person ST3    schedule 10.09.2013
comment
Почти максимальное значение int, умноженное на int, явно выходит за пределы int. Кажется, что любой компилятор не пытается предотвратить переполнение, используя long для этого шага вычисления.   -  person millimoose    schedule 10.09.2013
comment
Подходит ли x * 4008636143 внутри int?   -  person andre    schedule 10.09.2013
comment
@andre Даже близко нет.   -  person millimoose    schedule 10.09.2013
comment
потому что вы используете c99 strick   -  person Logan Murphy    schedule 10.09.2013
comment
Я бы сказал, что это неопределенное поведение, потому что результат умножения int на int должен снова быть int. Выполнение вычисления с использованием longs не имеет особого смысла, если результат все равно будет переполнять тип самого выражения. Похоже, VS здесь более оптимистичен и старается сохранить информацию на всякий случай.   -  person millimoose    schedule 10.09.2013
comment
@millimoose Ну... это целые числа без знака. Определено поведение переполнения.   -  person Mysticial    schedule 10.09.2013
comment
@millimoose: я тоже пришел к такому выводу, но для беззнаковых целых не происходит переполнения ...   -  person Oliver Charlesworth    schedule 10.09.2013
comment
поэтому это должно быть неопределенное поведение или неопределенное или определяемое реализацией поведение.   -  person avakar    schedule 10.09.2013
comment
@LoganMurphy, теперь я это вижу, но что не так с переполнением без знака int в этом компиляторе?   -  person ST3    schedule 10.09.2013
comment
Можете ли вы принудительно выполнить 32-битную компиляцию (-m32)?   -  person ninjalj    schedule 10.09.2013
comment
Измените его на return x * (unsigned int)(4008636143) <= (unsigned int)(286331153);   -  person lapk    schedule 10.09.2013
comment
Для удобочитаемости я думаю, что это лучше записать как return x * -(UINT_MAX / 15) <= (UINT_MAX / 15);, что позволяет избежать всей проблемы. Я запутался, пытаясь понять это, понял, что это можно переписать так, а затем понял, почему это работает :) (Редактировать: это, как и ваш код, очевидно, предполагает определенное значение для UINT_MAX.)   -  person    schedule 10.09.2013
comment
(Я могу опубликовать демонстрацию того, почему он на самом деле проверяет, является ли x divisible by 15, если кому-то интересно)   -  person Aurélien Ooms    schedule 10.09.2013


Ответы (2)


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

Тип целочисленной константы является первым из соответствующего списка, в котором может быть представлено ее значение. Десятичное без суффикса: int, long int, unsigned long int; восьмеричное или шестнадцатеричное без суффикса: 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) Если целочисленная константа не может быть представлена ​​ни одним типом в своем списке, она может иметь расширенный целочисленный тип, если расширенный целочисленный тип может представлять ее значение. Если все типы в списке для константы подписаны, то расширенный целочисленный тип должен быть подписан. [...]

Для полноты картины C++03 на самом деле имеет Undefined Behavior, когда целочисленная константа слишком велика, чтобы поместиться в long int. Из С++ 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.

person Adam Rosenfield    schedule 09.09.2013
comment
+1; в качестве дополнения, в C++ десятичные константы без суффикса всегда были подписаны (даже в C++03). - person avakar; 10.09.2013

Поскольку вы используете константы 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

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

person Mats Petersson    schedule 09.09.2013
comment
Целочисленные акции по умолчанию уже заставляют это вести себя правильно, верно? И одна константа, которая слишком велика, сама по себе «увеличивается», IIRC? - person Carl Norum; 10.09.2013
comment
Казалось бы, по крайней мере, g++ 4.6.3 ведет себя по-разному между использованием 'u' и его отсутствием. Может конечно баг. - person Mats Petersson; 10.09.2013
comment
лязг тоже. Подумав еще немного. - person Carl Norum; 10.09.2013
comment
@CarlNorum: Ага, проблема в том, что без u литерал интерпретируется как signed long, и, таким образом, x тоже повышается до signed long? - person Oliver Charlesworth; 10.09.2013
comment
Или long long, да. - person Carl Norum; 10.09.2013