0% found this document useful (0 votes)
10 views

03-c-model

Uploaded by

Arnav Shukla
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views

03-c-model

Uploaded by

Arnav Shukla
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 161

CS136

C Control Flow & Memory


January 25 – 30, 2024

1
C Model: Memory & Control Flow
Readings: CP:AMA 6.1–6.4, 7.1–7.3, 7.6, Appendix E

The ordering of topics is different in the text


Some portions of the above sections have not been covered yet

The primary goal of this section is to be able to model how C programs execute.
2
Models of Computation: Racket
In CS 135, we modelled the computational behaviour of Racket with
substitutions (the “stepping rules”).
To call (“apply”) a function, all arguments are evaluated to values and
then we substitute the body of the function, replacing the parameters
with the argument values.

(define (my-sqr x) (* x x))

(+ 2 (my-sqr x) (+ 3 1)))
=> (+ 2 (my-sqr 4))
=> (+ 2 (* 4 4))
=> (+ 2 16)
=> 18

3
Models of Computation: C
In this course, we model the behaviour of C with two complimentary
mechanisms:
• Control Flow
• Memory

4
Control Flow
Function Calls
Conditionals
Iteration
Control Flow
We use control flow to model how programs are executed.

During execution, we keep track of the program location, which is


“where” in the code the execution is currently occurring.

When a program is “run”, the program location starts at the


beginning of the main function (the program entry point).

6
Control Flow
In hardware, the location (register) is known as the program counter or
instruction pointer and contains the memory address of the current
instruction.

(More on this in CS 241).

7
Control Flow
In this course, we explore three types of control flow:
• Function Calls
• Conditionals (i.e., if-statements)
• Iteration (i.e., loops)

8
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo(int x) {
return 2 * x + bar(x);
}

int main(void) {
int result = foo(2);
}

9
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo(int x) {
return 2 * x + bar(x);
}

int main(void) {
► int result = foo(2); // C program start at entry point
} // (main-function)

10
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo(int x) {
return 2 * x + bar(x);
}

int main(void) {
int result =►foo(2); // Evaluate RHS before executing
} // assignment operator

11
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo( 2 ) { // x: 2
► return 2 * 2 + bar(2); // foo (callee) is called
}

int main(void) {
int result = foo(2); // main (caller) calls foo
}

12
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo( 2 ) { // x: 2
return►2 * 2 + bar(2); // executing statement left-to-right
}

int main(void) {
int result = foo(2);
}

13
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo( 2 ) { // x: 2
return 4 + ►bar(2); // foo calls bar
}

int main(void) {
int result = foo(2);
}

14
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar( 2 ) { // x: 2
► return 2 + 1;
}

int foo( 2 ) { // x: 2
return 4 + bar(2);
}

int main(void) {
int result = foo(2);
}

15
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar( 2 ) { // x: 2
return►2 + 1; // Evaluate RHS before returning
}

int foo( 2 ) { // x: 2
return 4 + bar(2);
}

int main(void) {
int result = foo(2);
}

16
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar( 2 ) { // x: 2
►return 3; // bar returns value 3 and with that
} // the control flow back to foo

int foo( 2 ) { // x: 2
return 4 + bar(2);
}

int main(void) {
int result = foo(2);
}

17
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo( 2 ) { // x: 2
return ►4 + 3; // Evaluate RHS before returning
}

int main(void) {
int result = foo(2);
}

18
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo( 2 ) { // x: 2
►return 7; // foo returns value 7 and with that
} // the control flow back to main

int main(void) {
int result = foo(2);
}

19
Control Flow: Function Calls
When a function is called, the program location “jumps” to the start of
the function. The return keyword “returns” the location back to the
calling function.

int bar(int x) {
return x + 1;
}

int foo(int x) {
return 2 * x + bar(x);
}

int main(void) {
int result =►7; // main continues
}

20
Function Calls: Return
The control flow statement return changes the program location to go
back to the most recent calling function: the control flow returns from the
callee back to the caller.

We revisit this when we introduce memory later in this section.


21
Control Flow: Conditionals
We introduced the control flow statement if in Section 02. We now
discuss if in more detail.

The syntax of if is:

if (predicate)
statement

where the statement is only executed if the predicate evaluates to


true:

if (n < 0) {
printf("n is less than zero\n");
}

Remember: the if statement does not yield a value. It only controls the flow of
execution.
22
Conditionals: if
The if-statement only affects whether the next statement is executed. To
conditionally execute more than a single statement, braces ({}) are used
to create a compound statement block (a sequence of statements):

if (predicate) {
compound_statement
}

For example:

if (n < 0) {
printf("n was less than zero\n");
n = -n;
}

23
Conditionals: if
Using braces is strongly recommended even if there is only one statement. It makes
the code easier to follow and less error prone.
(In the notes, we sometimes omit them, but only to save space.)

if (n < 0) {
printf("n is less than zero\n");
}

24
Conditionals: if – else
Braces are sometimes necessary to avoid a “dangling” else.

if (y > 0)
if (y != 7) printf("You lose");
else
printf("You win!"); // When does this print?

25
Conditionals: if
if (predicate) { stmnt may or may not be
compound_statement executed.
}

int input = read_int();


if (input == READ_INT_FAIL) {
return;
}
// ...

26
Conditionals: if – else
if (predicate) { Exactly one of stmnt_t or
compound_statement stmnt_e will be executed.
} else {
compound_statement
}

printf("input is ");
if (input % 2 == 0) {
printf("even\n");
} else {
printf("odd\n");
}
// ...

27
Conditionals: if – else if
if (predicate) { One or none of stmnt_1 ,
compound_statement stmnt_2, or stmnt_n will be
} else if (predicate) {
compound_statement executed.
}

int sign = 0;
if (input < 0) {
sign = -1;
} else if (input > 0) {
sign = 1;
}
// ...

28
Conditionals: if – else if – else
if (predicate) { Exactly one of stmnt_1 ,
compound_statement stmnt_2, stmnt_n , or stmnt_e
} else if (predicate) {
compound_statement will be executed.
} else {
compound_statement
}

