CS 208 s21 — Learning Block #6

Table of Contents

If shifting a base 10 number one place to the left multiplies by 10 and one place to the right divides by 10, what does shifting a binary number do to its value?1

1 Bitwise operations in C

  • in C, these operators can apply to any integral data type
    • 1-byte (char) examples (verify you understand how these work):2
      • ~0x41
      • ~0x00
      • 0x68 & 0x55
      • 0x68 | 0x55
      • 0x34<<1
      • 0x68>>2
      • !0x00
      • !!0x41
      • ~(0x00 || !0x20)
      • how many bits are in each of these results?
        • all of them contain 8 bits (1 byte)! zeroes used to "fill out" the fixed length

2 Packing and Extracting

These operations are also essential in settings with very little memory. Since storage sizes are fixed, like a 4-byte integer, representing 1-bit of information like true/false (1 or 0) will leave a lot of space unused. To be more efficient, we might want to combine a bunch of values into the bits of an int, with each value using a different range of the 32 bits. We would need ways of combining and extracting these values, however, and that's where bitwise operators come in.

Returning to the idea of combining multiple values into a single quantity, let's say we want to store three 1-byte characters in a 4-byte integer. The code below shows how these quantities would be combined and extracted. All the types are unsigned to prevent C from performing sign extension when converting a char to an int.

#include <stdio.h>

int combine(unsigned char a, unsigned char b, unsigned char c) {
    unsigned result = a; // result has a in the low order byte, 0 in the rest
    printf("result = %x\n", result);
    result = (result<<8) | b; // shift a 1 byte to the left, use OR to put b in the low order byte
    printf("result = %x\n", result);
    result = (result<<8) | c; // shift a and b 1 byte to the left, use OR to put c in the low order byte    
    printf("result = %x\n", result);
    return result;
}

int main() {
    unsigned char a = 0xaa;
    unsigned char b = 0xbb;
    unsigned char c = 0xcc;
    unsigned x = combine(a, b, c);

    // use the shift-and-mask strategy to extract each char
    // masking means use AND to zero-out bits you don't care about, leaving only the ones you do
    printf("a = %x\n", (x>>16) & 0xff); // shift x to the right to put a in low byte, mask out the rest with AND
    printf("b = %x\n", (x>>8) & 0xff); // shift x to the right to put b in low byte, mask out the rest with AND
    printf("c = %x\n", x & 0xff); // c is already in the low byte, mask out the rest with AND
}

QUICK CHECK: write a C expression to extract most significant byte from a 32-bit integer3. For example, given x = 0b 01100001 01100010 01100011 01100100 (spaces added to separate bytes), should return 0b 00000000 00000000 00000000 01100001

3 Pracice

CSPP practice problems 2.12 (p. 55), 2.14 (p. 57), 2.15 (p. 57), and 2.16 (p. 58)

Footnotes:

1

Shifting 1 to the left multiplies by 2, shifting 1 to the right divides by 2

2
  • ~0x41 \(\rightarrow\) ~0b01000001 \(\rightarrow\) 0b10111110 \(\rightarrow\) 0xbe
  • ~0x00 \(\rightarrow\) 0xff
  • 0x68 & 0x55 \(\rightarrow\) 0x40
  • 0x68 | 0x55 \(\rightarrow\) 0x7d
  • 0x34<<1 \(\rightarrow\) 0x68
  • 0x68>>2 \(\rightarrow\) 0x1a
  • !0x00 \(\rightarrow\) 0x01
  • !!0x41 \(\rightarrow\) 0x01
  • ~(0x00 || !0x20) \(\rightarrow\) ~(0x00 || 0x00) \(\rightarrow\) ~0x00 \(\rightarrow\) 0xff
  • how many bits are in each of these results?
    • all of them contain 8 bits (1 byte)! zeroes used to "fill out" the fixed length
3

Use shift-and-mask: (x>>24) & 0xff