Почему это неопределенное поведение?
мой ответ этот вопрос была такая функция:
inline bool divisible15(unsigned int x)
{
//286331153 = (2^32 - 1) / 15
//4008636143 = (2^32) - 286331153
return x * 4008636143 <= 286331153;
}
Он отлично работал на моей машине с компилятором VS2008, однако здесь это вообще не работает.
кто-нибудь есть идея, почему я получаю разные результаты на разных компиляторах? unsigned
переполнение не является неопределенным поведением.
важное замечание: после некоторого теста было подтверждено, что это быстрее, чем принимать оставшуюся часть деления на 15. (Однако не на всех компиляторах)
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
.