Issue with logical Right shift

Logical shift operation in C, as it seems is pretty straightforward.  But I in this post, am concerned about one special issue about the right shift.

We all understand the two kinds of shift present in standard C.  I am qouting from Open-std.org in their C standard.

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

Syntax 
shift-expression:
additive-expression 
shift-expression << additive-expression
shift-expression >> additive-expression

Now, understand these operations by means of an example.  Lets write a C file.

#include<stdio.h>
void logicalshift_op(int, int);
void logicalshift_op_u(unsigned int, unsigned int);

int main()
{
logicalshift_op(5,1);
logicalshift_op_u(5,1);

logicalshift_op(0x80000000,1);
logicalshift_op_u(0x80000000,1);

return 0;
}
void logicalshift_op(int x, int n)
{
int y=x>>n;
printf(“Integer- logical shift signed of %d by %d is %d\n”,x,n,y);
printf(“Hexadec- logical shift signed of %x by %x is %x\n”,x,n,y);
}
void logicalshift_op_u(unsigned int x, unsigned int n)
{
unsigned int y=x>>n;
printf(“Integer- logical shift signed of %d by %d is %d\n”,x,n,y);
printf(“Hexadec- logical shift signed of %x by %x is %x\n”,x,n,y);
}

The output is

Integer- logical shift signed of 5 by 1 is 2
Hexadec- logical shift signed of 5 by 1 is 2
Integer- logical shift signed of 5 by 1 is 2
Hexadec- logical shift signed of 5 by 1 is 2
Integer- logical shift signed of -2147483648 by 1 is -1073741824
Hexadec- logical shift signed of 80000000 by 1 is c0000000
Integer- logical shift signed of -2147483648 by 1 is 1073741824
Hexadec- logical shift signed of 80000000 by 1 is 40000000

So, for the first case of right shifting 5, they both did normal.(well they should).

But in the case of negative number they act strange. (This is not what I signed up for!!)  Lets see in binary.
0b 1000 0000   0000 0000  0000 0000  0000 0000
after one shift right gives,
0b 1100 0000   0000 0000  0000 0000  0000 0000
instead of
0b 0100 0000   0000 0000  0000 0000  0000 0000

WHY!!
bad things happen to good people??

Simply because not all the good people pay proper attention to basics and read documentation.

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type
or if E1 has a signed type and a nonnegative value, the value of the result is the integral
part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the
resulting value is implementation-defined. 

Implementation-defined.  Yes!

So, how is this defined in GCC, our only favorite ?
Well, I don’t know, but I tried to inquire.

Just convert our C file to assembly equivalent and see what it is like there.

logicalshift_op:
.pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl 12(%ebp), %eax
movl 8(%ebp), %edx
movl %eax, %ecx
sarl %cl, %edx
movl %edx, %eax
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
movl %eax, 12(%esp)
movl 12(%ebp), %eax
movl %eax, 8(%esp)
movl 8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
movl -12(%ebp), %eax
movl %eax, 12(%esp)
movl 12(%ebp), %eax
movl %eax, 8(%esp)
movl 8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC1, (%esp)
call printf
leave

 

I have underlined & colored the line and stripped the assembly code for simplicity.

First six lines, do as per the calling conventions, matching up the stack and base pointer for this function and then fetching up the integer arguments into callee registers.
Then it does a
“sarl” – shift arithmatic right for 32 bit
whereas we did a logical shift.
It copies up the end bit, i.e the most significant bit and keeps on impending for the number of shifts.  Hence, the last bit i.e. 1 gets replicated for (number_of_shifts) time.

In contrast, the assembly equivalent to shifting on unsigned number is :

 

logicalshift_op_u:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl 12(%ebp), %eax
movl 8(%ebp), %edx
movl %eax, %ecx
shrl %cl, %edx
movl %edx, %eax
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
movl %eax, 12(%esp)
movl 12(%ebp), %eax
movl %eax, 8(%esp)
movl 8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
movl -12(%ebp), %eax
movl %eax, 12(%esp)
movl 12(%ebp), %eax
movl %eax, 8(%esp)
movl 8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC1, (%esp)
call printf
leave
ret

 

Here, it does
“shrl” –  shirft right logically by 32.

Now, how to do this logical shifting in case of negative numbers?

I think of two options,
1. You can deliberately changed the assembly code to logical shift in place of arithmetic shift.

2.  Write a different function that can do the same for you.

I tried both and both works fine as obvious.

Code snippet for logical shift, i prefer calling it RAW right shifting.

int MY_logicalShift(int x, int n) {
int y=x>>n;
int z=(1<<31);
z=~(z>>n);
z=z<<1;
return (y&1)|(z&y);
}