macos GCC 左移溢出

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3871650/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 07:15:15  来源:igfitidea点击:

GCC left shift overflow

cmacosgcc

提问by Lucas

The following little program is very awkward using GCC version 4.2.1 (Apple Inc. build 5664) on a Mac.

下面这个小程序在 Mac 上使用 GCC 版本 4.2.1 (Apple Inc. build 5664) 非常笨拙。

#include <stdio.h>

int main(){
        int x = 1 << 32;
        int y = 32;
        int z = 1 << y;
        printf("x:%d, z: %d\n", x, z);
}

The result is x:0, z: 1.
Any idea why the values of x and z are different?
Thanks a lot.

结果是x:0, z: 1
知道为什么 x 和 z 的值不同吗?
非常感谢。

采纳答案by PP.

Short answer: the Intel processor masks the shift count to 5 bits (maximum 31). In other words, the shift actually performed is 32 & 31, which is 0 (no change).

简短回答:英特尔处理器将移位计数屏蔽为 5 位(最大 31)。换句话说,实际执行的移位是 32 & 31,即 0(无变化)。

The same result appears using gcc on a Linux 32-bit PC.

在 Linux 32 位 PC 上使用 gcc 会出现相同的结果。

I assembled a shorter version of this program because I was puzzled by why a left shift of 32 bits should result in a non-zero value at all:

我组装了这个程序的较短版本,因为我对为什么左移 32 位会导致非零值感到困惑:

int main(){
    int y = 32;
    unsigned int z = 1 << y;
    unsigned int k = 1;
    k <<= y;
    printf("z: %u, k: %u\n", z, k);
}

..using the command gcc -Wall -o a.s -S deleteme.c(comments are my own)

..使用命令gcc -Wall -o a.s -S deleteme.c(评论是我自己的)

main:
leal    4(%esp), %ecx
andl    $-16, %esp
pushl   -4(%ecx)
pushl   %ebp
movl    %esp, %ebp
pushl   %ecx
subl    , %esp
movl    , -16(%ebp)  ; y = 32
movl    -16(%ebp), %ecx ; 32 in CX register
movl    , %eax        ; AX = 1
sall    %cl, %eax       ; AX <<= 32(32)
movl    %eax, -12(%ebp) ; z = AX
movl    , -8(%ebp)    ; k = 1
movl    -16(%ebp), %ecx ; CX = y = 32
sall    %cl, -8(%ebp)   ; k <<= CX(32)
movl    -8(%ebp), %eax  ; AX = k
movl    %eax, 8(%esp)
movl    -12(%ebp), %eax
movl    %eax, 4(%esp)
movl    $.LC0, (%esp)
call    printf
addl    , %esp
popl    %ecx
popl    %ebp
leal    -4(%ecx), %esp
ret

Ok so what does this mean? It's this instruction that puzzles me:

好的,这是什么意思?正是这个指令让我感到困惑:

sall    %cl, -8(%ebp)   ; k <<= CX(32)

Clearly k isbeing shifted left by 32 bits.

显然ķ移位由32位左。

You've got me - it's using the sallinstruction which is an arithmetic shift. I don't know why rotating this by 32 results in the bit re-appearing in the initial position. My initial conjecture would be that the processor is optimised to perform this instruction in one clock cycle - which means that any shift by more than 31 would be regarded as a don't care. But I'm curious to find the answer to this because I would expect that the rotate should result in all bits falling off the left end of the data type.

你明白了 - 它使用的sall算术移位指令。我不知道为什么将其旋转 32 会导致该位重新出现在初始位置。我最初的猜测是处理器经过优化,可以在一个时钟周期内执行这条指令——这意味着任何超过 31 的移位都将被视为无关紧要。但是我很想找到这个问题的答案,因为我希望旋转应该导致所有位都从数据类型的左端脱落。

I found a link to http://faydoc.tripod.com/cpu/sal.htmwhich explains that the shift count (in the CL register) is masked to 5 bits. This means that if you tried to shift by 32 bits the actual shift performed would be by zero bits (i.e. no change). There's the answer!

我找到了一个指向http://faydoc.tripod.com/cpu/sal.htm的链接,它解释了移位计数(在 CL 寄存器中)被屏蔽为 5 位。这意味着如果您尝试移动 32 位,实际执行的移动将是零位(即没有变化)。答案来了!

回答by pmg

If your intsare 32 bits or shorter, the behaviour is undefined ... and undefined behaviour cannot be explained.

如果您ints是 32 位或更短,则行为未定义......并且无法解释未定义的行为