printf("input is ");
if (sign == 1) {
printf("positive\n");
} else if (sign == -1) {
printf("negative\n");
} else { // if (sign == 0)
printf("zero\n");
}
// ...

29
Conditionals: if – else
If there is a return statement within an if condition, there may be no
need for an else.

int sum(int k) {
if (k <= 0) {
return 0;
} else {
return k + sum(k - 1);
}
}

// Functionally equivalent code


int sum(int k) {
if (k <= 0) {
return 0;
}
return k + sum(k - 1);
}

30
Conditionals: switch
The C switch control flow statement (see CP:AMA 5.3) has a similar structure to
else if and cond, but very different behaviour.

A switch statement has “fall-through” behaviour where more than one branch can be
executed.

In our experience, switch is very error-prone for beginner programmers.

Do not use switch in this course.

31
Conditionals: goto
The C goto control flow statement (CP:AMA 6.4) is one of the most disparaged
language features in the history of computer science because it encourages writing
“spaghetti code”: code that is hard to understand.

Modern opinions have tempered and most agree it is useful and appropriate in some
circumstances.

To use goto, you must also have labels (code locations).

if (k < 0) {
goto mylabel;
}
//...
mylabel:

Do not use goto in this course.

32
Types of Control Flow: Iteration
With mutation, we can control flow with a method known as iteration:

while (predicate)
statement

while (predicate) {
compound_statement
}

while is similar to if: statement is only executed if the predicate


evaluates to true.
The difference is, while repeatedly “loops back” and executes the
statement until the predicate evaluates to false.

Like with if, you should always use braces ({}), even if the loop body only consists
of a single statement.
33
Iteration: while vs. if
void countdown(int start) {
assert(start > 0);
while (start >= 0) {
printf("%d...\n", start);
--start;
}
}

while (predicate) {
compound_statement
}

if (predicate) {
compound_statement
}

34
Iteration: Procedural vs. Recursive Implementation
Using a loop to solve a problem is called iteration.

Iteration is an alternative to recursion and is much more common in


procedural programming.

// simple recursion // iteration


int sum(int k) { int sum(int k) {
if (k <= 0) { int acc = 0;
return 0; while (k > 0) {
} acc = acc + k;
return k + sum(k - 1); --k;
} }
return acc;
}

35
Iteration: Procedural vs. Recursive Implementation
When first learning to write loops, you may find that your code is similar
to using accumulative recursion:

// accumulative recursion // iteration


int sum_wrk(int k, int acc) { int sum(int k) {
if (k <= 0) { int acc = 0;
return acc; while (k > 0) {
} acc = acc + k;
return sum_wrk(k – 1, --k;
acc + k); }
} return acc;
}
int sum(int k) {
return sum_wrk(k, 0);
}

36
Iteration: Procedural vs. Recursive Implementation
When first learning to write loops, you may find that your code is similar
to using accumulative recursion:
• Initializing the accumulator

// accumulative recursion // iteration


int sum_wrk(int k, int acc) { int sum(int k) {
if (k <= 0) { int acc = 0;
return acc; while (k > 0) {
} acc = acc + k;
return sum_wrk(k – 1, --k;
acc + k); }
} return acc;
}
int sum(int k) {
return sum_wrk(k, 0);
}

37
Iteration: Procedural vs. Recursive Implementation
When first learning to write loops, you may find that your code is similar
to using accumulative recursion:
• Updating / mutating the accumulator

// accumulative recursion // iteration


int sum_wrk(int k, int acc) { int sum(int k) {
if (k <= 0) { int acc = 0;
return acc; while (k > 0) {
} acc = acc + k;
return sum_wrk(k – 1, --k;
acc + k); }
} return acc;
}
int sum(int k) {
return sum_wrk(k, 0);
}

38
Iteration: Procedural vs. Recursive Implementation
When first learning to write loops, you may find that your code is similar
to using accumulative recursion:
• Reversed base condition and break condition!

// accumulative recursion // iteration


int sum_wrk(int k, int acc) { int sum(int k) {
if (k <= 0) { int acc = 0;
return acc; while (k > 0) {
} acc = acc + k;
return sum_wrk(k – 1, --k;
acc + k); }
} return acc;
}
int sum(int k) {
return sum_wrk(k, 0);
}

39
Iteration: Nested loops
Loops can be nested.

int i = 5; > *****


while (i > 0) { > ****
int j = i; > ***
while (j > 0) { > **
printf("*"); > *
--j; >
}
printf("\n");
--i;
}

40
Iteration: Nested loops
While it is okay to use identifier names like i and j, as long as it is clear
what they refer to conceptually, it is oftentimes better to use more
meaningful ones:

int rows = 5; > *****


while (rows > 0) { > ****
int columns = rows; > ***
while (columns > 0) { > **
printf("*"); > *
--columns; >
}
printf("\n");
--rows;
}

41
Iteration: Common while Errors
A simple mistake with while can cause an “endless loop” or “infinite
loop”. Each of the following examples cause an endless loop.

while (i >= 0) // missing {}


printf("%d\n", i);
--i;

while (i >= 0); { // extra ;


printf("%d\n", i);
--i;
}

while (i = 100) { ... } // assignment instead of comparison

while (1) { ... } // constant true expression

42
Iteration: do ... while
The do control flow statement is similar to while.

do {
compound_statement
} while (predicate);

The difference is that statement is always executed at least once, since


the predicate is checked after the execution of statement.

int my_number = 136;


int guess = 0;
do {
printf("Try to guess my number!\n");
guess = read_int();
} while (guess != my_number && guess != READ_INT_FAIL);

43
Iteration: do ... while
A do ... while loop executes a block of code and then either repeats
the block or exits the loop depending on a predicate.

do ... while-loop: while-loop:

stmnt_a stmnt_a
do { while (pred?) {
stmnt_l stmnt_l
} while (pred?); };
stmnt_z stmnt_z

44
Iteration: break
The break control flow statement is useful when you want to exit from
the middle of a loop.
• break immediately terminates the current (innermost) loop.
• break is often used with a (intentionally) infinite loop.

int input = INT_MIN;


while (true) {
input = read_int();
if (input == READ_INT_FAIL) {
break;
}
// ...
}
// ...

