cbit-manipulationinteger-overflow

Why does bit shifting with a large amount work in C?


I am a beginner in C and mess with its quirks. Why does this code even work?

#include <stdio.h>

int main() {
    unsigned char i = 126;
    i = (i << 1000000000000000000000000000000000) % 10;
    printf("%d", i);

    return 0;
}

i can virtually handle up to 6 bit shifts, but I am able to do this. The value I use for shifting the bits actually doesn't matter, unless I am above 6. I am getting the value as 6 and not 0. I believe it is the correct answer because I tried it with a higher width data type and a sensible left shift amount.

Obviously I know that I am using a modulo function, but my question is, shouldn't it be bit shifting it with that value which would virtually result in a 0 and then perform mod on it?


Solution

  • It doesn't "work", but is actually undefined behavior, which means no guarantees are made as to what the program will do. In fact one of the ways undefined behavior can manifest is that the code appears to work properly.

    If you compile with warnings enabled in gcc (i.e. -Wall -Wextra) you'll get the following output:

    x1.c: In function ‘main’:
    x1.c:5:15: warning: integer constant is too large for its type [enabled by default]
         i = (i << 1000000000000000000000000000000000) % 10;
                   ^
    x1.c:5:5: warning: left shift count >= width of type [enabled by default]
         i = (i << 1000000000000000000000000000000000) % 10;
         ^
    

    This gives you warning both for an integer constant that is too large and a shift which is too large. The shift in particular triggers undefined behavior, as specified in section 6.5.7p3 of the C standard regarding the bitwise shift operators:

    The integer promotions are performed on each of the operands. 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

    So i will first be promoted to type int. The width of an int is likely either 16, 32, or 64 depending on the system, and the given value is much bigger than any of those. Thus you have undefined behavior, which in this particular case appears to do what you expect, but there's no guarantee of that, and in fact how the undefined behavior manifests can change if you make a seemingly unrelated change to the code or compile with different settings.