CS 136 - 2 - Imperative C

2.1 - I/O and output side effects

Functional Programming

A programming paradigm is the programming approach, philosophy or style
In CS135, Racket uses a purely functional programming paradigm.

Imperative Programming

// This program demonstrates a compound statement

#include "cs136.h"

int main(void) { 
  trace_int(1 + 1);  // do this first 
  assert(3 > 2);     // then do this 
  return 0;          // and then do this
}

I/O

Input/Output is the term used to describe how a program interacts with the real world.

Text I/O

To display text output in C, we use the printf function with a "string" paramter.

// This program demonstrates output

#include "cs136.h"

int main(void) {
  printf("Hello, World");
  printf("C is fun!");
  printf("Hello, World\n");
  printf("C is\nfun!\n");
}
// This program demonstrates format specifiers

#include "cs136.h"

int main(void) {
  printf("2 plus 2 is: %d\n", 2 + 2);
  printf("%d plus %d is: %d\n", 2, 10 / 5, 2 + 2);
  printf("I am %d%% sure you should watch your", 100);
  printf("spacing!\n");
  printf("4 digits with zero padding: %04d\n", 42);    // format and alignment
}

Code output:

2 plus 2 is: 4
2 plus 2 is: 4
I am 100% sure you should watch yourspacing!
4 digits with zero padding: 0042

Side effects and state (introduction)

Example

Consider the following “real world” example: You have a blank piece of paper, and then you write your name on that paper. You have changed the state of that paper: at one moment it was blank, and in the next it was “autographed”. In other words, the side effect of writing your name was that you changed the state of the paper.

Documenting Side Effects

// This program demonstrates how to DOCUMENT
//   printf (output) side effects

#include "cs136.h"

// sqr(n) computers n^2
int sqr(int n) {
  return n * n;
}

// noisy_sqr(n) computes n^2
// effects: produces output
int noisy_sqr(int n) {
  printf("Yo! I'm squaring %d!\n", n);
  return n * n;
}

// noisy_abs(n) computes |n|
// effects: may produce output
int noisy_abs(int n) {
  if (n < 0) {
    printf("Yo! I'm changin' the sign!\n");
    return -n;
  } else {
    return n;
  }
}

int main(void) {
  trace_int(noisy_sqr(-3));
  trace_int(noisy_abs(3));
  trace_int(noisy_abs(-3));
  assert(sqr(-3) == 9); 
  assert(sqr(7) == 49); 
  assert(noisy_sqr(-3) == 9); 
  assert(noisy_sqr(7) == 49); 
}

Code Output:

Yo! I'm squaring -3!
Yo! I'm changin' the sign!
Yo! I'm squaring -3!
Yo! I'm squaring 7!
>>> [main.c|main|30] >> noisy_sqr(-3) => 9
>>> [main.c|main|31] >> noisy_abs(3) => 3
>>> [main.c|main|32] >> noisy_abs(-3) => 3

I / O Terminology

Debugging Tools

void functions

Expression Statements

Statement Types

// This program demonstrates unusual expression statements

#include "cs136.h"

// display_score(score, max) displays the player score 
// effects: produces output 
void display_score(int score, int max) { 
  printf("your score is %d out of %d.\n", score, max); 
  return; // optional 
}

// sqr(n) computes n^2
int sqr(int n) {
  return n * n;
}

int main(void) {
  display_score(97, 100);
  11;                                      // throws an edX warning
  10 + 1;                                  // throws an edX warning
  sqr(6) - sqr(5);                         // throws an edX warning
  printf("expression\n");
  printf("five\n") + 6;                    // throws an edX warning
  
  // Tracing unusual expression statements
  trace_int(11);
  trace_int(10 + 1);
  trace_int(sqr(6) - sqr(5));
  trace_int(printf("expression\n"));
  trace_int(printf("five\n") + 6);
}

2.2 - Mutation side effects

More side effects

Two more types:

Variables

// My first program with mutation

#include "cs136.h"

int main(void) {
  int m = 5; // definition (with initialization)
  trace_int(m);
  m = m + 1;     // mutation!
  trace_int(m);
  m = -1;    // more mutation!
  trace_int(m);
}

Code output:

>>> [main.c|main|7] >> m => 5
>>> [main.c|main|9] >> m => 6
>>> [main.c|main|11] >> m => -1

Mutation

Assignment Operator

// Demonstrating using = instead of == for equality

#include "cs136.h"

int main(void) {
  
  int i = 0;
  
  if ((i = 13)) {
    printf("disaster!\n");
  }
  trace_int(i);
}

Code output:

disaster!
>>> [main.c|main|12] >> i => 13

Initialization

// Demonstrating initialization

#include "cs136.h"

int main(void) {
  int my_variable = 7;      // initialized
  int another_variable;     // uninitialized (BAD!)
  
  int n = 5;                // initialization syntax
  n = 6;                    // assignment operator
  
  int x = 0, y = 2, z = 3;  // bad style
  int a, b = 0;             // a is uninitialized (BAD!)
}