break only terminates loops. You cannot break out of a conditional, e.g., if-
statement.
45
Iteration: continue
The continue control flow statement skips over the rest of the
statements in the current block ({}) and “continues” with the loop.

int input = INT_MIN;


while (true) {
input = read_int();
if (input == READ_INT_FAIL) {
break;
} else if (input % 2 == 1) {
continue;
}
// only process even numbers
}
// ...

46
Iteration: for
The final control flow statement we introduce is for, which is often
referred to as a for-loop.

A for-loop is a condensed version of a while-loop.

47
Iteration: for
The format of a while-loop is often of the form:

setup statement int i = 0;


while (predicate) { while (i < 5) {
body statement printf("%d", i);
update statement ++i;
} }

The C syntax allows to re-write this code as a single for-loop:

for (setup statement; predicate; update statement) {


body statement
}

for (int i = 0; i < 5; ++i) {


printf("%d", i);
}

48
Iteration: for

stmnt_a
for (setup, pred?, update) {
stmnt_l
}
stmnt_z

49
Iteration: for

stmnt_a
for (setup, pred?, update) {
stmnt_l
}
stmnt_z

50
Iteration: for
Most for-loops follow one of these forms (or “idioms”):

// counting up from 0 to n-1


for (int i = 0; i < n; ++i) {...}

// counting up from 1 to n
for (int i = 1; i <= n; ++i) {...}

// counting down from n-1 to 0


for (int i = n - 1; i >= 0; --i) {...}

// counting down from n to 1


for (int i = n; i > 0; --i) {...}

It is a common mistake to be “off by one” (e.g., using < instead of <=). Sometimes
re-writing as a while is helpful.
51
Iteration: for
In C99, the setup statement can be a variable definition. This is
convenient for defining a variable that only has local (block) scope within
the for-loop:

for (int i = 100; i >= 0; --i) {


printf("%d\n", i);
}

The equivalent while-loop would need an extra compound statement


(block):

{
int i = 100;
while (i >= 0) {
printf("%d\n", i);
--i;
}
}

52
Iteration: for
You can omit any of the three components of a for statement.

If the predicate is omitted, it always evaluates to true.

for (; i < 100; ++i) {...} // i was defined previously

for (; i < 100;) {...} // same as while (i < 100)

for (;;) {...} // infinite loop

53
Iteration: for
You can use the comma operator (,) to use more than one expression in the setup and
update statements of a for loop. See CP:AMA 6.3 for more details.

for (int i = 1, j = 100; i < j ; ++i, --j) {...}

A for-loop can almost always be rewritten as a while-loop (and vice-versa).

The only difference is when a continue statement is used. In a while-loop,


continue jumps back to the expression. In a for-loop, the update statement is
executed before jumping back to the expression.

54
Iteration: for vs. while
for (int i = 0; i < n; ++i) { ... }

Use for-loops if
• the number of iterations is known beforehand
• you need to keep tally of your current iteration

Usually, the iterator variable (here: i) is not changed in the body of the
for-loop.

int input = INT_MIN;


while ((input = read_int()) != READ_INT_FAIL) { ... }

Use while loops if


• the number of iterations is not known or predictable

55
Quiz time!
Considering the three for-loops on the right. Which one produces a
different output from the other ones? [Select the most appropriate
answer!]

A. for (int i = 0; i < 5; ++i) {


printf("%d\n", i);
}
B. for (int i = 0; i <= 4; i++) {
printf("%d\n", i);
}
C. for (int i = 5; i > 0; --i) {
printf("%d\n", 5 - i);
}
D. for (int i = 0; i != 9; i += 2) {
printf("%d\n", i / 2);
}
E. for (int i = 0, j = 10; i != j; ++i, j--) {
printf("%d\n", i);
}
56
Quiz time!
Answers will be discussed in class!

57
Memory
Structure of Memory
Overflow
Structure of Memory
One bit has two possible states: 0 or 1.

A byte consists of 8 bits. Each byte in memory can represent one of 256
possible states.

The smallest accessible (addressable) unit of memory is a byte.

To access a block of memory, you must know its position in memory,


which is known as its address.

For example, if you have 256 Bytes of memory (RAM), the address of the
first byte is 0 and the address of the last byte is 255 (28 − 1).

Note: Memory addresses are usually represented in hexadecimal, so the


address of the first byte is 0x00, and the address of the last byte is 0xFF.
59
Structure of Memory
You can visualize computer memory as a collection of “labeled mailboxes”
where each mailbox stores a byte.

(The values above are arbitrary.)

60
Structure of Memory: Defining Variables
Whenever a variable is defined, C
• reserves sufficient space in memory to store the variable,
• associates the location (i.e., address) of that space with the identifier,
• writes the initial value of the variable to that location (i.e., address).

61
Structure of Memory: Defining Variables
For example, with the definition

int n = 136;

C reserves a 4-byte block of memory, associates its address with n, and


stores the value 136 in that block.

(Here, each addressable memory cell (“row”) contains four bytes.)


62
Structure of Memory: Defining Variables
In our CS 135 substitution model, a variable is a “name for a value”.

When a variable appears in an expression, a substitution occurs, and the


identifier is replaced by its value.

In our new model, a variable is a “name for a location” (label, identifier)


where a value is stored.

When a variable appears in an expression, C “fetches” the contents at its


address to obtain the value stored there.

63
Structure of Memory: sizeof
The amount of space required to store data depends on its type.

The size operator (sizeof) yields the number of bytes required to store
a type (it can also be used on identifiers).

int n = 0;
trace_int(sizeof(int)); // int is a type
trace_int(sizeof(n)); // n is an identifier

> sizeof(int) => 4


> sizeof(n) => 4

(sizeof looks like a function, but it is an operator.)

Size may also depend on the environment (the machine and compiler).
64
Structure of Memory: sizeof
In C, the size of an int depends on the machine (processor) and / or the operating
system that it is running on.

Every processor has a natural “word size”, for example:


• 8-bit (e.g., ATmega328)
• 16-bit (e.g., Intel 8086)
• 32-bit (e.g., x86, ARM7)
• 64-bit (e.g., x86_64, amd64, ARM8)

