Week 5

Week 5 #

Programming in C #

Memory Model #

  • Array of bytes, “Addresses” are indexes
  • Variables may occupy several consecutive bytes, its address refers to the first occupied byte
  • “Pointer”: Variable/parameter that stores an address
int i = 69; // Suppose i was occupying bytes 45 to 74
int *p = &i // 45

Memory Regions #

  • Text:
    • Stores code
    • Pointers pointing to functions point here
  • Global:
    • Stores global variables
  • Stack:
    • Used for function calls
    • Stores local variables
    • Auto allocation and deallocation
  • Heap:
    • Manual allocation (malloc(), calloc()) and deallocation (free())
    • Used for dynamic data outside of functions

Global Variables #

  • Two types of varaibles
    int pubVar = 10;              // top-level public global variable
    int f() {
      static int privVar = 1010;  // function private global variable
      pubVar++;
      privVar++;
      ...
    }
    

Integer Types #

  • All possible combinations: {signed, unsigned $\times$ char, short, int, long, long long}
  • Byte size depends on platform
    • eg. x86-64
      TypeSize (bytes)
      char (default signed)1
      short2
      int4
      long8
      long long8

Integer Literal Notation #

LiteralType
3int
cchar
3Uunsigned int
3Llong
3ULunsigned long
3LLlong long
3ULLunsigned long long
printf("%lu\n", 3UL) // Good usage
printf("%lu\n", 3)   // Bad since 3 is `int`

Type Casting with Numbers #

  • Larger size type to smaller:
    • Automatic conversion
    • Lose some information in a natural way (double to int removes decimal place)
    • Better to explicitly typecast:
      double d = 420.69;
      int i = (int)d;
      
  • Smaller size type to larger:
    • Automatic conversion
    • Completely lossless

Implicit Number Promotion #

  • The smaller operand type gets promoted to the larger operand type
    • Note: char and short are always promoted to int
  • eg.
    // Suppose the following
    double d = 65.0;
    int i = 65;
    char c = 'A';
    /**
    i / c -> promotes c to `int` and preforms integer division
    d / j -> promotes j to `double` and preforms floating-point division
    **/
    

Enumeration Types #

  • New “types” and integer constant names
    enum rps {
      ROCK,     // ROCK      = 0 
      PAPER,    // PAPER     = 1
      SCISSORS  // SCISSORS  = 2
    };
    enum coin {
      HEAD,     // HEAD      = 0
      TAIL      // TAIL      = 1
    };
    
    enum rps a = PAPER; // a = 1
    enum coin c = HEAD; // c = 0
    
  • “Types” are simply int, are mixable and not checked
  • Practically useful for meaningful names only

Union Types #

  • Overlapping “fields” that share the same space
    union myUnion { // sizeof(myUnion) = "largest field size" = 4 (for this example)
      unsigned short s;
      unsigned int i;
      unsigned char b[4];
    };
    union myUnion u; // can use u.s, u.i, u.b[j] etc.
    
  • Use Cases:
    • High-level: Data has multiple mutually exclusive cases
    • Low-level:
      1. Store an int in i
      2. Read b[0] to b[3] to discover how i splits

Tagged Union Idiom #

  • Example: Suppose you wanted an array that holds both int and double
  • Idiom: Make an outer struct
    struct int_or_double {
      enum { INT, DOUBLE } tag; // remembers which case you are in
      union {                   // shares the same space in memory regardless of int or double
        int i;                  // case value is an int
        double d;               // case value is a double
      } data;
    };
    struct int_or_double a[10]; // as wanted
    

Type Alias with typedef #

  • Very general, i.e can use with struct, enum, int, double etc.
    typedef struct node {
      int i;
      struct node *next;
    } nodetype; // use `nodetype` instead of `struct node`
    
  • Cannot use the same typedef name for more than one thing
    typedef coin { HEAD, TAIL } coin;
    typedef int coin; // illegal since `typedef coin` is already defined