spiralmoon
spiralmoon

Reputation: 3272

Bitwise Leftshift (<<) strange behavior

gcc bitwise Leftshift (<<) strange behavior. Here is my code:

#include <stdio.h>
#include <string.h>

void foo(int n){
  printf("1<<32:%d\n", 1<<32);
  printf("1<<(32-n):%d\n", 1<<(32-n));
}

int main(){
    foo(0);
}

If I pass 0 as parameter, the result could be different. Compiling the source code:

$gcc main.c -o demo -lm -pthread -lgmp -lreadline 2>&1
main.c: In function 'foo':
main.c:5:3: warning: left shift count >= width of type [enabled by default]

Executing the program:

$demo

1<<32:0
1<<(32-n):1

This result is what I've got from compile online site

How can I make the foo function output 0 if I pass 0 to it? (currently it outputs 1 instead)

Upvotes: 2

Views: 2027

Answers (6)

Yu Hao
Yu Hao

Reputation: 122383

According to the C standard ISO 9899:1999 Chapter 6.5.7 Bitwise shift operators:

The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. 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.

It is strange that the compiler treats the two expressions differently. But since this causes undefined behavior anyway, it's not a problem. What you need to do is to check the operands before evaluation to make sure it's a valid expression.

Upvotes: 0

Shafik Yaghmour
Shafik Yaghmour

Reputation: 158469

gcc is telling you what the problem is with the warning:

main.c:5:3: warning: left shift count >= width of type [enabled by default]

Your shifts need to be less than the size of the type otherwise it is undefined behavior. The C99 draft standard section 6.5.7 Bitwise shift operators paragraph 3 says:

[...]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.

Why is the first printf different from the second one? If you build using -fdump-tree-original you will see the following code generated for foo:

printf ((const char * restrict) "1<<32:%d\n", 0);
printf ((const char * restrict) "1<<(32-n):%d\n", 1 << 32 - n);

It seems the first case it being optimized away to 0 which is consistent with undefined behavior, the compiler can do anything, including behavior that appears to work.

Upvotes: 0

spiralmoon
spiralmoon

Reputation: 3272

I finally figure out a work-around solution, at least make the output identical.

#include <stdio.h>
#include <string.h>

void foo(int n){
  printf("1<<32:%d\n", 1<<32);
  printf("1<<(32-n):%d\n", (1<<(31-n))<<1);
}

int main(){
    foo(0);
}

Upvotes: -1

Stian Ellingsen
Stian Ellingsen

Reputation: 208

Since you're shifting 32-bit ints, shifting by 32 bits would result in a zero value. However, the bitshift operation of the CPU can only shift by 0 to 31 bits as anything else is generally not useful and would only complicate the computation.

The reason that the first example, 1<<32, seems to work, is that the compiler optimises this to 0 at compile time, while also printing a warning. The other example, 1<<(32-n), however, has a shift value that cannot be determined at compile time (thus no warning either). Instead, the CPU uses the result of the subtraction 32 - n == 32 for its shift operation, but the CPU only takes the five lowest bits and thus overflows to 0, and the result is 1 << 0 == 1.

To work around this, you will have to either special-case n == 0, use a wider data type, or simply use fewer bits.

Upvotes: 1

caf
caf

Reputation: 239041

Shifting by a value that is equal or greater than the width of the promoted type of the left operand is undefined behaviour, so you must specifically test for and avoid this. In addition, a left-shift of a signed type that results in overflow is also undefined behaviour, so you need to also avoid a shift of 31, too:

printf("1<<(32-n):%d\n", (n > 1 && n < 33) ? 1 << (32-n) : 0);

This particular expression uses 0 for the cases that are otherwise undefined, but you can handle those differently if you need to.

Upvotes: 2

Marc B
Marc B

Reputation: 360672

Don't be surprised. you're dealing with a 32bit int, so when you do 1<<32, you've shifted that set bit right off the end of the int and zeroed out the whole thing.

e.g. in binary:

    33222222 22211111 11111000 00000000
    10987654 32109876 54321098 76543210
 1: 00000000 00000000 00000000 00000001

   ^--- position #32

Upvotes: 0

Related Questions