Historically, the size of an int was the word size, but most modern systems use a 32-
bit int to improve compatibility.

In C99, the inttypes module (#include <inttypes.h>) defines many types (e.g.,
int32_t, int8_t) that specify exactly how many bits to use.

In this course, you should only use int, and there are always 32 bits in an int.

65
Structure of Memory: Integer Limits
In this course, the size of an integer is 4 bytes (32 bits).

Because C uses 4 bytes (32 bits) to store an int, there are only 232
(4,294,967,296) possible values that can be represented.

The range of C int values is −231 , … , 231 − 1 or


− , , .

The constants INT_MIN and INT_MAX are defined with those limit
values.

Unsigned integer variables represent the values 0, … , 232 − 1, but we do not use them
in this course.
66
Structure of Memory: Integer Limits
In the read_int function we provide, the value of the constant READ_INT_FAIL is
actually INT_MIN, so the smallest value of int that can be successfully read by our
read_int function is −2,147,483,647 (i.e., INT_MIN + 1).

67
Structure of Memory: Integer Overflow
If we try to represent values outside of the int limits, overflow occurs.

By carefully specifying the order of operations, you can sometimes avoid


overflow.

You should never assume what the value of an int will be after an overflow occurs:
the value of an integer that has overflown is undefined.

In CS 251 / CS 230 you learn more about overflow.


68
Structure of Memory: Integer Overflow

int bil = 1000000000;


int nine_bil = 9 * bil; // integer overflow

> Illegal instruction (core dumped)

Remember, do not try to “deduce” what the value of an int will be after
overflow: overflow behaviour is undefined!

69
Structure of Memory: Integer Overflow
Racket can handle arbitrarily large numbers, such as (expt 2 1000).
Why did we not have to worry about overflow in Racket?

Racket does not use a fixed number of bytes to store numbers. Racket represents
numbers with a structure that can use an arbitrary number of bytes (imagine a list of
bytes).

There are C modules available that provide similar features (a popular one is available
at gmplib.org).

70
Quiz time!
If a calculation causes an overflow, what will happen?

[Select the most appropriate answer!]

A. The value will wrap around the number line.


B. It will cause a runtime error.
C. The system will allocate additional memory.
D. The value will reset to 0.
E. Anything is possible.

71
Quiz time!
Answers will be discussed in class!

72
Additional Types
Characters
Structures
Additional Types: char
The char type is also used to store integers, but C only allocates 1 byte of
storage for a char (an int uses 4 bytes).

There are only 28 = 256 possible values for a char, and the range of
values is (−128, … , 127) in our edX environment.

Because of this limited range, char is rarely used for calculations. As the
name implies, it is often used to store characters.

74
The char Type: ASCII
Early in computing, there was a need to represent text (characters) in
memory.

The American Standard Code for Information Interchange (ASCII) was


developed to assign a numeric code to each character.

Upper case A is 65, while lower case a is 97. A ␣ (space) is 32.

ASCII was developed when teletype machines were popular, so the


characters 0, ..., 31 are teletype “control characters”, e.g., 7 (or \a)
is a bell sound.

The only control characters we use in this course are line feed (10), which
is the newline-character \n, and later null (0), which is the null-character
\0.