The Standard says:

标准说:

6.5.7/3 [...] If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

6.5.7/3 [...] 如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。



You can check your intwidthbit size, for example with:

您可以检查您的int宽度位大小,例如:

#include <limits.h>
#include <stdio.h>
int main(void) {
    printf("bits in an int: %d\n", CHAR_BIT * (int)sizeof (int));
    return 0;
}

And you can check your intwidth (there can be padding bits), for example with:

你可以检查你的int宽度(可以有填充位),例如:

#include <limits.h>
#include <stdio.h>
int main(void) {
    int width = 0;
    int tmp = INT_MAX;
    while (tmp) {
        tmp >>= 1;
        width++;
    }
    printf("width of an int: %d\n", width + 1 /* for the sign bit */);
    return 0;
}

Standard 6.2.6.2/2: For signed integer types, the bits of the object representation shall be divided into three groups: value bits, padding bits, and the sign bit. There need not be any padding bits; there shall be exactly one sign bit

标准 6.2.6.2/2:对于有符号整数类型,对象表示的位应分为三组:值位、填充位和符号位。不需要任何填充位;应该只有一个符号位

回答by JeremyP

The C99 standard says that the result of shifting a number by the width in bits (or more) of the operand is undefined. Why?

C99 标准说,将数字移位操作数的位(或更多)宽度的结果是未定义的。为什么?

Well this allows compilers to create the most efficient code for a particular architecture. For instance, the i386 shift instruction uses a five bit wide field for the number of bits to shift a 32 bit operand by. The C99 standard allows the compiler to simply take the bottom five bits of the shift count and put them in the field. Clearly this means that a shift of 32 bits (= 100000 in binary) is therefore identical to a shift of 0 and the result will therefore be the left operand unchanged.

好吧,这允许编译器为特定架构创建最有效的代码。例如,i386 移位指令使用一个 5 位宽的字段来表示将 32 位操作数移位的位数。C99 标准允许编译器简单地获取移位计数的底部五位并将它们放入字段中。显然,这意味着 32 位的移位(= 100000 二进制)因此与 0 的移位相同,因此结果将是左操作数不变。

A different CPU architecture might use a wider bit field, say 32 bits. The compiler can still put the shift count directly in the field but this time the result will be 0 because a shift of 32 bits will shift all the bits out of the left operand.

不同的 CPU 架构可能使用更宽的位域,比如 32 位。编译器仍然可以将移位计数直接放在字段中,但这次结果将为 0,因为 32 位移位会将所有位移出左操作数。

If the C99 defined one or other of these behaviours as correct, either the compiler for Intel has to put special checking in for shift counts that are too big or the compiler for non i386 has to mask the shift count.

如果 C99 将这些行为中的一个或其他定义为正确,则 Intel 的编译器必须对过大的移位计数进行特殊检查,或者非 i386 的编译器必须屏蔽移位计数。

The reason why

之所以

   int x = 1 << 32;

and

   int z = 1 << y;

give different results is because the first calculation is a constant expression and can be performed entirely by the compiler. The compiler must be calculating constant expressions by using 64 bit arithmetic. The second expression is calculated by the code generated by the compiler. Since the type of both y and z is intthe code generates a calculation using 32 bit wide ints (int is 32 bits on both i386 and x86_64 with gcc on Apple).

给出不同的结果是因为第一次计算是一个常量表达式,完全可以由编译器执行。编译器必须使用 64 位算术计算常量表达式。第二个表达式由编译器生成的代码计算。由于 y 和 z 的类型都是int代码生成使用 32 位宽 int 的计算(int 在 i386 和 x86_64 上都是 32 位,在 Apple 上使用 gcc)。

回答by Stephan Korsholm

In my mind "int x = y << 32;" does not make sense if sizeof(int)==4.

在我看来“int x = y << 32;” 如果 sizeof(int)==4 没有意义。

But I had a similar issue with:

但我有一个类似的问题:

long y = ... long x = y << 32;

长 y = ... 长 x = y << 32;

Where I got a warning "warning: left shift count >= width of type" even though sizeof(long) was 8 on the target in question. I got rid of the warning by doing this instead:

即使 sizeof(long) 在相关目标上为 8,我还是收到了警告“警告:左移计数 >= 类型宽度”。我通过这样做来消除警告:

long x = (y << 16) << 16;

长 x = (y << 16) << 16;

And that seemed to work.

这似乎奏效了。

On a 64 bit architecture there was no warning. On a 32 bit architecture there was.

在 64 位架构上没有警告。在 32 位架构上有。