FAQ | Newsboard | Telnet | Email TA | Lectures | Assignments | Man pages | Help |
---|
Assertions, Invariants, Pre/Post Conditions
You can think of these terms as ways of documenting your program or as formal methods for verifying program correctness. Assertions are formal (or semi-formal) facts or statements about the state of your program. The state of your program or your computation is like a snapshot of the contents of all variables and other information at some instant of execution.
More precisely an assertion claims something about the state of your computation at the location of the assertion. For example:
int trialCount[50]; for (int i = 0; i < 50 ; i++) trialCount[i] = 0; //ASSERT: All trialCount[0..49] == 0The assertion claims that trialCount[i] for all i between 0 and 49 is 0 at the point the assertion is made -- right after the loop. Placing the assertion before the loop or inside the loop body make the assertion false. You want to make and place assertions where they are TRUE
Invariants, preconditions, and postconditions are kinds of assertions.
Loop invariants are one kind of invariants. A loop invariant makes an assertion within a loop that is true every time control reaches that position in the loop. By convention, a loop invariant is located just before the test for loop exit in a loop.
int trialCount[50]; int i = 0; while (i < 50)// INV: All trialCount[0..i-1] == 0 { && 0 <= i <= 50 trialCount[i] = 0; i++; } //ASSERT: All trialCount[0..49] == 0 && i = 50The loop invariant (denoted by an INV) documents what the loop does and must be true every time control reaches that point in the loop. That is, the loop invariant must be true
Important in documenting a function. the Precondition describes everything the function requires to be true at the moment the caller invokes the function. The Postcondition describes the state of computation at the moment the function finishes executing.
void SwapChars(char& ch1, char &ch2) { char tmp; tmp = ch1; ch1 = ch2; ch2 = tmp; }What must be true when the function SwapChars is invoked? ch1 and ch2 need to have been assigned values by the caller. We can write this as:
PRE: Assigned(ch1) && Assigned(ch2)I can't think of anything else that is nontrivial and must be true when SwapChars is invoked.
Now, what must be true when the function exits? The values of ch1 and ch2 must have been interchanged right? That is:
POST: The values of ch1 and ch2 from the time of invocation have been interchanged.we can write this in much more formal manner using notation from logic, but for this class, let's stick to the semi-formal notation provided in the book and rewrite it as
POST: ch1 == ch2<entry> && ch2 == ch1<entry>to denote the values of ch1 and ch2 with respect to their values upon entry <entry> to the function.
We can now re-write the function with documentation
void SwapChars(char& ch1, char &ch2) { // PRE: Assigned(ch1) && Assigned(ch2) // POST: ch1 == ch2<entry> && ch2 == ch1<entry> char tmp; tmp = ch1; ch1 = ch2; ch2 = tmp; }with practice, anyone looking at this documentation will easily and UNAMBIGUOUSLY be able to use the function.
Read pages 35 - 37 in the textbook.
Here's the file:
typedef int boolean; #ifndef TRUE #define TRUE (1 == 1) #endif #ifndef FALSE #define FALSE (0 == 1) #endif boolean Empty(void); boolean Full(void); void InitStack(void); void Push(char ch); char Pop(void); void StackPrint(void);Let's ignore the way we have defined boolean and the definitions of TRUE and FALSE for now and concentrate on documenting the functions exported by the stack module.
Consider the function InitStack:
What is the precondition? That is, what does this function assume to
be true before invocation? I can't think of anything! This function
assumes little about how it is invoked. How about the postcondition?
Aha, the stack is initialized and ready to use by the invoker when
InitStack exits. So the postcondition is non-empty and looks
like:
POST: STACK is initialized and can be used
Let's look at the function boolean Empty(void):
The precondition is that the stack has been initialized previously with a call
to InitStack.
PRE: Initstack as been invokedThe postcondition is more interesting. Empty returns true if the stack is empty and false otherwise. The notation used to describe values returned by functions is to use FCTVAL to denote the value returned by a function. So the postcondition can be stated as
POST: FCTVAL == TRUE, if STACK is empty == FALSE, otherwiseIn the same way we can design pre- and postconditions for the rest of the stack module's exported functions.
PRE: Initstack as been invoked
POST: FCTVAL == TRUE, if STACK is full == FALSE, otherwise
PRE: NOT(Full()) that is, the stack is not full. && Assigned(item) && InitStack has been invoked
POST: item is at top of stack
PRE: NOT(Empty()) that is, the stack is not empty && InitStack has been invoked
POST: FCTVAL = item at top of stack
PRE: InitStack has been invoked
POST: Prints contents of stack on screen
Putting it all together let's make the specification file more informative to the user by documenting the pre and postconditions that we have derived. Here's the right way to define stack.h
void InitStack(void); // PRE: // POST: STACK is initialized and can be used boolean Empty(void); // PRE: InitStack has been invoked // POST: FCTVAL == TRUE, if STACK is empty == FALSE, otherwise boolean Full(void); // PRE: InitStack has been invoked // POST: FCTVAL == TRUE, if STACK is full == FALSE, otherwise void Push(char ch); // PRE: NOT(Full()) that is, the stack is not full. // && Assigned(item) // && InitStack has been invoked // POST: item is at top of stack char Pop(void); // PRE: NOT(Empty()) that is, the stack is not empty // && InitStack has been invoked // POST: FCTVAL = item at top of stack void StackPrint(void); // PRE: && InitStack has been invoked // POST: Prints contents of stack on screen