75
The char Type: Simplified ASCII Table
Invisible Punct. Digits Punct. Upper-case Letters Punct. Lower-case Letters Punct.
0 \0 32 ␣ 48 0 58 : 65 A 78 N 91 [ 97 a 110 n 123 {
33 ! 49 1 59 ; 66 B 79 O 92 \ 98 b 111 o 124 |
34 " 50 2 60 < 67 C 80 P 93 ] 99 c 112 p 125 }
35 # 51 3 61 = 68 D 81 Q 94 ^ 100 d 113 q 126 ~
36 $ 52 4 62 > 69 E 82 R 95 _ 101 e 114 r
37 % 53 5 63 ? 70 F 83 S 96 ` 102 f 115 s
38 & 54 6 64 @ 71 G 84 T 103 g 116 t
39 ' 55 7 72 H 85 U 104 h 117 u
40 ( 56 8 73 I 86 V 105 i 118 v
41 ) 57 9 74 J 87 W 106 j 119 w
10 \n 42 * 75 K 88 X 107 k 120 x
43 + 76 L 89 Y 108 l 121 y
44 , 77 M 90 Z 109 m 122 z
45 -
46 .
47 /

76
The char Type: Unicode
ASCII worked well in English-speaking countries in the early days of computing, but in
today’s international and multicultural environments it is outdated.

The Unicode character set supports more than 100,000 characters from all over the
world.

A popular method of encoding Unicode is the UTF-8 standard, where displayable ASCII
codes use only one byte, but non-ASCII Unicode characters use more bytes.

77
The char Type: Relation to int
In C, single quotes (') are used to indicate an ASCII character.
For example, 'a' is equivalent to 97 and 'z' is 122. C “translates” 'a'
into 97.

In C, there is no difference between the following two variables:

char letter_a = 'a';


char ninety_seven = 97;
trace_bool(letter_a == ninety_seven);

> true

Always use single quotes with characters: "a" (string) is not the same as 'a' (char).
78
The char Type: Output
The printf placeholder to display a character is "%c".

char letter_a = 'a';


char ninety_seven = 97;

printf("letter_a as char: %c\n", letter_a);


printf("ninety_seven as char: %c\n", ninety_seven);

printf("letter_a as decimal: %d\n", letter_a);


printf("ninety_seven as decimal: %d\n", ninety_seven);

> letter_a as char: a


> ninety_seven as char: a
> letter_a as decimal: 97
> ninety_seven as decimal: 97

79
The char Type: Arithmetic
Because C can interpret characters as integers, characters can be used in
expressions to avoid having “magic numbers” in your code.

// is_lowercase(c) returns true of c is lower case and false


// otherwise.
bool is_lowercase(char c) {
return ('a' <= c) && (c <= 'z');
}

// to_lowercase(c) converts the letter c to lower case.


char to_lowercase(char c) {
if (('A' <= c) && (c <= 'Z')) {
return c - 'A' + 'a';
}
return c;
}

80
The char Type: Arithmetic
The order of operation can have influence whether and int or char
overflows. While the code below returns the correct value most of the
time (i.e., in edX), there is no guarantee that it always will. After adding
'a', the variable c could potentially overflow!

// to_lowercase(c) converts upper case letters to lower


// case letters, everything else is unchanged
char to_lowercase(char c) {
if ((c >= 'A') && (c <= 'Z')) {
return c + 'a' - 'A'; // vs: c - 'A' + 'a';
}
return c;
}

81
The char Type: Input
In Section 03, we used the read_int function to read integers from
input.

We have also provided read_char for reading characters.

When reading int values, we ignored whitespace in the input. When


reading in characters, you may or may not want to ignore whitespace
characters, depending on your application. read_char has a parameter
for specifying if whitespace should be ignored.

82
The char Type: Input
Consider the following code, which uses READ_WHITESPACE to read
whitespace:

int main(void) {
while (true) {
char input = read_char(READ_WHITESPACE);
if (input == READ_CHAR_FAIL) {
break;
}
printf("%c", input);
}
}

Input: Output:
> This > This
> is > is
> CS 136! > CS 136!
> >

83
The char Type: Input
Consider the following code, which uses IGNORE_WHITESPACE to ignore
whitespace:

int main(void) {
while (true) {
char input = read_char(IGNORE_WHITESPACE);
if (input == READ_CHAR_FAIL) {
break;
}
printf("%c", input);
}
}

Input: Output:
> This > ThisisCS136!
> is
> CS 136!
>

84
Quiz time!
Assume char c contains a lower-case letter. What is the best way to
correctly convert it to upper case?

[Select the most appropriate answer!]

A. c -= ' ';
B. c -= 32;
C. c = c + 'A' - 'a';
D. c = c - 'a' + 'A';
E. c = c + 'a' - 'A';

85
Quiz time!
Answers will be discussed in class!

86
Additional Types: Symbols
In C, there is no equivalent to Racket’s 'symbol type. To achieve similar
behaviour in C, you can define a unique constant integer for each
“symbol”.

const int POP = 1; // instead of 'pop


const int ROCK = 2; // instead of 'rock

int my_favourite_genre = POP;

It is common to use an alternative naming convention (such as ALL_CAPS)


when using numeric constants to represent symbols.

We have provided some tools for working with C “symbols” on your assignments.
87
Additional Types: Symbols
In C, there are enumerations (enum, CP:AMA 16.5) which allow you to create your
own enum types and help to facilitate defining constants with unique integer values.

Enumerations are an example of a C language feature that we do not introduce in this


course.

After this course, we would expect you to be able to read about enum in a C reference
and understand how to use them.

If you would like to learn more about C or use it professionally, we recommend


reading through all of CP:AMA after this course is over.

88
Additional Types: Floating Point Numbers
The C floating point number type can represent real numbers.

float pi = 3.14159f;
float avogadro = 6.022e23f; // 6.022 * 10^23

The f-suffix for defining floats (or more precisely, for disambiguating between (32-bit)
floats and (64-bit) doubles) is not part of the official course material. Therefore, you are
not required to use it and you will not see it in assignments and exams. Using the f-
suffix for floats is, however, the preferred (i.e., correct) way.

C’s float type is similar to inexact numbers in Racket (which appear with an #i prefix in
the teaching languages):

(sqrt 2) ; => #i1.4142135623730951


(sqr (sqrt 2)) ; => #i2.0000000000000004
89
Floating Point Numbers: Printing
The printf placeholder to display a float is "%f".

float penny = 0.01f;


printf("One penny is $$%f.", money);

> One penny is $0.01.

90
Floating Point Numbers: Precision
Unfortunately, floats can be susceptible to accumulating precision errors.

float penny = 0.01f;


float dollar = 0.0f;
for (int n = 0; n < 100; ++n) {
dollar += penny;
}
printf("One hundred pennies are $$%f.", dollar);

> One hundred pennies are $0.999999.

91
Floating Point Numbers: Precision
Floats can also be susceptible to precision errors when using values of
different order of magnitude.

float billion = 1000000000.0f;


float thousand = 1000.0f;
printf("One billion is: %f\n", billion);
printf("One billion and a thousand is: %f\n", billion +
thousand);

> One billion is: 1000000000.000000


> One billion and a thousand is: 1000001024.000000

92
Floating Point Numbers: Precision
In the previous two examples, we highlighted the precision errors that
can occur with the float type. C also has a double type that is still
inexact but has significantly better precision.

Just as we use check-within with inexact numbers in Racket, we can


use a similar technique for testing in floating point numbers C.

Assuming that the precision of a double is perfect or “good enough” can be a serious
mistake and introduce errors.
Unless you are explicitly told to use a float or double, you should not use them in
this course.
93
Floating Point Numbers: float vs. double
Historically, C distinguishes between integers and real numbers. Both
types of numbers are represented in different bit-patterns: “two's
complement” for integers, “floating point” for real numbers.

Types of integers in C are historically char and int. The type for real
numbers is float (or single-precision floating point numbers). When
programmers needed higher precision, they introduced double-
precision floating point numbers, or double in short.

Both float and double are floating point numbers, just at different
levels of precision (32 bits vs. 64 bits).

94
Floating Point Numbers: Memory
A double has more precision than a float because it uses more memory.

Just as we might represent a number in decimal as 6.022 × 1023 , floating point


numbers use a similar strategy.
• A 32 bit float uses 23 bits for the mantissa and 8 bits for the exponent.
• A 64 bit double uses 52 bits and 11 bits respectively.

Floating point numbers and their internal representation are discussed in CS 251 / 230
and in detail in CS 370 / 371.

95
Additional Types: Structures
Structures (compound data) in C are similar to structures in Racket.

struct posn { // name of the structure


int x; // field type and identifier
int y;
}; // closing semicolon

Because C is statically typed, each field requires its own type information.

The structure type includes the keyword struct. For example, the type
is struct posn, not just posn. This can be seen in the definition of p
below.

struct posn p = {3, 4}; // initialization

Do not forget the last semicolon (;) in the structure definition.


96
Structures: Memory
For a structure declaration, no memory is reserved:

struct posn {
int x;
int y;
};

Memory is only reserved when a structure is defined.

struct posn p = {3, 4};

97
Structures: Size in Memory
The amount of space reserved for a struct is at least the sum of the
sizes of each field, but it may be larger.

struct posn {
int x; // size: 4 bytes
int y; // size: 4 bytes
};

struct posn p = {3, 4};


trace_int(sizeof(struct posn));

> sizeof(struct posn) => 8

98
Structures: Size in Memory
Always use the sizeof operator to determine the size of a structure.

struct data {
int x; // size: 4 bytes
char c; // size: 1 byte (in memory: 4 bytes, see below)
int y; // size: 4 bytes
};

struct data my_data = {136, 'C', 9001};


trace_int(sizeof(struct data));

> sizeof(struct data) => 12

99
Structures: Size in Memory
The size of a structure may depend on the order of the fields and the compiler
settings

struct s1 { struct s2 {
char c; char c;
int i; char d;
char d; int i;
}; };

trace_int(sizeof(struct s1));
trace_int(sizeof(struct s2));

> sizeof(struct s1) => 12


> sizeof(struct s2) => 8

C may reserve more space for a structure to improve efficiency and enforce alignment
within the structure (a.k.a., padding).

10
0
Structures: Size in Memory
struct data {
int x; // size: 4 bytes, padding: 0 bytes, offset: 0 bytes
char id; // size: 1 byte, padding: 3 bytes, offset: 4 bytes
int y; // size: 4 bytes, padding: 0 bytes, offset: 8 bytes
};

struct data my_data = {136, 'C', 9001}; // initialization


my_data.x; // my_data (addr) 0xF0
// .x (offset) + 0 => 0xF0
my_data.y; // my_data (addr) 0xF0
// .y (offset) + 8 => 0xF8
my_data.id; // my_data (addr) 0xF0
// .id (offset) + 4 => 0xF4

10
1
Structures: Initialization
C99 supports an alternative way to initialize structures:

struct posn p = {.y = 4, .x = 3};

This prevents you from having to remember the “order” of the fields in the
initialization.

Any omitted fields are automatically zero, which can be useful if there are many fields:
struct posn p = {.x= 3}; // p.y is 0

102
Structures: Fields
Instead of selector functions, C has a field access operator (.), which
“selects” the requested field.

struct posn p = {3, 4};


trace_int(p.x); // accessing field x
trace_int(p.y); // accessing field y

> p.x => 3


> p.y => 4

103
Structures: Mutation
The assignment operator can be used with structures to copy all fields
from one structure to another. Individual fields can also be mutated.

struct posn p = {1, 2};


struct posn q = {3, 4};

p = q; // p.x => 3, p.y => 4


p.x = 23; // p.x => 23

104
Structures: Mutation
The braces ({}) are part of the initialization syntax and cannot be used in
assignment. Instead, each field must be mutated separately.

struct posn p = {1, 2};


p = {5, 6}; // INVALID
p.x = 5; // VALID
p.y = 6; // VALID

On rare occasions, you may want to define a new struct so you can
mutate all fields of an existing one “all at once”.

struct posn p = {1, 2};


struct posn new_p = {5, 6};
p = new_p;

105
Structures: Comparators
Comparators (e.g., <, ==) do not work with structures.

You must define your own comparator functions.

// posn_equal(a, b) returns true if a and b represent the same


// coordinate, and false otherwise.
bool posn_equal(const struct posn a, const struct posn b) {
return (a.x == b.x) && (a.y == b.y);
}

// posn_lt(a, b) returns true if a is shorter (i.e., closer to


// the origin) than b, and false otherwise.
bool posn_lt(const struct posn a, const struct posn b) {
return (a.x * a.x + a.y * a.y) < (b.x * b.x + b.y * b.y);
}

106
Structures: Output
printf only works with primitive types and does not work with structures.

To print a structure, you must print each field individually or define your
own print function.
// posn_print(p) prints the content of p.
// effects: writes to the console
void posn_print(const struct posn p) {
printf("The value of p is (%d,%d).\n", p.x, p.y);
}

struct posn p = {3, 4};


printf("p: (%d,%d)\n", p.x, p.y);
posn_print(p);

> p: (3,4)
> The value of p is (3,4).
>

107
Quiz time!
Considering the structure declarations below and select all true
statements regarding struct data d.

[Select all that apply!] A. C does not allow to store a


structure within another
struct posn { structure.
int x;
int y; B. Its size is 16 bytes.
}; C. Its size is at least 10 bytes.
struct data { D. The syntax for accessing its id
char id; is d.id.
struct posn pos; E. The syntax for accessing its y-
char flag; coordinate is d.posn.y.
};

struct data d = {'C', {1, 0}, '5'};

108
Quiz time!
Answers will be discussed in class!

A. C does not allow to store a


structure within another
structure.
B. Its size is 16 bytes.
C. Its size is at least 10 bytes.
D. The syntax for accessing its id
is d.id.
E. The syntax for accessing its y-
coordinate is d.posn.y.

109
Sections of Memory
In this course we model five sections (or “regions”) of memory:

Other courses may use alternative names. The heap section is introduced in Section 10.
110
Sections of Memory
Sections are combined into memory segments, which are recognized by the hardware
(processor).

When you try to access memory outside of a segment, a segmentation fault occurs
(more on this in CS 350).

111
Sections of Memory: The Code Section
When you program, you write source code in a text
editor using ASCII characters that are “human
readable”.

To “run” a C program, the source code must first be


converted (compiled) into machine code, i.e., code that
is “machine readable”.

This machine code is then placed into the code section


of memory where it can be executed.

Converting source code into machine code is known as compiling. It is briefly discussed
in Section 13 and covered extensively in CS 241.
112
Sections of Memory
Earlier we described how C “reserves space” in memory for a variable
definition, such as,:

int n = 0;

The location in memory depends on whether the identifier has global or


local scope and whether the data is constant or variable.

All global variables are placed in either the global constant section or the global
variable section.
113
Sections of Memory: Global Constants
Global constants are available throughout the entire
execution of the program.

The space for global constants is reserved before the


program begins execution.

114
Sections of Memory: Global Variables
Global variables are available throughout the entire
execution of the program.

The space for global variables is reserved before the


program begins execution.

Both global memory sections are created and initialized at compile time.
115
Sections of Memory: Local Data
Data with local scope, i.e., local constants, local variables, and function
parameters, must be stored somewhere in memory.

bool foo(int a, char c) { // a, c


const int POWER = 9001; // POWER
int result = 0; // result
for (int i = 0; i < c; i *= 2) { // i
result += c;
}
return result > POWER;
}

116
Sections of Memory: The Return Address
In addition, a function must know where to return to when it reaches a
return-statement (or, for void-functions, when it reaches the end of
the function).
In other words, the system must “remember” the program location to
“jump back to” when a function returns the control flow to the caller.

1. int inc( 2 ) {
2. ►return 3;
3. }
4.
5. int main(void) {
6. int result = inc(2);
7. }

This location is known as the return address.

117
Sections of Memory: Stack Frames
All local data of a function and its return address are stored in a stack
frames.

Each function call generates a stack frame. Each stack frame contains:
• the argument values
• all local data (both variables and constants) that appear within the
function block (including any sub-blocks, e.g., for-loops)
• the return address

The return address is a location from inside the calling function.


118
Sections of Memory: The Stack
All stack frames are stored in the stack section of our C
memory model.

In practice, the “bottom” of the stack (i.e., where the


main stack frame is placed) is placed at the highest
available memory address. Each additional stack frame
is then placed at increasingly lower addresses. The
stack “grows” toward lower addresses.

If the stack grows too large, it can “collide” with other sections of memory. This is
called “stack overflow” and can occur with very deep (or infinite) recursion.
119
Sections of Memory: The Stack Frame

=========================
inc:
1. int inc(int x) { x: 2
2. int result =►x + 1; result: ???
3. return result; r/a: main:7
4. } =========================
5. main:
6. int main(void) { result: ???
7. int result = inc(2); r/a: OS
8. } =========================

In this course, we use the name of the calling function and a line number
(or an arrow) to represent the return address (or short: r/a).

120
Sections of Memory: Stack Frames
As with Racket, before a function can be called, all arguments must be
values.

Whereas space for a global data is reserved before the program begins
execution, space for a local data is only reserved when the function is
called.

When the function returns, the stack frame is removed and effectively
“disappears”.

In C, local variables are known as automatic variables because they are “automatically”
created when needed. There is an auto keyword in C, but it is rarely used.

C makes a copy of each argument value and places the copy in the stack frame. This
is known as the “pass by value”-convention.
121
Stack Frames: Calling a Function
We can now model the entire control flow when a function is called:
• a stack frame is created and stored (“pushed”) in the stack
• a copy of each of argument is placed in the stack frame
• the current program location is placed in the stack frame as the return
address
• the program location is changed to the start of the new function
• the initial values of local data are set when their definition is
encountered

122
Stack Frames: Calling a Function
When a function returns:
• the current program location is changed back to the return address
(which is retrieved from the stack frame)
• the stack frame is removed (“popped”) from the stack

123
Stack frames: Calling a function
The return address is a code location from the calling function.
It has nothing to do with the location of any return statement(s), or if one does not
exist (e.g., a void function).
There is always exactly one return address in a stack frame.

124
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9.
10. int foo(int x) {
11. int b = x / 3;
12. int res = 2 * bar(b) + bat(b);
13. return res;
14. }
15. =========================
16. int main(void) { main:
17. int result =►foo(50); result: ???
18. ... r/a: OS
19. } =========================

125
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b =►x / 3; x: 50
12. int res = 2 * bar(b) + bat(b); b: ???
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

126
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res =►2 * bar(b) + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

127
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res = 2 *►bar(b) + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

128
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4. =========================
5. int bar(int x) { bar:
6. int res =►x * 2; x: 16
7. return res; res: ???
8. } r/a: foo:12
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res = 2 * bar(b) + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

129
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4. =========================
5. int bar(int x) { bar:
6. int res = x * 2; x: 16
7. ►return res; res: 32
8. } r/a: foo:12
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res = 2 * bar(b) + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

130
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res =►2 * 32 + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

131
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res =►64 + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

132
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res = 64 +►bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

133
The Call Stack: Example
1. int bat(int n) {
2. ►return x % 2;
3. }
4.
5. int bar(int x) { =========================
6. int res = x * 2; bat:
7. return res; n: 16
8. } r/a: foo:12
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res = 64 + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

134
The Call Stack: Example
1. int bat(int n) {
2. return►0;
3. }
4.
5. int bar(int x) { =========================
6. int res = x * 2; bat:
7. return res; n: 16
8. } r/a: foo:12
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res = 64 + bat(b); b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

135
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res =►64 + 0; b: 16
13. return res; res: ???
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

136
The Call Stack: Example
1. int bat(int n) {
2. return n % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9. =========================
10. int foo(int x) { foo:
11. int b = x / 3; x: 50
12. int res = 2 * bar(b) + bat(b); b: 16
13. ►return res; res: 64
14. } r/a: main:17
15. =========================
16. int main(void) { main:
17. int result = foo(50); result: ???
18. ... r/a: OS
19. } =========================

137
The Call Stack: Example
1. int bat(int x) {
2. return x % 2;
3. }
4.
5. int bar(int x) {
6. int res = x * 2;
7. return res;
8. }
9.
10. int foo(int x) {
11. int b = x / 3;
12. int res = 2 * bar(b) + bat(b);
13. return res;
14. }
15. =========================
16. int main(void) { main:
17. int result = 64; result: 64
18. ► ... r/a: OS
19. } =========================

138
Memory Snapshots
You may be asked to draw a memory diagram
(including the call stack) at a particular
moment in the code execution.
For example, “draw the memory when line 18
is reached”.
• make sure you show any variables in the global
and read-only sections, separate from the stack
• include all local variables in stack frames,
including definitions that have not yet been
reached (or are incomplete)
• local variables not yet fully initialized have a value
of ???
• you do not have to show any temporary storage
(e.g., intermediate results of an expression)

139
Stack Frames: Temporary Results
When evaluating C expressions, the intermediate results must be
temporarily stored.

int result = foo(3) + bar(4);

In the above expression, C must temporarily store the value returned


from foo(3) “somewhere” before calling bar(4).

In this course, we are not concerned with this “temporary” storage.

Intermediate results and the return value (for non-void functions) are stored in a
temporary memory area, which we are not discussing in this course. This is discussed
further in CS 241.
140
Stack Frames: Uninitialized Memory
In most situations, mutable variables should be initialized, but C allows
you to declare variables without any initialization.

int i;

For all global data, C automatically initializes the variable to 0.

Regardless, it is good style to explicitly initialize global variables to be


zero, even if they are implicitly initialized.

int global_var = 0;

141
Stack Frames: Uninitialized Memory
A local variable (on the stack ) that is uninitialized has an arbitrary initial
value.

void mystery(void) {
int k;
printf("The value of k is %d\n", k);
}

> warning: variable 'k' is uninitialized when used here

edX gives you a warning if you obtain the value of an uninitialized


variable.

In the example above, the value of k will likely be a leftover value from a previous stack
frame.
142
Quiz time!
What is the minimum number of distinct elements that would be stored
in the stack frame for the function below?

[Select the most appropriate answer!]

const int f = 42;


A. 4
int magic(int a, int b) { B. 5
const int c = a * f;
int e = c - b; C. 6
return e; D. 7
}
E. 8

143
Quiz time!
Answers will be discussed in class!

14
4
Stack Frames: Recursion in C
Now that we understand how stack frames are used, we can see how
recursion works in C.

In C, each recursive call is simply a new stack frame with a separate frame
of reference.

The only unusual aspect of recursion is that the return address is a


location within the same function.

In the following example, we also see the control flow with the if-
statement.

145
Stack Frames: Recursion in C

1. int sum(int n) {
2. if (n == 0) {
3. return 0;
4. } else {
5. return n + sum(n – 1);
6. }
7. }
8.
9. int main(void) { =========================
10. int result =►sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
146
Stack Frames: Recursion in C

1. int sum(int n) {
2. if►(n == 0) {
3. return 0;
4. } else {
5. return n + sum(n – 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
147
Stack Frames: Recursion in C

1. int sum(int n) {
2. if (n == 0) {
3. return 0;
4. } else {
5. return n +►sum(n - 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
148
Stack Frames: Recursion in C

1. int sum(int n) { =========================


2. if►(n == 0) { sum:
3. return 0; n: 2
4. } else { r/a: sum:5
5. return n + sum(n – 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
149
Stack Frames: Recursion in C

1. int sum(int n) { =========================


2. if (n == 0) { sum:
3. return 0; n: 2
4. } else { r/a: sum:5
5. return n +►sum(n - 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
150
Stack Frames: Recursion in C

=========================
sum:
n: 1
r/a: sum:5
1. int sum(int n) { =========================
2. if►(n == 0) { sum:
3. return 0; n: 2
4. } else { r/a: sum:5
5. return n + sum(n – 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
151
Stack Frames: Recursion in C

=========================
sum:
n: 1
r/a: sum:5
1. int sum(int n) { =========================
2. if (n == 0) { sum:
3. return 0; n: 2
4. } else { r/a: sum:5
5. return n +►sum(n - 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
152
Stack Frames: Recursion in C
=========================
sum:
n: 0
r/a: sum:5
=========================
sum:
n: 1
r/a: sum:5
1. int sum(int n) { =========================
2. if►(n == 0) { sum:
3. return 0; n: 2
4. } else { r/a: sum:5
5. return n + sum(n – 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(3); main:
11. ... result: ???
12. } r/a: OS
=========================
153
Stack Frames: Recursion in C
=========================
sum:
n: 0
r/a: sum:5
=========================
sum:
n: 1
r/a: sum:5
1. int sum(int n) { =========================
2. if (n == 0) { sum:
3. ►return 0; n: 2
4. } else { r/a: sum:5
5. return n + sum(n – 1); =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(2); main:
11. ... result: ???
12. } r/a: OS
=========================
154
Stack Frames: Recursion in C

=========================
sum:
n: 1
r/a: sum:5
1. int sum(int n) { =========================
2. if (n == 0) { sum:
3. return 0; n: 2
4. } else { r/a: sum:5
5. ►return n + 0; =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(2); main:
11. ... result: ???
12. } r/a: OS
=========================
155
Stack Frames: Recursion in C

1. int sum(int n) { =========================


2. if (n == 0) { sum:
3. return 0; n: 2
4. } else { r/a: sum:5
5. ►return n + 1; =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(2); main:
11. ... result: ???
12. } r/a: OS
=========================
156
Stack Frames: Recursion in C

1. int sum(int n) {
2. if (n == 0) {
3. return 0;
4. } else {
5. ►return n + 3; =========================
6. } sum:
7. } n: 3
8. r/a: main:10
9. int main(void) { =========================
10. int result = sum(2); main:
11. ... result: ???
12. } r/a: OS
=========================
157
Stack Frames: Recursion in C

1. int sum(int n) {
2. if (n == 0) {
3. return 0;
4. } else {
5. return n + sum(n – 1)
6. }
7. }
8.
9. int main(void) { =========================
10. ►int result = 6; main:
11. ... result: ???
12. } r/a: OS
=========================
158
Sections of Memory
So far, we have introduced four of the five sections of memory:
• Code
• Read-only data
• Global data
• Heap ✘
• Stack

Other courses may use alternative names.


The heap section is introduced in Section 10.
159
End of the Session

• Use the introduced control flow statements Any further


(iterations, conditionals) in your program.
questions?
• Re-write a recursive function with iteration and vice
versa
• Explain integer limits and overflow
• Use the char type and explain how characters are
represented in ASCII
• Use structures

160
End of the Session

• Explain how C execution is modelled with memory Any further


and control flow, as opposed to the substitution
model of Racket questions?
• Describe the four areas of memory: code, global
variables, global constants, and the stack
• Identify which in section of memory an identifier is
stored
• Explain a stack frame and its content (return
address, parameters, local variables)
• Model the execution of small programs by hand, and
draw the stack frames at specific execution points
(i.e., memory diagrams)

161

You might also like