More assignment operators

// Demonstrating assignment operators

#include "cs136.h"

int main(void) {
  int x = 0;
  int j = 0;
  x += 4;                                           // x = x + 4;
  printf("After line 8: x = %d\n", x);
  x -= 2;                                           // x = x - 2;
  printf("After line 10: x = %d\n", x);
  x++;                                              // x += 1;
  printf("After line 12: x = %d\n", x);
  x--;                                              // x -= 1;
  printf("After line 14: x = %d\n", x);
  
  x = 5;
  j = x++;                                          // j = 5, x = 6 (BAD STYLE!)
  printf("After line 18: x = %d and j = %d\n", x, j);
  
  x = 5;
  j = ++x;                                          // j = 6, x = 6 (BAD STYLE!)
  printf("After line 22: x = %d and j = %d\n", x, j);
}

Code output:

After line 8: x = 4
After line 10: x = 2
After line 12: x = 3
After line 14: x = 2
After line 18: x = 6 and j = 5
After line 22: x = 6 and j = 6

Constants

Global and local variables

// Demonstrating constants and global/local variables and constants

#include "cs136.h"

const int my_global_constant = 42;
int my_global_variable = 7;

void f(void) {
  const int my_local_constant = 22;
  int my_local_variable = 11;
  trace_int(my_local_constant);
  trace_int(my_local_variable);
  trace_int(my_global_variable);
}

int main(void) {
  trace_int(my_global_constant);
  trace_int(my_global_variable);
  f();
}

2.3 - More Mutation

Variable Scope

Block (local) scope

// Demonstrating scope

#include "cs136.h"

void f(int n) {
                        // b OUT of scope
  if (n > 0) {
                        // b OUT of scope
  int b = 19;
                        // b IN scope
  trace_int(b);
  }
                        // b OUT of scope
  // ...
}

                        // g OUT of scope
int g = 1;
                        // g IN scope
                        
int main(void) {
                        // g IN scope
  trace_int(g);
  f(2);
  // shadowing example:
  trace_int(g);         // g => 1
  int g = 2; 
  trace_int(g);         // g => 2
  {
    int g = 3;    
    trace_int(g);       // g => 3
  }
  trace_int(g);       // g => 2
}

Example:

// Discuss with your neighbour; figure out what the following code should display.
// Once you have written down your best guess, uncomment and run it.    
#include "cs136.h"

/*
guess:
4 8
5 7
*/

/*
void squish(int lo, int hi) {
  if (lo < hi) {
    printf("%d %d\n", lo, hi);
    return squish(++lo, hi--);
  } else {
    return;
  }
}
*/


// this is much better !
void squish(int lo, int hi) {
  if (lo < hi) {
    printf("%d %d\n", lo, hi);
    return squish(lo + 1, hi + 1);
  } else {
    return;
  }
}

int main(void) {
  squish(4, 8);
}

"Impure" functions

int noisy_sqr(int n) {
  printf("Yo! I'm squaring %d!\n", n);
  return n * n;
}

Mutating global variables

// Demonstrating mutating a global variable

#include "cs136.h"

int counter = 0;       // global variable

// increment() returns the number of times it has been called
// effects: modifies counter
int increment(void) {
  counter += 1;
  return counter;
}

int main(void) {
  assert(increment() == 1);
  assert(increment() == 2);
}

Mutating local variables

// Demonstration: mutating a local var is NOT a function side effect

#include "cs136.h"

// add1(n) calculates n + 1
int add1(int n) {
  int k = 0;
  k += 1;       // a local variable is being mutated (no side effect)
  return n + k;
}

int main(void) {
  assert(add1(3) == 4);
}

Mutating Parameters

// Demonstration: mutating a parameter is NOT a function side effect

#include "cs136.h"

// add1(n) calculates n + 1
int add1(int n) {
  n += 1;
  return n;
}

int main(void) {
  assert(add1(3) == 4);
}

Global dependency

Avoid global mutable variables

// Demonstration: Functions with global dependency are "impure"

#include "cs136.h"

int n = 10;

// addn(k) returns n + k
int addn(int k) {
  return k + n;
}

int main(void) {
  assert(addn(5) == 15);
  n = 100;
  assert(addn(5) == 105);
}
Static Local Variables

  • They have the scope of a local variable, but the duration of a global variable and their value persists between function calls.
  • THEY ARE ALMOST ALWAYS POOR STYLE!
  • Not allowed in CS136!!
  • Example:
int increment(void) {

static int counter = 0;
counter += 1;
return counter;
}

More on scope

2.4 - Input side effects

Text input

int x = 0;
int retval = scanf("%d",&x);

scanf return value

EOF in different environments

In our environment, EOF is -1, however, it is good style to use the constant EOF instead of -1

Invalid input

// This program demonstrates how to use scanf

#include "cs136.h"

