My C program is very simple, and runs in 64-bit Linux:
#include <stdio.h>
int main(void)
{
unsigned char a = 0xff;
unsigned short b = (a << 6) ;
return 0;
}
I am curious about where the temporary result of (a << 6) is stored, so I check the assembly code:
0x0000000000400474 <+0>: push %rbp
0x0000000000400475 <+1>: mov %rsp,%rbp
0x0000000000400478 <+4>: movb $0xff,-0x3(%rbp)
0x000000000040047c <+8>: movzbl -0x3(%rbp),%eax
0x0000000000400480 <+12>: shl $0x6,%eax
0x0000000000400483 <+15>: mov %ax,-0x2(%rbp)
0x0000000000400487 <+19>: mov $0x0,%eax
0x000000000040048c <+24>: leaveq
0x000000000040048d <+25>: retq
From the assembly code, I can see the eax
is used to store the temporary result of shift operation.
So I want to know is it a C standard to use signed/unsigned 32-bit integer to store the temporary result of shift operation?
I have checked section 6.5.7
Bitwise shift operators of n1570 specification, but nothing can be found.
It is a binary operator that requires two operands to shift or move the position of the bits to the left side and add zeroes to the empty space created at the right side after shifting the bits.
Most C and C++ implementations, and Go, choose which right shift to perform depending on the type of integer being shifted: signed integers are shifted using the arithmetic shift, and unsigned integers are shifted using the logical shift.
A shift operator performs bit manipulation on data by shifting the bits of its first operand right or left.
The bitwise shift operators are the right-shift operator (>>), which moves the bits of shift_expression to the right, and the left-shift operator (<<), which moves the bits of shift_expression to the left.
The standard does not specify those low-level details but you missed one important part in the draft C11 standrd section 6.5.7
paragraph 2 says (emphasis mine):
The integer promotions are performed on each of the operands.[...]
This is covered in section 6.3.1
Arithmetic operands subsection 6.3.1.1
Boolean, characters, and integers paragraph 2 which says:
The following may be used in an expression wherever an int or unsigned int may be used:
and includes the following bullet:
An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.
which includes short and char and goes on to say (emphasis mine):
If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.58) All other types are unchanged by the integer promotions.
So both a
and 6
has to be promoted to int and that will provide a lower bound on which register can be used to store the intermediate results.
Note, as Keith points out in your code sample since the assignment and shift have no observable behavior the compiler would be free to optimize those instructions out.
The C compiler can store the result anywhere it likes: you specified a
as char
(8 bits typically) and b
as short int
(16 bits typically), both of them easily fit within the 32-bit %eax
.
If you define a
and b
as long long int (64 bits), you'll note that the compiler has to use a 64 bit register (e.g. %rax
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With