A problem about type cast in bitwise shift

K

kernelxu

Hi,folks.
I got some suggestion about bitwise shift from <The C Book, second
edition>(written by Mike Banahan, Declan Brady and Mark Doran,
originally published by Addison Wesley in 1991. This version is made
freely available at http://publications.gbdirect.co.uk/c_book/)
....snip...
The position is clearer if an unsigned operand is right shifted,
because there is no choice: it must be a logical shift. For that
reason, whenever right shift is being used, you would expect to find
that the thing being shifted had been declared to be unsigned, or cast
to unsigned for the shift, as in the example:
int i,j;
i = (unsigned)j >> 4;
....snip...
However, when I run the following program with some little changes,
the result confused me.
statement 1:
the printed results of variables m and n are different.
replacing statement 1 with statement 2:
the same as above.
replacing statement 1 with statement 3:
the printed results of variables m and n are the same, which is what I
expected.
So, I'am sure the statements 1 and 2 are incorrect.
The variables i and n both have the type of unsigned char,
is the type of "n<<i"unsigned char?If so, is the type cast necessary?
or, if not, is that the reason of integer promotion?
What about statement 2 comparing to statemnt 1?
IMHO, they are the same.
Any comments are appreciated in advance.
/**************bitwise_shift.c**************/
#include <stdio.h>

int main(void)
{
unsigned char c = 0x79;
unsigned char m;
unsigned char n;
unsigned char i = 2;

printf("sizeof(unsigned char) = %u\n", sizeof(unsigned char));
m = c;
m <<= i;
m >>= 7U;
printf("m = %c\n", m);
n = c;
n = (n << i) >> 7U; /*statement 1*/
/*n = (n << 2U) >> 7U;*/ /*statement 2*/
/* n = (unsigned char)(n << i) >> 7;*/ /*statement 3*/
printf("n = %c\n", n);

return 0;
}
/***************end of bitwise_shift***************/
 
S

suresh

kernelxu wrote:
....snip...
However, when I run the following program with some little changes,
the result confused me.
statement 1:
the printed results of variables m and n are different.
replacing statement 1 with statement 2:
the same as above.
replacing statement 1 with statement 3:
the printed results of variables m and n are the same, which is what I
expected.
So, I'am sure the statements 1 and 2 are incorrect.
The variables i and n both have the type of unsigned char,
is the type of "n<<i"unsigned char?If so, is the type cast necessary?
or, if not, is that the reason of integer promotion?
What about statement 2 comparing to statemnt 1?
IMHO, they are the same.
Any comments are appreciated in advance.
/**************bitwise_shift.c**************/
#include <stdio.h>

int main(void)
{
unsigned char c = 0x79;
unsigned char m;
unsigned char n;
unsigned char i = 2;

printf("sizeof(unsigned char) = %u\n", sizeof(unsigned char));
m = c;

/* Statement 0 */
m <<= i;
m >>= 7U;
printf("m = %c\n", m);
n = c;
n = (n << i) >> 7U; /*statement 1*/
/*n = (n << 2U) >> 7U;*/ /*statement 2*/
/* n = (unsigned char)(n << i) >> 7;*/ /*statement 3*/
printf("n = %c\n", n);

return 0;
}
/***************end of bitwise_shift***************/

As you have guessed correctly, the reason for the different values is
integral promotion.

In statement 0 the type of the operands are promoted to int (which can
at least store 15 bit unsigned number).
The result of shifting 0x79 by 2 is 0x1E4, but when you store the value
into m, only the modulo 256 of the value is stored back (ie. 0xE4) due
to implicit typecast to unsigned char. The value 0xE4 is then shifted
right by 7 which gives 1 as result.

Whereas in statement 1, the value 0x1E4 is shifted right by 7 without
truncation which gives 3 as a result.

And there is no difference between statement 1 and 2.

In statement 3, the value 0x1E4 is typecasted to unsigned char which
results in 0xE4 and then shifted right by 7 which is same as the
statement 0.
 
K

kernelxu

suresh said:
/* Statement 0 */


As you have guessed correctly, the reason for the different values is
integral promotion.

In statement 0 the type of the operands are promoted to int (which can
at least store 15 bit unsigned number).
The result of shifting 0x79 by 2 is 0x1E4, but when you store the value
into m, only the modulo 256 of the value is stored back (ie. 0xE4) due
to implicit typecast to unsigned char. The value 0xE4 is then shifted
right by 7 which gives 1 as result.

Whereas in statement 1, the value 0x1E4 is shifted right by 7 without
truncation which gives 3 as a result.

And there is no difference between statement 1 and 2.

In statement 3, the value 0x1E4 is typecasted to unsigned char which
results in 0xE4 and then shifted right by 7 which is same as the
statement 0.
Hi, suresh
Thank you very much. Your explain makes me more clear.
 
P

Peter Nilsson

kernelxu said:
...when I run the following program with some little changes,
the result confused me.
...
Any comments are appreciated in advance.
/**************bitwise_shift.c**************/
#include <stdio.h>

int main(void)
{
unsigned char c = 0x79;
unsigned char m;
unsigned char n;
unsigned char i = 2;

printf("sizeof(unsigned char) = %u\n", sizeof(unsigned char));

The sizeof operator produces a value of type size_t. Whilst size_t must
be an unsigned integer type, there is no requirement in C that it be
unsigned int. Under C99, you can use %zu to print a size_t value.
Under C90, you're better off casting the sizeof result to unsigned int
or unsigned long (and using %lu.)

That said, you do realise that sizeof(unsigned char) is _always_ 1
on any conforming implementation? You're better off printing
CHAR_BIT from said:
m = c;
m <<= i;
m >>= 7U;

Your U suffix is completely redundant.
printf("m = %c\n", m);

You have no guarantee that the value you're printing is actually
a printable character. Use %x or %X instead, or write a small
routine to dump the binary contents...

#include <limits.h>

void dump_uc(unsigned char x)
{
unsigned char m;
for (m = -1, m = m/2+1; m; m >>= 1)
putchar('0' + !!(x & m));
}

void dump_u(unsigned x)
{
unsigned m;
for (m = -1, m = m/2+1; m; m >>= 1)
putchar('0' + !!(x & m));
}
n = c;
n = (n << i) >> 7U; /*statement 1*/

The result of (n << i) will be promoted before shifting occurs.
Again, the U suffix is redundant.
/*n = (n << 2U) >> 7U;*/ /*statement 2*/
/* n = (unsigned char)(n << i) >> 7;*/ /*statement 3*/

Each assignment will result in a conversion of the expression value
to the (unqualified) type of the object being assigned. The expression
0x79 << 2 is always going to produce 0x1E4. When that is assigned
to an 8-bit unsigned char (as your implementation presumably is),
it's going to be converted to 0xE4.

Without the conversion, (0x79 << 2) >> 7 is going to produce 0x03.

The effect of the prior assignment means the result is the same
as...

((unsigned char) 0x79 << 2) >> 7

<snip>
 
K

kernelxu

Peter said:
The sizeof operator produces a value of type size_t. Whilst size_t must
be an unsigned integer type, there is no requirement in C that it be
unsigned int. Under C99, you can use %zu to print a size_t value.
Under C90, you're better off casting the sizeof result to unsigned int
or unsigned long (and using %lu.)

That said, you do realise that sizeof(unsigned char) is _always_ 1
on any conforming implementation? You're better off printing


Your U suffix is completely redundant.


You have no guarantee that the value you're printing is actually
a printable character. Use %x or %X instead, or write a small
routine to dump the binary contents...

#include <limits.h>

void dump_uc(unsigned char x)
{
unsigned char m;
for (m = -1, m = m/2+1; m; m >>= 1)
putchar('0' + !!(x & m));
}

void dump_u(unsigned x)
{
unsigned m;
for (m = -1, m = m/2+1; m; m >>= 1)
putchar('0' + !!(x & m));
}


The result of (n << i) will be promoted before shifting occurs.
Again, the U suffix is redundant.


Each assignment will result in a conversion of the expression value
to the (unqualified) type of the object being assigned. The expression
0x79 << 2 is always going to produce 0x1E4. When that is assigned
to an 8-bit unsigned char (as your implementation presumably is),
it's going to be converted to 0xE4.
That's the key point. statement 3 (/* n = (unsigned char)(n << i) >>
7;*/ )
does the same job as statements" m <<= i; m >>= 7U;" do, which is to
truncate 0x1E4 to 0xE4.

Peter, thanks.

....snip...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,999
Messages
2,570,243
Members
46,835
Latest member
lila30

Latest Threads

Top