int main(void) {
  int val = 0;
  int retval = 0;
  retval = scanf("%d", &val);
  
  if (1 == retval) {
    printf("Read an int: %d\n", val);
  } else {
    printf("Failed to read: val is still %d\n", val);
    printf("retval is %d\n", retval);
  }

If on stdin, you input 4 5, code output will be:

Read an int: 4

Reading many integers

// This program demonstrates recursively reading input

#include "cs136.h"

// print_forward reads from input and outputs each value
//  (until a read failure occurs)
// effects: reads input
//          produces output
void print_forward(void) {
  int n = 0;
  int retval = scanf("%d",&n);
  
  if (retval != 1) { 
    return;
  } else {
    printf("%d\n", n);
    print_forward();
  }
}

int main(void) {
  print_forward();
}

Another example - There is a switch in the occurrence of the printing and recursive call. Also, this is more concise:

// This program demonstrates recursively reading input

#include "cs136.h"

// print_reverse reads from input and outputs each value in
//   reverse order (until a read failure occurs)
// effects: reads input
//          produces output
void print_reverse(void) {
  int n = 0;
  
  if (scanf("%d",&n) != 1) {
    return;
  } else {
    print_reverse();
    printf("%d\n", n);
  }
}

int main(void) {
  print_reverse();
}

2.5 - I/O Testing

Input formatting

Invalid input

Testing harness

Out function testing strategies are:

There is another approach for return values. We can write a dedicated test function that reads in argument values from input, passes those values to the function, and prints out the return values. It is useful for both "pure" and "impure" functions.
Example:

// This program demonstrates an I/O testing harness
// (see test_sqr)

#include "cs136.h"

// sqr(n) computes n^2
int sqr(int n) {
  return n * n;
}

// test_sqr() is an I/O testing harness for sqr
//   it continuously reads in argument values (e.g., n)
//   and then prints out sqr(n)
// effects: reads input
//          produces output
void test_sqr(void) {
  int n = 0;
  if (scanf("%d", &n) == 1) {
    printf("%d\n", sqr(n));
    test_sqr(); // recurse
  }
}

int main(void) {
  test_sqr();
}

Enhancing the testing harness

// This program demonstrates an I/O testing harness with symbols

#include "cs136.h"

// abs(n) returns the absolute value of n
//   uses the ternary ? operator just to showcase it
int abs(int number) {
    return (number < 0) ? -number : number;
}

// sqr(n) computes n^2
int sqr(int n) {
  return n * n;
}

void run_test(void) {
  const int CMD_ABS = lookup_symbol("ABS");
  const int CMD_SQR = lookup_symbol("SQR");
  const int CMD_SQUARE = lookup_symbol("SQUARE");
  int cmd = read_symbol();
  
  if (cmd != INVALID_SYMBOL){
    int n = 0;
    if (scanf("%d", &n) == 1){
      if (cmd == CMD_ABS) {
        printf("%d\n", abs(n));
      } else if (cmd == CMD_SQR || cmd == CMD_SQUARE){
        printf("%d\n", sqr(n));        
      }
      run_test(); // run next test
    }
  }
}

int main(void) {
  run_test();
}

test1.in:

ABS -1
ABS 2
SQR 5
SQUARE 9


test1.expect:

1
2
25
81

Symbol Tools Documentation (cs136.h library)

/****************************************************************************
  SYMBOL TOOLS
****************************************************************************/

// symbols follow the same naming convention as identifiers ("names") in C:
//   - they can only contain letters, underscores and numbers
//   - they must start with a letter
//   - they must be <= 63 characters
// at most there can be 255 symbols defined

// when reading or looking up symbols, they are assigned an int ID


// the constant INVALID_SYMBOL is returned by lookup_symbol & read_symbol when:
// a) the next symbol in the input or the parameter is invalid, or
// b) the end of the input (e.g., EOF) is encountered (read_symbol only), or
// c) a new symbol is being defined and 255 symbols have already been defined
extern const int INVALID_SYMBOL;


// read_symbol(void) returns the ID for the next valid symbol from input
//   (which may be a new or existing ID) or INVALID_SYMBOL
// effects: reads from input
int read_symbol(void);


// lookup_symbol(symbol_string) returns the ID for symbol_string
//   (which may be a new or existing ID) or INVALID_SYMBOL
int lookup_symbol(const char *symbol_string);


// print_symbol(symbol_id) displays the symbol corresponding to symbol_id
// requires: symbol_id is a valid ID
// effects: displays a message
void print_symbol(int symbol_id);

Testing Terminology

Assertion testing: using assertions to test our code
I/O-driven testing: uses input and expected output
Testing harness: using input to call functions

White box testing: you can see the code being tested
Black box testing: you can not see the code being tested
Unit testing: testing one piece at a time

Sometimes changes that fix a bug introduce other bugs, breaking code that previously worked. In this case the code is said to have “regressed” (gotten worse). Regression testing reruns all tests to check if everything still works (after a change to any part of the code) to ensure that changes made don’t introduce new bugs.