Signed and unsigned variables can represent positive and negative numbers, or just positive numbers respectively. Declaring a variable as unsigned increases its maximum positive range. Storage classes like auto, static, extern and register provide information about a variable's location and visibility. Bitwise operators operate on binary representations of numbers and are commonly used for tasks like compression/encryption that require bit-level manipulation.
1. Signed and unsigned variables
The difference between signed and unsigned variables is that signed
variables can be either negative or positive but unsigned variables can only
be positive.
By using an unsigned variable you can increase the maximum positive range.
When you declare a variable in the normal way it is automatically a signed
variable. To declare an unsigned variable you just put the word unsigned
before your variable declaration or signed for a signed variable although there
is no reason to declare a variable as signed since they already are.
Signed Range Unsigned Range
char -128 to 127 unsigned char 0 to 255
int -32768 to 32,767 unsigned int 0 to 65,535
2. Declaration of Storage Class
Variables in C have not only the data type but also storage class that provides
information about their location and visibility. The storage class divides the
portion of the program within which the variables are recognized.
auto : It is a local variable known only to the function in which it is declared. Auto
is the default storage class.
static : Local variable which exists and retains its value even after the control is
transferred to the calling function.
extern : Global variable known to all functions in the file
register : Social variables which are stored in the register.
3. auto - storage class
auto is the default storage class for local variables.
{
int Count;
auto int Month;
}
The example above defines two variables with the same storage class. auto
can only be used within functions, i.e. local variables.
4. register - Storage Class
register is used to define local variables that should be stored in a
register instead of RAM. This means that the variable has a maximum
size equal to the register size (usually one word) and can’t have the
unary '&' operator applied to it (as it does not have a memory location).
{
register int Miles;
}
Register should only be used for variables that require quick access -
such as counters. It should also be noted that defining 'register' goes
not mean that the variable will be stored in a register. It means that it
MIGHT be stored in a register - depending on hardware and
implimentation restrictions.
5. static - Storage Class
static is the default storage class for global variables. The two
variables below (count and road) both have a static storage class.
static int Count;
int Road;
main()
{
printf("%dn", Count);
printf("%dn", Road);
}
6. extern - storage Class
extern defines a global variable that is visible to ALL object modules.
Source 1 Source 2
------------ ------------
extern int count; int count=5;
write() main()
{ {
printf("count is %dn", count); write();
} }
Count in 'source 1' will have a value of 5. If source 1 changes the value of
count - source 2 will see the new value.
7. Register Variables
Register variables are a special case of automatic variables.
Automatic variables are allocated storage in the memory of the computer.
These computers often have small amounts of storage within the CPU itself
where data can be stored and accessed quickly. These storage cells are called
registers.
Normally, the compiler determines what data is to be stored in the registers of the
CPU at what times.
However, the C language provides the storage class register so that the
programmer can ``suggest'' to the compiler that particular automatic variables
should be allocated to CPU registers, if possible.
Thus, register variables provide a certain control over efficiency of program
execution. Variables which are used repeatedly or whose access times are
critical, may be declared to be of storage class register.
8. Register variables behave in every other way just like automatic variables.
They are allocated storage upon entry to a block; and the storage is freed
when the block is exited.
The scope of register variables is local to the block in which they are
declared. Rules for initializations for register variables are the same as for
automatic variables.
As stated above, the register class designation is merely a suggestion to
the compiler.
Not all implementations will allocate storage in registers for these
variables, depending on the number of registers available for the particular
computer, or the use of these registers by the compiler.
They may be treated just like automatic variables and provided storage in
memory.
9. Finally, even the availability of register storage does not guarantee
faster execution of the program.
For example, if too many register variables are declared, or there are
not enough registers available to store all of them, values in some
registers would have to be moved to temporary storage in memory in
order to clear those registers for other variables. Thus, much time may
be wasted in moving data back and forth between registers and
memory locations.
10. When should you use bitwise operators?
Bitwise operators are good for saving space –
In computer programming, a bitwise operation operates on one or two
bit patterns or binary numerals at the level of their individual bits. On most
microprocessors.
bitwise operations are usually slightly faster than addition and subtraction
operations and usually significantly faster than multiplication and division
operations.
There are also times when you need to use bitwise operators: if you're
working with compression or some forms of encryption, or if you're
working on a system that expects bit fields to be used to store boolean
attributes.
11. Bitwise operations
& AND
| OR
^ XOR
~ one's compliment
<< Shift Left
>> Shift Right
DO NOT confuse & with &&: & is bitwise AND, && logical AND. Similarly for |
and || .
~ is a unary operator -- it only operates on one argument to right of the
operator.
The shift operators perform appropriate shift by operator on the right to the
operator on the left. The right operator must be positive. The vacated bits are
filled with zero (i.e. There is NO wrap around).
12. 0000 0 1000 8
0001 1 1001 9
0010 2 1010 a
0011 3 1011 b
0100 4 1100 c
0101 5 1101 d
0110 6 1110 e
0111 7 1111 f
13. AND
The bitwise operators operate on numbers (always integers) as if they
were sequences of binary bits (which, of course, internally to the
computer they are). These operators will make the most sense,
therefore, if we consider integers as represented in binary, octal, or
hexadecimal (bases 2, 8, or 16), not decimal (base 10). Remember, you
can use octal constants in C by prefixing them with an extra 0 (zero),
and you can use hexadecimal constants by prefixing them with 0x (or
0X).
The & operator performs a bitwise AND on two integers. Each bit in the
result is 1 only if both corresponding bits in the two input operands are
1. For example, 0x56 & 0x32 is 18, because (in binary):
0 1 0 1 0 1 1 0
& 0 0 1 1 0 0 1 0
-------------------
0 0 0 1 0 0 1 0
14. OR
The | (vertical bar) operator performs a bitwise OR on two
integers.
Each bit in the result is 1 if either of the corresponding bits
in the two input operands is 1.
For example, 0x56 | 0x32 is 118, because:
0 1 0 1 0 1 1 0
| 0 0 1 1 0 0 1 0
-------------------
0 1 1 1 0 1 1 0
15. XOR
The ^ (caret) operator performs a bitwise exclusive-OR on
two integers.
Each bit in the result is 1 if one, but not both, of the
corresponding bits in the two input operands is 1.
For example, 0x56 ^ 0x32 is 100:
0 1 0 1 0 1 1 0
^ 0 0 1 1 0 0 1 0
-------------------
0 1 1 0 0 1 0 0
16. one's compliment
The ~ (tilde) operator performs a bitwise complement on
its single integer operand.
Complementing a number means to change all the 0
bits to 1 and all the 1s to 0s.
For example, assuming 16-bit integers, ~0x56 is 0xffa9:
~ 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0
----------------------------------------
1 1 1 1 1 1 1 1 1 0 1 0 1 0 0 1
17. Bit shifts
The bit shifts are sometimes considered bitwise
operations, since they operate on the binary
representation of an integer instead of its numerical value;
however, the bit shifts do not operate on pairs of
corresponding bits, and therefore cannot properly be
called bit-wise operations.
18.
19. In an arithmetic shift, the bits that are shifted out of either
end are discarded.
In a left arithmetic shift, zeros are shifted in on the right; in
a right arithmetic shift, the sign bit is shifted in on the left,
00010111 LEFT-SHIFT
= 00101110
00010111 RIGHT-SHIFT
= 00001011
20. In the first case, the leftmost digit was shifted past the
end of the register, and a new 0 was shifted into the
rightmost position.
In the second case, the rightmost 1 was shifted out and a
new 0 was copied into the leftmost position, preserving
the sign of the number.
Multiple shifts are sometimes shortened to a single shift
by some number of digits. For example:
00010111 LEFT-SHIFT-BY-TWO
= 01011100
21. The << operator shifts its first operand left by a number of bits given
by its second operand, filling in new 0 bits at the right.
Similarly, the >> operator shifts its first operand right. If the first
operand is unsigned, >> fills in 0 bits from the left. (it's usually a good
idea to use all unsigned operands when working with the bitwise
operators.)
For example, 0x56 << 2 is 0x158:
0 1 0 1 0 1 1 0 << 2
------------------------
0 1 0 1 0 1 1 0 0 0
And 0x56 >> 1 is 0x2b:
0 1 0 1 0 1 1 0 >> 1
-------------------
0 1 0 1 0 1 1
22. if x = 00000010 (binary) or 2 (decimal)
then:
or 0 (decimal)
Also: if x = 00000010 (binary) or 2 (decimal)
or 8 (decimal)
Therefore a shift left is equivalent to a multiplication by 2.
Similarly a shift right is equal to division by 2
NOTE: Shifting is much faster than actual multiplication (*) or division (/) by 2.
So if you want fast multiplications or division by 2 use shifts.
23. To illustrate many points of bitwise operators let us write a function, Bitcount,
that counts bits set to 1 in an 8 bit number (unsigned char) passed as an
argument to the function.
int bitcount(unsigned char x)
{
int count;
for (count=0; x != 0; x>>=1);
if ( x & 01)
count++;
return count;
}
This function illustrates many C program points:
•x x = x >> 1
•for loop will repeatedly shift right x until x becomes 0
•use expression evaluation of x & 01 to control if
•x & 01 masks of 1st bit of x if this is 1 then count++
24. Bit Fields
Bit Fields allow the packing of data in a structure. This is especially useful
when memory or data storage is at a premium. Typical examples:
•Packing several objects into a machine word. e.g. 1 bit flags can be
compacted
•Reading external file formats -- non-standard file formats could be read in. E.g.
9 bit integers.
C lets us do this in a structure definition by putting :bit length after the variable.
i.e.
struct packed_struct {
unsigned int f1:1;
unsigned int f2:1;
unsigned int f3:1;
unsigned int f4:1;
unsigned int type:4;
unsigned int funny_int:9;
} pack;
25. Here the packed_struct contains 6 members: Four 1 bit flags f1..f4, a 4 bit
type and a 9 bit funny_int.
C automatically packs the above bit fields as compactly as possible, provided
that the maximum length of the field is less than or equal to the integer word
length of the computer. If this is not the case then some compilers may allow
memory overlap for the fields while other would store the next field in the next
word (see comments on bit fiels portability below).
Access members as usual via:
pack.type = 7;
NOTE:
•Only n lower bits will be assigned to an n bit number. So type cannot take
values larger than 15 (4 bits long).
•Bit fields are always converted to integer type for computation.
•You are allowed to mix ``normal'' types with bit fields.
•The unsigned definition is important - ensures that no bits are used as a flag.
26. A note of caution: Portability
Bit fields are a convenient way to express many difficult operations. However,
bit fields do suffer from a lack of portability between platforms:
integers may be signed or unsigned.
Many compilers limit the maximum number of bits in the bit field to the size
of an integer which may be either 16-bit or 32-bit varieties.
Some bit field members are stored left to right others are stored right to left
in memory.
If bit fields too large, next bit field may be stored consecutively in memory
(overlapping the boundary between memory locations) or in the next word of
memory.
27. Bit Fields: Practical Example
Frequently device controllers (e.g. disk drives) and the operating system
need to communicate at a low level. Device controllers contain several
registers which may be packed together in one integer.
struct DISK_REGISTER {
unsigned ready:1;
unsigned error_occured:1;
unsigned disk_spinning:1;
unsigned write_protect:1;
unsigned head_loaded:1;
unsigned error_code:8;
unsigned track:9;
unsigned sector:5;
unsigned command:5;
};