为什么这是一个未定义的行为?

ST3*_*ST3 59 c c++ undefined-behavior

我对这个问题的回答是这个函数:

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

它完全适用于我的机器与VS2008编译器,但在这里它根本不起作用.

有没有人有想法,为什么我在不同的编译器上得到不同的结果? unsigned溢出不是未定义的行为.

重要提示:经过一些测试后,确认它比将除法的余数除以15更快.(但不是在所有编译器上)

Ada*_*eld 98

它不是Undefined Behavior,它只是C89和C99之间C语言标准的重大变化.

在C89中,像4008636143这样的整数常量(不适合int或者long int适合于a)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
Run Code Online (Sandbox Code Playgroud)

从C89§3.1.3.2开始:

整数常量的类型是相应列表中可以表示其值的第一个.unsuffixed decimal:int,long int,unsigned long int; unsuffixed octal或hexadecimal: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
-------+------------------+------------------------------
[...]
Run Code Online (Sandbox Code Playgroud)

6)如果整数常量不能由其列表中的任何类型表示,则它可以具有扩展整数类型,如果扩展整数类型可以表示其值.如果常量列表中的所有类型都已签名,则应对扩展整数类型进行签名.[...]

为了完整性,当整数常量太大而不适合时,C++ 03实际上确实有未定义的行为long int.来自C++03§2.13.1/ 2:

整数文字的类型取决于其形式,值和后缀.如果它是十进制且没有后缀,则它具有这些类型中的第一个,其值可以表示为:int,long int; 如果该值不能表示为a 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为as 4008636143u.


Mat*_*son 9

由于您使用int常量,编译器可以"使用"未定义的溢出来快捷代码.如果你用U修改如下,它"工作".

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

测试:

#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;
    }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

a:0
b:0
a:15
a:30
a:45
a:60
a:75
a:90
Run Code Online (Sandbox Code Playgroud)

码:

_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
Run Code Online (Sandbox Code Playgroud)

所以,是的,我同意Carl/Adam的分析,它不适合32位int,所以因此被提升为longlong long.

  • 默认的整数提升已经使行为正确,对吧?那个太大的常数会自行"升级",IIRC? (2认同)
  • @CarlNorum:啊哈,问题是没有`u`,文字被解释为`signed long`,因此`x`也被提升为`signed long`? (2认同)