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




неопределенное поведение c++ (2)

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

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

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

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

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

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

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

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


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





undefined-behavior