0% found this document useful (0 votes)
30 views437 pages

Combined - Itp3 To 11

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)
30 views437 pages

Combined - Itp3 To 11

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/ 437

WEEK 3

Reading: Conditional Statements in C


This reading introduces you to branching. You will learn how decision-making can be incorporated into a program. You will
learn to write a program incorporating decision-making using conditional statements, including the if statement, if-else
statement, and switch statement.

Statements and Blocks in C:

Statement: A statement is an expression terminated by a semicolon. A program consists of many such statements.

A statement needs NOT to be an entire line in a program. Whenever a C compiler sees a semicolon (;) in a line, which is not
in a string or in a comment, it considers it as the end of the current statement. Thus, there can be more than one statement
in a single line.

For example:

For the C compiler, there are in total four statements in the above snippet, which are listed as follows:

Statement 1: c = a + b;

Statement 2: i++;

Statement 3: printf(“Hello World”);

Statement 4: 25;

In fact, ‘25;’ is also a statement. It is an expression whose value is True. Since we are not assigning it to a variable or not
doing anything with it, this expression is just ignored by the compiler.

Block: A block in C is a set of statements enclosed inside a set of braces { and }.

A block is also called a compound statement since it consists of one or many statements combined as a single unit.

For example:

Why Block Statements?

Blocks can be used in scenarios where we want to make a decision in a program, and based on the outcome of the decision,
whether True or False, we want to execute a particular block.
Conditional Statements in C: The If Statement

The if statement is one of the simplest conditional statements in C, which helps us make a decision and choose a
corresponding path of action. Branching in C language is primarily done using the if statement.

Syntax:

Condition is an expression in C that is either True or False or it evaluates to either True or False. A Conditional expression
can be written as:

• True or False

• Statement using relational operators such as ==, >= , <=, !=, <, and > Example:

o (x+y >= 10)

o (x == 0)

• Statement using logical operators such as &&, ||, and ! Example:

o (marks >= 90 && marks <= 100)


Note: There is a difference between the relational operator ‘==’ and the assignment operator ‘=’. The ‘==’ operator is used
for comparing equality in a conditional expression that results in either True or False, whereas the ‘=’ operator is used to
assign a value to a variable in assignment statements.

Evaluation or Outcome of a Condition

The outcome of a condition is always one of the following:

• Non-zero or true

• Zero or false

Examples:

Syntax of an if statement:

The statement gets executed only if the condition evaluates to true or non-zero.
The block of statements gets executed only if the condition evaluates to true or non-zero.

Flowchart of an if statement:

Figure 1: Flowchart of an if statement.

Let us take an example of a C program that uses an if statement.

Figure 2: A C program to check if the entered number is positive, using a conditional if statement.

The program in Figure 2 is to check if an entered number is positive. It gets the number in variable ‘a’ from the user and
checks it in a condition in line 6 using an if statement. The conditional statement is ‘(a > 0)’. Whether ‘a’ is greater than 0 or
not. The value of this conditional statement is either True, when ‘a’ is greater than 0, and it is False when ‘a’ is not greater
than 0. This value of the conditional statement decides whether the if statement block will be executed or skipped. The
statements in the block (lines 7 to 9) are executed if the condition is true, otherwise the statements are ignored or skipped.

The program in Figure 1 thus runs differently depending on the entered number. Following are some example cases:

1. If the number is 23 Output: Enter a number 23 Number is positive Rest of the program Program execution:
Variable is initialized at line 3 and the user is prompted to ‘Enter a number’ at line 4. The program waits for the user
to type a number and press Enter. When the user enters 23, at line 5, variable ‘a’ is assigned a new value 25 using
scanf(). The ‘scanf’ is a standard input function provided by the C programming language to take input from the
user and store it at a location corresponding to the address of a variable. Now, with the value stored at the address
of ‘a’ as 23 (that is, a is equal to 23), at line 6, the value of the conditional statement (a > 0) becomes True, since 23
is greater than 0. Thus, program flow enters the block at line 7 and executes the statements sequentially. In this
program, there is only one statement in the block, line 8. After executing the statement in the block and printing
‘Number is positive’, the program flow continues sequentially with the next statements, that is, lines 10 and 11. At
line 10, the program prints ‘Rest of the program’ in a new line and the program exits and stops the execution at line
11.
2. If the number is -20 Output: Enter a number -20 Rest of the program If the user enters the number ‘-20’, the value
of the conditional statement (a > 0) in line 6 becomes ‘False’ since the value stored at the address of ‘a’ is -20 and -
20 is NOT greater than 0. Thus, the program control entirely skips the if statement block (between curly braces,
line 8) and directly executes the next statement in the flow, that is, line 10. Thus, the program prints only ‘Rest of
the program’.
3. If the number is 0 Output: Enter a number 0 Rest of the program Similar to Case II, the input is 0, which is NOT
greater than 0. Hence, the entire if statement block was skipped to print only ‘Rest of the program’.
The If-Else Statement

The if statement without else executes a block only if the condition is true. But the program does nothing if the condition
is false, and the rest of the program is executed.

When we also want to define a statement or block of statements, which should be executed when the condition is false, we
use the optional else clause. The if-else statement executes a block if the condition is true, otherwise executes another
block if the condition is false.

Syntax:

If the condition is true, the program executes only statements in ‘block1’ and not ‘block2’. Otherwise, if the condition is
false, it executes only ‘block2’. The execution flow of ‘rest of the program’ is independent of the if-else statement, and it is
executed as it is in either case.

Nested If-Else Statements

Nested if statement: This is an if statement inside another if statement when there are no else clauses. This occurs in the
following scenarios:
Syntax:

Nested if-else statement: An if-else statement inside the if block or else block of another if-else statement is called a
nested if-else statement. Please note that there can always be an if without an else clause.

Syntax:

Similarly, we can have any number of such nestings, although it may make the code appear bulky.

Example of nested if-else statement

Let us consider a C code snippet of a program to find the largest of three numbers in Figure 3. Let us assume the three
numbers are unique. The program in line 5 stores three integers provided by the user in variables a, b, and c.

First, we check if a is greater than b in line 6. If a is greater than b, this means b is not the largest; either a or c is the largest
number. Whichever of a or c is greater, that number is the largest. To compare between a and c, we put a nested if-else
statement in the if block that is lines 8–11. If now, a is greater than c, this implies a is the largest, thus, print “A”. At line 9,
condition 1 (is a > b?) and condition 2 (is a > c?) both are True. Otherwise, if a is not greater than c, that is, c is greater than
a, then print “C” in line 11 since C is the largest.

If our first condition, if a is greater than b, is False, then else block is executed, that is, in lines 14–19. Here, b is greater
than a, thus, either b or c is the largest. So, we put another nested if-else statement (lines 15–18) but inside the else block
this time, to compare between b or c. If b is greater than c, in line 16, we print “B” as the largest number since b is greater
than both a and c. Otherwise, if b is lesser than c, this implies c is the largest since c has already been established as a
greater number than a. Thus, we print “C” in the else statement of the nested if-else statement in line 18.
Figure 3: A C program to find the largest of three numbers.

Let us consider a = 2, b = 8, and c = 4 as entered numbers of the program in Figure 3.

Program control in line 6 checks is a greater than b? False! 2 is not greater than 8. Thus, control goes directly to the else
part in line 13. Now inside else, it checks is b greater than c? in line 15. Yes! 8 is greater than 4. Thus, the program
successfully prints out B as the largest number. And after line 16, the control goes to line 20 to the “End of the program”.

Output:

Please note that with such usage of nested if-else statements, we ensured that exactly one of the four statements/blocks
would be executed out of line 9 or 11 or 16 or 18. Thus, in such scenarios, branching decision-making or nested if-else
statements can be used.

The Switch Statement

A switch statement is a compact way of writing a multiway decision that tests whether an expression matches one of several
constant integer values and branches accordingly.

Syntax
A switch case statement is used when we want to execute a certain matching statement or block of statements for a
particular integer value, which the expression evaluates to.

• The expression (expr) must evaluate to a constant integer value.

• The ‘case’ keyword is used to match the value of the expression to a statement or block of statement. const-expr
is a constant integral value (not a keyword) that accompanies ‘case’ keyword.

• The ‘break’ keyword is used to stop the execution inside the switch statement block. Once statements inside the
switch block start getting executed pertaining to a case match until a ‘break’ is found, the execution will not stop,
and all the statements will be executed irrespective of their cases. If ‘break’ is not used, the flow would fall through
the next case and execute the statements until the switch statement naturally terminates or a ‘break’ is found.

• The ‘default’ keyword is used to specify statement(s) that need to be executed when there is no case match. There
can only be a limited number of cases that can be written in a switch statement. To handle any other value, which
is not already specified in a case, the ‘default’ keyword is used.

If expr evaluates to const-expr1, statements1 are executed and the ‘break’ is encountered and thus the control comes out
of the switch statement. Otherwise, if expr evaluates to const-expr2, statements2 are executed. If expression evaluates to
a value, which is not specified in any case, then default statements are executed.

Examples

Let us take a few examples of branching in C using switch case statements.

Figure 4: A C snippet to match ‘language’ using a switch case statement.

In Figure 4, the integer value of expression ‘language’ is 10. So, the execution flow will directly jump to case 10. As there is
no case 10 (only case 1, case 2, and case 3 are present), the flow goes to the ‘default’ and prints “Other programming
language” and terminates. Thus, the output is:
Figure 5: A C snippet to match a ‘number’ using a switch case statement.

The code snippet in Figure 5 is a very unusual written switch statement. There are no statements as well as no breaks in
cases 1, 2, 4, and 5. Please note that any case can have 0 or more statements or it may or may not have a ‘break’ statement.
When there is no break, the execution flow keeps on falling through the next cases.

The value of the expression number is 5, so it will directly jump to case 5. Since no statements are there in case 5 and, also,
no break is present, the flow falls through the next case, i.e., case 6, and prints “Four, Five, or Six.” Then, it sees a break in
line 12 and gets out of the statement to termination.

Thus, the output is:

Reading Summary

In this reading, you have learned the following:

• How to interpret and use conditional statements in a C code

• The if-else statement and nested if-else statement

• When and how to use a switch statement for branching in a C program


C Programming Practice Lab1
Assignment Questions:

PracticeLab1_Question1

a) Write a C program that takes three integers as input from the user. Print True when all the three integers
are equal, and False otherwise. Do not use switch statements or loops in your program.

b) Now, modify the above program to print True when none of the three integers entered by the user is
equal to another, and False otherwise.

PracticeLab1_Question2

Write a C program that accepts as inputs the marks obtained by a student in a particular exam and then
prints the description of marks obtained based on the following rules.

>=90 - Extraordinary

>=80 - Very Good

>=65 - Good

>=50 - Average

>=40 - Below Average

else - Fail

It is important to note that the program written in the file has some compile time and runtime errors. You
need to resolve them so that the program works correctly. You can use the below test cases for your
reference.

Testcase1 Testcase2 Testcase3

Input - 65 Input - 15 Input - 49

Output - Good Output - Fail Output - Below Average

Compilation/Execution Steps:

• Open a new terminal by clicking on the menu bar and selecting ->Terminal ->New Terminal
Set the path in the terminal: Type the below command and click Enter. (Refer to the md file in the lab for
more details).

cd Week_3/PracticeLab1/Question1 (Change to Question2 when required)

Compile the C file using the following command in the terminal:

gcc <Filename.c> (Example: gcc Question1.c)

Run the C file using the following command in the terminal once the a.out file is created:

./a.out

Note: Every time you make a change in your program or the filename.c file, it is required to compile the
program again using "gcc filename.c" to reflect that change in your a.out. Otherwise, you will be accessing
an a.out file of the previous version of your code and changes you made will not be reflected in the output.
Reading: Loops and While Loop
Reading Objective:

This reading introduces you to looping. You will learn how repetitive and iterative tasks can be achieved using a while loop.

Loops in C:

Looping is used when we want to repeatedly execute a particular statement or a block of statements a number of times to
solve a problem, which is repetitive in nature. Loops are also called repetitive constructs.

What kind of problems are repetitive in nature?

• To print all numbers from 1 to 500

• Compute the average mark of students in a class of size 100

• Sum all the digits on a positive number (the number can have any number of digits)

In the above problems, loops are very effective in writing the program.

Loops should terminate:

If a loop doesn’t terminate, it will keep on executing (goes into an infinite loop) using resources such as RAM, CPU, storage,
etc. This may result in a runtime error. A loop, thus, should have a finite number of steps.

Figure 1: Example flow of a general loop

For example, in Figure 1, suppose we have a key variable x = 25. In step 2, we check the control expression in line 3 if x
is less than 50. If it is true, then we print the current value of x, incrementing the key variable, x by 1. And we go back to the
beginning of the loop and reevaluate the control expression since we have updated the key variable. Repeat this step until
the condition turns false. If it turns false, go to step 3.

Step 2 is thus a looping step. The conditional statement checks a key variable, and some statements in lines 5, 6, and 7 are
executed repeatedly in the loop until the condition remains true. These statements constitute the body of the loop. The
loop will terminate since the statements in the loop also change the key variable, ‘x,’ increasing it to ultimately make the
condition false.

While Loop in C
‘While’ loops are simple loops that repeat a particular statement or block of statements as long as the control expression
evaluates to True. The structure is similar to the general loop above. There is a key variable, control expression involving
the key variable and body of the loop, which is executed while the expression remains True.

It uses a variable in control expression that is generally defined outside.

Syntax

These statements are executed until exp (a control expression) remains true. Following area few examples of Control
Expressions:

• while (answer == ‘Y’): remain in a loop until the value of the answer variable is true.

• while (count): a variable can also be an expression. Here if the count is an integer, if it is non-zero, a loop is
executed, and if it is zero, the loop is terminated.

• while (5): 5 is always a non-zero; thus, it is always True. Thus, there will be an infinite loop unless there is a
mechanism to come out of the loop, which we will see later in this reading.

• while (base>0 && power>=0): relational sub-expressions joined using a logical AND operator to form a
relational expression.

Example:

Consider a C program snippet in Figure 2 to print numbers till 3 using a while loop construct. In line 1, variable var is
initialized with value 1. This will act as the key variable in our loop. Line 2 starts the while loop, which should run the loop
body until ‘var’ remains less than or equal to 3. The loop body is the block of the statement(s) in curly braces in line 4.

The execution flow enters the loop with the value of var as 1. Since 1 is less than or equal to 3, the control expression is true,
and line 4 is executed. It prints the current value of var, that is, 1, and after executing line 4, the value of var is incremented
by 1 (since we have written var++) to become 2. The control flow loops back to line 2 to again compare the value of var with
3. Since it is 2 that is still less than or equal to 3, the body is executed once again. The current value of the variable is printed,
that is, 2, and its value is incremented by 1 to 3. Again, the condition is checked, which is true since 3 is less than or equal
to 3, and line 4 is executed one last time, printing the value of var, 3, and incrementing it to 4. When the flow loops back to
line 2 to check the condition, it now evaluates to false since 4 is not less than or equal to 3, and the loop terminates.

Figure 2: Snippet using while loop construct to print numbers till 3


Hence the loop is executed 3 times, printing values 1, 2, and 3. Then finally terminates executing line 6. The output of the
above snippet is:

A Null Body

There is a difference between while (num > 0) and while (num > 0);

After a while loop, a statement or block is placed. For example:

But a while loop in C can also have a null body. As we know, any statement in C ends with a semicolon ( ; ). Thus, a
standalone semicolon represents an empty statement. If instead of any statement or block of statements in the body of
the ‘while’ loop, there is just a semicolon, it represents an empty body.

Please note that such empty loops are generally unwanted since they can be infinite. Do NOT put a semicolon after
‘while()’, unless it is intentionally required.

The loop will still run, use up, and waste computer resources, but in each iteration, it does nothing. And if the loop is started,
it will most probably never terminate since the condition for which the loop started remains true, and the key variable is
not modified in any iteration.

Example1 of a While Loop

To write a program to calculate the sum of the first N positive integers, you may define the value of N using a pre-processor
directive.

Figure 3: A C program to calculate the sum of the first N positive integers

Refer to the C program in Figure 3. In line 6, a while loop is placed, which is used to find the sum of N numbers and store it
in the variable ‘sum’, which is initially set to 0. We are counting from 1 to number N using the variable ‘index’ and adding
the value of the current number (index) at each step, updating the new sum in variable ‘sum’. In each step, the control
variable, index, is incremented by 1, and the loop will terminate when the index becomes greater than N. Thus, after N
steps,the loop terminates with the final and desired sum.

Line 9 could also be written as,

or

Line 11 is out of the while loop and will be executed just after the loop breaks. The loop is terminated when the variable
‘index’ becomes just greater than N, that is, N+1. Hence, the output of the program is:

Let us take a look at the value of variables in each iteration of the while loop in this program:

But what if we wanted the sum of the first 100 positive integers instead of 10? or 50? In an already written program, you can
simply change the value of ‘N’ in line 2 and run the program to get quick results. Following are a few examples with different
values of ‘N’:

Example 2 of a While Loop

Let us write a C program to ensure that the user enters a positive integer. That is:

• Every time a negative number or a zero is input, it gives an appropriate message

• Once a positive integer is entered, it prints the square of that number.

So, the program should keep on asking the user to input a positive number until the user inputs such a number. This
implies the usage of a loop in code. Now, we will use a while loop, as seen in the program in Figure 4.

The user enters a number for the first time in line 6. It is checked in line 7 if the number is positive before entering the loop.
If it is positive, do not enter the loop and print its square in line 12. But if it is a negative number, enter the loop, and ask for
the number again. Since the number itself is in the condition as a key variable, the loop will keep on asking for input (using
scanf) until a positive number is seen. And whenever a positive number is entered, the loop terminates, and the program
prints its square.
Figure 4: A C program to print the square of an input number, ensuring the user enters a positive number

The output of the program with certain inputs is:

‘While’ loop is very important in this scenario since we don’t know how many times the loop may run. Depending on the
user’s input, it may run 0 or 100, or countless times.

Nested While Loops

A while loop within a while loop is called a nested while loop. Following is the simplest syntax of a nested while loop:

Let’s take a look at one such example.

Example of a Nested While Loop


Assume there are N students in a class, and we want a C program to compute the average marks obtained by each student
in his/her three subjects. Thus, the program should take as input three subject scores for each student and print the average
marks obtained by each student.

Refer to the program in Figure 5. There are two loops, one outer and one inner. The outer loop in line 6 is to iterate over N
students, and for each student, there is the inner loop in line 11 to iterate over their subjects. Thus, the inner loop will run
3 times corresponding to the three subjects. The key variables are incremented to make sure the loops do not run infinitely.
For the outer loop, the key variable ‘index’ is incremented in line 21, which is the last line of the body of the outer ‘while’.
And for the inner loop ‘j’ is incremented in line 16, which is the last line of the body of the inner while.

Figure 5: A C program to compute the average marks of each student using a Nested While Loop

The following is a sample output of the program:


The Break and Continue Statements

We have seen a loop running for N iterations. But what if we decide in one of those iterations that we want to skip that
iteration or come out of the loop entirely? These are made possible with the help of continue and break statements.

• break: It forces control to come out of the immediately enclosing loop. We have already seen that the break
statement is used to come out of the Switch statement as well.

• continue: It skips the subsequent statements in the body of the immediately enclosing loop and proceeds to its
next iteration.

Example

Let us consider an example of writing a program to calculate the sum of positive numbers entered by the user such that:

• When a negative value is entered, ignore it; prompt the user to enter a positive integer.

• When zero is entered, terminate the loop, and finally,

• Display the sum of the positive numbers entered by the user.

Refer to the program code in Figure 6 that solves this problem and uses break and continue statements.

In line 6, we start an infinite loop since 1 means always true. But we don’t want an infinite loop; thus, we will use ‘break’ to
come out of the loop when 0 is entered. Thus, the program terminates the loop, prints the sum in line 16, and immediately
stops as soon as the user enters 0.
Figure 6: A C program to find the sum of all input positive numbers until 0 is entered

If a user enters any negative number, the condition in line 10 becomes true, and the ‘continue’ statement is executed. The
continue statement skips the rest of the loop in that iteration. Hence, lines 12 to 14 are not executed (and the sum is not
updated) when the user enters a negative number.

The following is a sample output of the execution of the program:

The user entered only three positive numbers: 1, 2, and 3, and the program printed the sum as 6, ignoring/skipping the
iterations with negative numbers and terminating as soon as the user entered 0.

Reading Summary

In this reading, you have learned the following:

• What is meant by a while loop, how it operates, and when to use it

• How to write a simple, compact program using a while loop

• The power of a while loop is that it can be used when the number of iterations is not known

• How to use the nested while loops


• How and when to use the continue and break statements within a loop
C Programming Practice Lab2
Assignment Brief:

Practice Lab 2_Question 1

1. Please find the output of a C program intended to print a pattern using integers. The pattern has N
rows, where N is input from the user. The first row begins with the number N and prints all numbers
from N to 1 in decreasing order. The second row starts with the number N-1, and so on. The last row
has the single number 1. The following is the inverted right triangle pattern for N=5:

The program provided in the file has a semantic error, and it is printing the following pattern for N = 5:

Semantic error is different from syntactic error. It means the C code will successfully compile without error
but will not produce the intended results. Modify the provided code to rectify the logical error.

2. Modify the program slightly to print the following pattern as well:

Practice Lab 2_Question 2

Use a While loop to write C programs that enable the user to enter a number and keep entering till

1. A positive number is entered

2. A negative number is entered

3. The number is either greater than 5 or less than -5

4. The number is greater than 20 and less than 100


[Note: Please use multi-line comment to comment out the previous subpart while doing the second subpart
onwards.]

Practice Lab 2_Question 3

Write a program to verify the Collatz conjecture. Here, the Collatz conjecture is asking whether repeating
two simple arithmetic operations will eventually transform every positive integer into 1. Consider the
following two operations:

When the number is odd:

f(odd) = 3*odd + 1

When the number is even:

f(even) = even/2

With these two transformations, any number should ultimately reach 1. For example, taking 10 will generate
the following series till 1:

10 5 16 8 4 2 1

This is just a conjecture and not a theorem. You need to verify if all the numbers from 1 to 1000000 follow
this conjecture. Write a program that loops through numbers from 1 to N, and then for each number, it loops
with either of the two operations until number 1 is seen.

If the loop runs infinitely, it means that the conjecture is not holding correctly for that number yet.

[Hint: Explore long long or unsigned long long data types for variable declaration.]

PracticeLab 2_Question_4

a) Complete the C program using a switch case to print the month's name for a corresponding integer the
user enters. The program has to ensure that the user enters integers only from 1 to 12. Consider the
following sample inputs and outputs:

Testcase1 Testcase2 Testcase3 Testcase4

Input - 13

Input - (-1)
Input - 3 Input - 11 Input - 2
Input - 16
Output - March Output - November Output - February
Input - 2

Output - February

b) Modify the above program to print all the months till December, starting from the month corresponding
to the integer input.
Testcase

Input - 9

Output - September

October

November

December
Reading: For Loop
Reading Objective:

In this reading, you will be introduced to the for loop in C and the various parts of a for loop. You will learn the ways to use
a for loop and how it is different from a while loop.

Main Reading Section:

For loop is a powerful iterative construct defined in C. The structure of a for loop is as follows:

Fig 1: Structure of a For Loop

A for loop consists of 4 parts:

1. Initialization

a. This part of the for loop is highlighted in red in Fig 1.

b. The initialization part of the for loop is executed exactly once, and it is when the for loop starts executing
for the first time.

c. Usually, it is used to initialize variables meant to count the number of iterations for which the for loop has
been executed.

d. It is worth noting that any variable declared inside the initialization part (or the body) of the for loop will
be deleted when the for loop exits (You will learn more about why this happens in Week 4, Lesson 2). Hence,
do not declare the variables that store the result of your computation inside the initialization of the for
loop.

2. Condition

a. This part of the for loop is highlighted in blue in Fig 1.

b. The condition part of the for loop is either blank or contains a Boolean expression. This expression is
evaluated after every iteration of the loop. If it is True, the loop is executed once again, and if it is False,
then the loop is terminated.

c. When the condition is left blank, the compiler interprets it as True, and thus, the loop is executed infinitely.

d. We can use logical or relational operators to make the condition part of the for loop.

3. Increment/Decrement

a. This part of the for loop is highlighted in gold/yellow in Fig 1.

b. It is executed after every iteration of the for loop, i.e., if the condition is True, the body of the loop is
executed, and after that, this variable’s value is incremented/decremented, and it is this new value used
for the next check with the condition. Usually, it is used to increment/decrement the value of the counter
variable.

4. Body of the for Loop


a. This part of the for loop is highlighted in grey in Fig 1.

b. This is the single statement or the block of statements that comes just after the for loop statement and is
executed repeatedly by the for loop as long as the condition holds true.

The Flow of Execution of the For Loop

The flow of execution of the for loop can be understood very easily from the flow chart in Fig 2.

Fig 2: Flow Chart Showing Execution of a For Loop

Firstly, the initialization part of the for loop is executed(usually initializing value to a variable). Then the condition is
evaluated to be True or False. If the condition is False, the loop is terminated. If the condition is True, then the body of the
loop is executed, and then the increment/decrement statement is executed. Then the condition is evaluated again, and
this continues until the condition finally evaluates to False.

Let’s see how the execution of the above program happens step by step.

1. Firstly, the variable i is declared in line 3, and its value is some garbage value.

2. Then the initialization statement in the for loop is executed, and the value of i becomes 1.

3. The condition statement is executed, i<=3 is checked, which evaluates to True because i = 1.

4. Now, the printf statement is executed, 1 is printed on the screen.

5. The increment statement is executed, i++, and the value of i becomes 2.

6. The condition statement is executed, i<=3 is checked, which evaluates to True because i = 2.

7. Now, the printf statement is executed, 2 is printed on the screen.


8. The increment statement is executed, i++, and the value of i becomes 3.

9. The condition statement is executed, i<=3 is checked, which evaluates to True because i = 3

10. Now, the printf statement is executed, 3 is printed on the screen.

11. The increment statement is executed, i++, and the value of i becomes 4.

12. Condition statement is executed, i<=3 is checked, which evaluates to False because i = 4

13. The loop terminates, and return 0 is executed, which terminates the entire program.

The output of the program is

In the above example, there is only one statement inside the for loop, so we can omit the use of curly brackets around that
statement and rewrite the above code as shown below.

However, if we happen to mistakenly add the return 0 statement inside the for loop, as shown below, then the execution
will be different, as explained below.

The steps of execution of the above program are as follows:

1. Firstly, the variable i is declared in line 3, and its value is some garbage value.

2. Then the initialization statement in the for loop is executed, and the value of i becomes 1.

3. The condition statement is executed, i<=3 is checked, which evaluates to True because i = 1.

4. Now, the printf statement is executed, 1 is printed on the screen

5. return 0 is executed and the entire program terminates.

The output of the program is


There is nothing right or wrong in using return 0 inside the for loop. It’s important that we understand that using return 0
inside the for loop would completely change how the program executes.

Various Forms of For Loop

There are some variations that we can do in a for loop. These are discussed below:

1. We can change the increment for the counter to any value in the increment statement. In fact, we can declare/initialize
multiple variables inside the for loop and increment/decrement multiple variables inside the increment section of the for
loop.

The following code is also correct

2. Instead of declaring the counter variable inside the for loop, we can declare it outside the loop also as shown below.
When the variable is declared outside the loop, the variable still exists after the termination of the loop, whereas if we had
declared the variable inside the initialization statement of the for loop, then the variable would have been deleted after the
termination of the loop.

3. Instead of incrementing the counter in the increment/decrement part of the for loop, we can also increment/decrement
the counter variable inside the body of the for loop.

4. We can omit either or all the 3 parts of the for loop, i.e., initialization, condition, and increment/decrement of the for
loop. For example, the below code executes an infinite loop.

5. We can also put an empty statement as the only statement in the body of the for loop. For example, the below code
consists of an empty statement in the body of the loop.
Examples of For Loops

Example 1: Print the sum of the first N odd numbers.

In the above program, the variable i is used for iterating over all natural numbers, the variable sum is used to store the sum
of odd numbers, and the counter variable count is used to store how many odd numbers have already been visited. We
make the loop execute for the value of i = 1, then 2, then 3, then 4, and so on. In case i is even, we skip that iteration of the
loop using a continue statement. Else, we add i to the sum variable and increment the count variable. When the count
becomes equal to N, we break out of the loop.
Note: When the continue statement is executed inside a for loop, the control resumes execution from the
increment/decrement step of the for loop. Break statement in for loop works similarly to break statement in while loop,
both terminate the loop and resume program execution from the next statement after the loop.

Example 2: Write a C program using a for loop to print the sum of all numbers from 1 to 1000 (including both 1
and 1000), which are multiples of 3 but not multiples of 4.

In the above program, we iterate over all the numbers from 1 to 1000. For each number, we check whether it is a multiple
of 3, and not a multiple of 4. If yes, it is added to the final sum. At last, we print the value of the sum.

Example 3: Write a C program to print the following pattern using for loops:

* *

* * *

* * * *

* * * * *

Take the value of N (the number of rows in the pattern) as input from the user.
In the above program, we take input from the user in a variable n. Then we declare two variables, i_row and i_col, to store
the index of the current row and column in which we are printing the pattern. We iterate over the number of rows using the
i_row variable. In each row, we print stars equal to the index of the row using the i_col variable, and then we print a new
line character. Note that the printf statement in line 9 is the only statement in the body of the for loop in line 8, and the for
loop in line 7 contains lines 8–10 in its body. The above code is an example of a nested for loop. The key takeaway should
be that the outer loop gets initialized first, then the condition is checked, and if it is True, program control goes into the
body of the loop. In this case, the body has another inner for loop, which again does its own initialization and checks its
condition. If the condition is True, it keeps executing the body of the inner loop as long as its condition remains True. Once
it becomes False, it comes out of the inner for loop, and now only the increment happens to the outer for loop, again checks
the condition and gives control to the inner for loop, and this continues until the outer for loop condition becomes false.

While vs. For Loop

Writing for loop in the form of a while loop

for loop

while loop

Fig 3: How to write a for loop in the form of a while loop

Writing while loop in the form of for loop


while loop

for loop

Fig 4: How to write a while loop in the form of a for loop

Figures 3 and 4 show how easy it is to write a for loop as a while loop, and a while loop as a for loop. But still, the structure
of both the loops is such that it is preferred to use the for loop when we already know the number of iterations for which
the loop will be executed, and the while loop is preferred when we do not have a fixed number of iterations of the loop, we
only have a termination condition. For example, if we want to keep asking the user to enter an integer till he/she enters a
valid integer, we have a termination condition, i.e., the user enters a valid integer, but there is no fixed number of iterations
of the loop, so we use a while loop. Whereas, to find the sum of the first N odd numbers, we know the number of iterations
of the loop, so we use a for loop.

Try to execute all the programs discussed in this reading in Coursera labs. You will learn best when you try to write and
execute these programs yourself.

Reading Summary:

In this reading, you have learned the following:

• The structure of a for loop and the flow of its execution

• How to write initialize, condition, and increment/decrement statements in a for loop

• Write programs using various forms of the for loop

• How to choose between a while and for loop for a given problem
C Programming Practice Lab3
Assignment Questions:

Practice Lab 3_Question 1

1. Compile a C program using the file included along with this question. The file contains a C program
that is intended to take a natural number as input from the user and print the sum of squares from
1 to N.

Please note that upon compiling this program, the compiler shows some errors. You need to resolve the
errors and successfully compile the program. You can check whether the program has successfully
compiled by testing it over the following test cases.

Testcase 1 Testcase 2 Testcase 3


Input – 10 Input – 17 Input – 100
Output – 385 Output – 1785 Output – 338350
b) Now, copy the above program into a new file named sum_of_cubes.c, and try to modify it to print the
sum of cubes from 1 to N.

Practice Lab 3_Question 2

The C program file attached with this question compiles successfully. Upon execution, the program enables
the user to enter a positive number and then prints some numbers before terminating.

Go through the program and find out the functionality of the above program. Pay special attention to the
use of continue and break statements inside the for loop and explore the use of these statements.

Practice Lab 3_Question 3

1. The C program in the file attached to this question intends to print an alternating pattern of 1’s and
0’s, based on the number of rows entered by the user as follows:

101010……

010101……

…………….

…………….

where the number entered by the user is the number of rows and columns to be printed in the pattern. Fill
in the blanks A, B, C, and D with appropriate expressions so that the program works correctly. Consider the
following test cases for your reference.

Testcase 1 Testcase 2

Input - 5
Input - 2
Output - 10101
Output - 10
01010
01
10101
Testcase 1 Testcase 2

01010

10101

2. Now, change what you entered in the blanks A, B, C, and D, so that the program prints a triangle of
alternating 0’s and 1’s instead of a square. Consider the following test cases for your reference.

Testcase 1 Testcase 2

Input - 5

Input - 3 Output -1

Output - 1 01

01 101

101 0101

10101

Practice Lab 3_Question 4

Write a C program that takes two positive numbers, a and b, as input from the user. If either of the numbers
is less than or equal to zero, then terminate the program there itself. Use a for loop to print the value of a
raised to the power b, that is, abab.

Below are some test cases for your reference. You can use the C program file attached with this question as
a template. The variables in this program are declared with unsigned long long int datatype because when
performing power operation, the result can be more than the maximum limit of value that an int variable
can store in C.

Testcase 1 Testcase 2 Testcase3

Input - 2 3 Input - 3 2 Input - 3 4

Output - 8 Output - 9 Output - 81


Solutions to the Practice Labs
PracticeLab1_Question1(a)
#include<stdio.h>
int main(){
int a, b, c;
printf("Enter three integers a, b, and c - ");
scanf(" %d %d %d",&a,&b,&c);

if(a==b){
if(a==c){

printf("True\n");
}
else{
printf("False\n");
}
}
else{

printf("False\n");
}

/*
//alternate soln
if(a==b && a==c){
printf("True\n");
}
else{
printf("False\n");
}
*/

return 0;
}
PracticeLab1_Question1(b)
#include<stdio.h>
int main(){
int a, b, c;
printf("Enter three integers a, b, and c - ");
scanf(" %d %d %d",&a,&b,&c);

if(a!=b && a!=c && b!=c){ printf("True\n");

}
else{
printf("False\n");
}

/*
//alternate soln
if(a==b)
printf("False\n");
if(a==c)
printf("False\n");
if(b==c)
printf("False\n");
else

printf("True\n");
*/

return 0;
}
PracticeLab1_Question2
#include<stdio.h>

int main(){

int marks;
printf("Please enter your marks: ");

scanf(" %d", &marks);

if(marks >=90)
printf("Your grade is Extraordinary");

else if(marks >=80)

printf("Your grade is Very Good");

else if(marks >=65) printf("Your


grade is Good");

else if(marks >=50)

printf("Your grade is Average");

else if(marks >=40)

printf("Your grade is Below Average"); else

printf("Fail, but it is not the end of the world.");

Practice Lab 2_Question 1-(1)part


#include<stdio.h>
int main()
{
/*
Current wrong output:
1
21
321
4321
54321

Intended Output, part a:


54321
4321
321
21
1
*/
int N;
printf("Enter a Number N: ");
scanf(" %d", &N);

int i = 1; // outer loop control variable


int j; // inner loop control variable
while(i <= N)
{
j = N-i+1;
while(j > 0)
{
printf("%d",j);
j--;
}
i++;
printf("\n");
}
}
Practice Lab 2_Question 1-(2)part
#include<stdio.h>
int main()
{
/*
part b
Intended Output:
54321
5432
543
54
5
*/
int N;
printf("Enter a Number N: "); scanf("
%d", &N);

i = 1; // outer loop control variable


while(i <= N)
{
j = N;
while(j >= i)
{
printf("%d",j);
j--;
}
i++;
printf("\n");
}
}
Practice Lab 2_Question 2
#include<stdio.h>
int main()
{

int N;
printf("Please Enter a number greater than 0: ");
scanf(" %d", &N);

while( N<=0)
{
printf("Please Enter a number greater than 0: ");
scanf(" %d", &N);
}
printf("Ok, number entered is greater than 0");

printf("\nPlease Enter a number lesser than 0: ");


scanf(" %d", &N);
while( N>=0)
{
printf("Please Enter a number lesser than 0: ");
scanf(" %d", &N);
}
printf("Ok, number entered is lesser than 0\n");

printf("\nPlease Enter a number greater than 5 OR less than -5: ");


scanf(" %d", &N);
while( N<=5 && N>=-5)
{ printf("Please Enter a number greater than 5 OR less than -5: ");

scanf(" %d", &N);


}
printf("Ok, number entered is greater than 5 OR less than -5\n");

printf("Please Enter a number greater than 20 AND less than 100: ");
scanf(" %d", &N);
while( N<=20 || N>=100)

{
printf("Please Enter a number greater than 20 AND less than 100:
");

scanf(" %d", &N);

printf("Ok, number entered is greater than 20 AND less than 100\n");

return 0;
}
Practice Lab 2_Question 3
#include<stdio.h>
int main(){

int end = 1000000; // 1. Try smaller number


int start = 1;
unsigned long long i = start, j = 0; // 2. Should we use just int here
if 'end' is too large ? Change datatype as mentioned in the ques

while(i <= end){

j = i;
// printf("%llu ", j); //uncomment if you want to see the series
// loop until the current number becomes 1
while(j!=1)
{

if(j%2 == 0){
j/=2;
}

else{
j = 3*j +1;
}

// printf("%llu ", j); //uncomment if you want to see the


series
}

// %d id for int, explore %llu


// printf("\n%llu \n\n", i);
printf("%llu ", i);
i++;
}
return 0;
}
PracticeLab 2_Question_4 (a)
#include<stdio.h>

int main(){

int month_number; // 1 to 12

printf("Please enter month number between 1 and 12: "); scanf("


%d", &month_number);

while(month_number <1 || month_number>12){

printf("Please enter Correct month number between 1 and 12: ");

scanf(" %d", &month_number);

switch(month_number){ case 1:
printf("January"); break;

case 2: printf("February"); break;

case 3: printf("March"); break;

case 4: printf("April"); break;

case 5: printf("May"); break;

case 6: printf("June"); break;

case 7: printf("July"); break; case


8: printf("August"); break;

case 9: printf("September"); break;

case 10: printf("October"); break;

case 11: printf("November"); break;

case 12: printf("December"); break;


}

return 0;

PracticeLab 2_Question_4 (b)


#include<stdio.h>

/*

sample inputs and outputs

input: 9

output: September

October

November
December

*/

int main(){

int month_number; // 1 to 12

printf("Please enter month number between 1 and 12: ");


scanf(" %d", &month_number);

while(month_number <1 || month_number>12){

printf("Please enter Correct month number between 1 and 12: ");

scanf(" %d", &month_number);

switch(month_number){
case 1: printf("January\n");
case 2: printf("February\n");

case 3: printf("March\n");

case 4: printf("April\n");

case 5: printf("May\n");

case 6: printf("June\n");
case 7: printf("July\n");

case 8: printf("August\n");

case 9: printf("September\n");

case 10: printf("October\n");

case 11: printf("November\n");

case 12: printf("December\n");

return 0;

Practice Lab 3_Question 1 (a)


#include<stdio.h>

int main(){

int N;

printf("Enter the value of N - ");

scanf(" %d", &N);

int sum = 0;

for(int i = 1; i<=N; ++i){

sum += i*i;

}
printf("Sum of squares from 1 to %d is %d\n", N, sum);

return 0;

Practice Lab 3_Question 1 (b)


#include<stdio.h>
int main(){

int N;
printf("Enter the value of N - ");
scanf(" %d", &N);

int sum = 0;

for(int i = 1; i<=N; ++i){


sum += i*i*i;
}

printf("Sum of cubes from 1 to %d is %d\n", N, sum);

return 0;
}
Practice Lab 3_Question 2
The program prompt asks the user to enter a positive number (although it doesn’t throw an error
if zero or a negative number is entered). Then if the number is greater than or equal to 79, it
simply exits. If number entered is less than 79 (even a negative number), the program prints all
the multiples of 5 in the range (start,79). ‘Start’ will be printed only if it is a multiple of 5
otherwise the next immediate multiple of 5 coming after 5 will be printed, similarly at the end as
last number 75 is printed which is the last multiple of 5 before 79.
#include<stdio.h>

int main(){

int num_rows;

printf("Enter the number of rows for which the pattern should be

printed - ");

scanf(" %d", &num_rows);

for(int i = 1; i<=num_rows ;++i){


for(int j = 1;j<=i; ++j){

printf("%d",(i+j+1)%2);

Practice Lab 3_Question 3 (1)


#include<stdio.h>
int main(){

int num_rows;

printf("Enter the number of rows for which the pattern should be

printed - ");

scanf(" %d", &num_rows);

for(int i = 1; i<=num_rows ;++i){ for(int


j = 1;j<=num_rows; ++j){

printf("%d",(i+j+1)%2);

printf("\n");

printf("\n");

return 0;
}

Practice Lab 3_Question 3 (2)


printf("\n");

printf("\n");

return 0;
}
Practice Lab 3_Question 4

#include<stdio.h>

int main(){

unsigned long long int a, b, ans = 1;


// Variables are declared as unsigned long long int so that they can

store big integers also

printf("Enter two positive integers a and b - ");

/* ENTER A STATEMENT TO TAKE a AND b AS INPUT FROM THE USER HERE*/


scanf(" %llu %llu", &a, &b);

/*WRITE A FOR LOOP TO MULTIPLY ans BY a FOR b NUMBER OF TIMES*/

for(unsigned long long int i = 1; i<=b; ++i)

ans *= a;

printf("%llu^%llu = %llu\n",a,b,ans);

return 0;

}
Control Structures—
7 Decision Making
Chapter 7, Sections 7.1-7.7, 7.10, 7.12, and 7.15.

WHAT TO LEARN

Significance of the control expression for making decisions.


Use of the if-else construct for two-way decision making.
Nesting of if-else constructs for multi-way decision making.
Indentation and pairing issues with nested constructs.
Using a conditional expression for simple two-way decisions.
Usefulness of the switch construct for testing equality.
Whether the goto statement deserves the ill-reputation that it has acquired.

7.1 DECISION-MAKING CONCEPTS


We are frequently confronted with the need to make a decision on what to do next. Decisions
enable devices or applications to behave in different ways depending on the data they encounter.
A decision can be two-way or multi-way, and one decision can lead to further decision making.
Here are some real-life situations that involve making decisions:
Blocking access to an ATM when a user fails to enter correct PIN in three attempts.
Switching off the geyser when the required temperature has been attained.
Setting the motor speed of a washing machine depending on the type of wash chosen.
Checking the residual battery charge of a cellphone to activate a notification LED.
All programming languages support at least one construct that supports selection, i.e., decision
making. This construct specifies what to do if one or more conditions are met, and also what to
do otherwise. Complex situations are handled by chaining together a number of such constructs.
As noted before, sequence, selection and repetition can solve any problem that is capable of being
described unambiguously.
208 Computer Fundamentals & C Programming

Execution of a decision-making construct interrupts the default sequential flow of program


execution. It causes control to branch to a different point in the program from where it should return
to resume execution from the next statement. This may not happen if this transfer of control is
caused by improper use of the GOTO statement. Structured programs, therefore, invoke procedures
or functions from where the return is automatic.

7.2 DECISION MAKING IN C


The C language recognizes that, depending on the nature and complexity, different situations need
different decision-making tools to handle them. Consequently, C addresses selection issues with
the following three constructs:
The if statement supported by the optional else keyword.
The switch statement.
The conditional operator (6.17).
The if-else construct, which is common to all languages, takes up most of the chapter space.
The construct handles complex decisions by combining a number of simple if-else constructs.
In certain situations, the switch is better suited for multi-way decision making. The conditional
operator (?:) uses one-liners to handle very simple tasks. You have seen it used in Section 6.17.
7.2.1 The Control Expression
Some C constructs like the if-else and while statements use the true or false value of a control
expression to determine control flow (6.15.1). They require this expression to be enclosed by
parentheses. The control expression is normally a relational or logical expression as shown by the
following examples:
if (amount > 20000)
if (answer == ‘Y’)
if (age >= 60 && sex == ‘F’)
But a control expression can also be any expression that returns any value including 0.
Also, because any non-zero value is true in C, the following expressions are also valid even if they
are neither relational nor logical:
if (total) True if total is greater than 0.
if (count++) True if count is greater than 0 before postfixing.
if (5) Always true.
When framing these expressions, you need to know the relative precedence and associativity of their
operators. Although discussed before, an essential subset of these operators is presented here for
quick reference (Table 7.1). Even though the % doesn’t belong to this subset, it has been included
because it appears in some of the programs in this chapter. The operators are arranged in decreasing
order of their precedence.
Note: The relational operators have a higher precedence than the logical ones. Also remember that
the AND operation is performed before OR.
Control Structures—Decision Making 209

TABLE 7.1 Operators Used for Decision Making (in order of precedence)
Operator(s) Signi cance Associativity
! Logical NOT R-L
% Modulus L-R
<, <=, >, >= Relational L-R
==, != Relational (Equality) L-R
&& Logical AND L-R
|| Logical OR L-R
?: (ternary operator) Conditional R-L

7.2.2 Compound Statement or Block


When using constructs like if and while, you’ll need to address a group of statements as a single
unit. This unit, which is flanked on either side by a matched pair of curly braces, is called a compound
statement. It is also known as a control block, or, simply, block. Here’s a compound statement drawn
from Section 6.5:
if ((last_number % 2) == 1) { Control block begins
printf(“Not an even number\n”);
return 0;
} Control block ends
The printf and return statements form a compound statement which is manipulated by if as
a single statement. Whenever two or more statements are affected by a control expression, they must
be enclosed in curly braces to form a compound statement. It is also permissible to use a compound
statement wherever a single statement is allowed. That is sometimes done to provide clarity.
It could be helpful to keep in mind that a block changes the scope (i.e. visibility) of a variable declared
inside it. Such a variable is not visible outside the block or in other blocks. These visibility issues
will be addressed in Chapter 11.

7.3 THE if STATEMENT


The if statement is the most commonly used selection construct in any programming language.
It has basically two forms—with and without the else keyword. The other forms discussed later
are derived from these two forms. The first form (Fig. 7.1) specifies the action if expression evaluates
to true. The syntax shows the usage for both simple and compound statements.

if (expression is true) if (expression is true) {


statement; statement1;
statement2;
...
}

FIGURE 7.1 The if Statement (Form 1)


210 Computer Fundamentals & C Programming

Execution of this statement begins by evaluating expression. If this value is true, then one or more
statements are executed. Curly braces are not required for a single statement but they are needed
for a compound statement. Here’s a simple example:
if (hours_left > 6) if syntax requires use of parentheses
rate = 25; Next statement should be indented
printf(“Refund amount = %f\n”, rate * amount);
Here, 25 is assigned to rate if hours_left is greater than 6. Note that the printf statement is
executed unconditionally and has nothing to do with the binary outcome of the control expression.
The difference in indentation reveals our true intention and is consistent with what the compiler
also sees.
A compound statement needs curly braces at its two ends. The following compound statement
comprises three simple ones:
if (amount > 10000) {
printf(“You can’t withdraw more than 10000 with this debit card\n”);
printf(“Key in an amount not exceeding 10000\n”);
scanf(“%d”, &amount); if ends at this ;
}
Note there is no semicolon following the } symbol. In the absence of curly braces, only the first
printf would have been executed conditionally.

Note: When used with a single statement, if ends at the semicolon used as statement terminator.
For a compound statement, if ends at the terminating ; of the last statement.

Caution: All control expressions used by the decision-making and loop constructs must be enclosed
within parentheses. The compiler will generate an error if you use if count > 0 or
while count++ <= MAX_COUNT.

7.4 average_integers.c: AVERAGE CALCULATING PROGRAM


Our first program (Program 7.1) calculates the average of two non-zero integers keyed in by a user,
but considers only their absolute values after validation. This means that the - sign is stripped off
from a negative integer. The task has been achieved by using one relational (<) and one logical
operator (||). The program is run thrice.
The program first assigns two integer variables, inta and intb, with input from the keyboard.
It then uses a logical expression as the control expression for if to find out if either of the integers is
zero. It’s good programming practice to first handle those situations that lead to errors. If program
flow moves beyond the first if statement, it means that we have two valid integers in our hands.
Once the validation test has been cleared successfully, the next step is to use two if statements to
compute the absolute values of the two integers. The simplest way to do that is to use the unary
- operator, which simply multiplies its operand by -1. We use the (float) cast to compute the
average of these two integers, because without it, the division would result in truncation of the
fractional part.
Control Structures—Decision Making 211

/* average_integers.c: Calculates average of the absolute values of two


integers. Quits program if one integer is zero. */
#include <stdio.h>
int main(void)
{
int inta, intb;
printf(“Enter two non-zero integers: “);
scanf(“%d %d”, &inta, &intb);
if (inta == 0 || intb == 0) {
printf(“At least one integer is zero\n”);
return 1; /* Good to return nonzero for invalid entry */
}
/* Assigning absolute values to inta and intb */
if (inta < 0)
inta = -inta; /* Using the - unary operator */
if (intb < 0)
intb = -intb;
/* If we have come here, both inta and intb must be positive */
printf(“Average of absolute value of the integers = %.2f\n”,
(float) (inta + intb) / 2);
return 0;
}

PROGRAM 7.1: average_integers.c


Enter two non-zero integers: 7 0 First invocation
At least one integer is zero
Enter two non-zero integers: 5 10 Second invocation
Average of absolute value of the integers = 7.50
Enter two non-zero integers: -13 8 Third invocation
Average of absolute value of the integers = 10.50

PROGRAM OUTPUT: average_integers.c


This program features two return statements, and interestingly, the first one returns the value 1
instead of 0. Does returning 1 instead of 0 make any difference? Yes, it does but only if the next
program takes advantage of this value. It will take a while before you work with cooperative programs,
but it’s good to know that this is the way return was designed to be used (see Inset—How It Works).
Note that we didn’t use separate validation routines for the two integers inta and intb. Instead,
we used the following logical expression:
if (inta == 0 || intb == 0)
This is acceptable for this introductory program since we don’t need to know, in the event of
failure, who the offender is. Sometimes, we need to pinpoint the cause of failure, in which case
you need to use two relational expressions with individual if statements (as if (int a == 0) and
if (intb == 0)), and specify separate paths for each of them.
212 Computer Fundamentals & C Programming

HOW IT WORKS: Why return 1 instead of return 0?


The return statement, when placed in the body of main, terminates a program and transmits
a value to its caller. The caller in most cases is the operating system which saves this value until the
next program is run. In applications comprising multiple programs that depend on one another, it
is often necessary for one program to know whether a previous program has completed successful
execution. The return value provides this information. On operating systems like UNIX and
Linux, a return value of 0 signifies success. On these systems, a programmer uses a non-zero
value with return to indicate failure!

7.5 if-else: TWO-WAY BRANCHING


None of the if statements of the previous program, average_integers.c, explicitly specifies the action
to take when its control expression evaluates to false. The default action is to proceed sequentially
and move on to the next statement following the if statement. The second form of the if construct
permits two-way branching using the else keyword (Fig. 7.2). The statements following else are
executed when the control expression fails.

if (expression is true) if (expression is true) {


statement; statements;
else ...
statement; }
else {
statements;
...
}

FIGURE 7.2 The if-else Statement (Form 2)


Consider a store that offers a weekend discount of 10% for purchases over Rs 1000, and 5% otherwise.
The code that computes the amount payable can be represented in this manner:
if (total > 1000) total previously declared as float
total = total * 0.90; Or total *= 0.90;
else
total = total * 0.95; Or total *= 0.95;
printf(“The total amount payable is Rs %.2f\n”, total);
Because of proper indentation, it doesn’t take any effort to know that the printf statement is not
affected by the else clause and is executed unconditionally.

7.6 leap_year_check.c: PROGRAM TO CHECK LEAP YEAR


Let’s now use both forms of the if statement in our next program (Program 7.2). This program
subjects a user-input integer to a leap year check. A leap year is divisible by 4, so year % 4 is 0 for
a leap year. This program ignores the special check made for years that signify a change of century
(like 1800, 1900, etc.) but it indicates when the next leap year will occur.
Control Structures—Decision Making 213

/* leap_year_check.c: Checks for leap year using the if-else structure.


Doesn’t make the check for century. */
#include <stdio.h>
int main(void)
{
short year, years_left;
printf(“Enter year for leap year check: “);
scanf(“%hd”, &year);
if (year < 0) {
printf(“Invalid year\n”);
return 1;
}
if (year % 4 == 0) /* No parentheses required */
printf(“Year %hd is a leap year.\n”
“Next leap year is after 4 years.\n”, year);
else {
years_left = 4 - year % 4; /* No parentheses required */
printf(“Year %hd is not a leap year.\n”
“Next leap year is %hd.\n”, year, year + years_left);
}
return 0;
}

PROGRAM 7.2: leap_year_check.c


Enter year for leap year check: 2009 First invocation
Year 2009 is not a leap year.
Next leap year is 2012.

Enter year for leap year check: 2012 Second invocation


Year 2012 is a leap year.
Next leap year is after 4 years.

Enter year for leap year check: 1900 Third invocation


Year 1900 is a leap year. This is incorrect
Next leap year is after 4 years.

PROGRAM OUTPUT: leap_year_check.c

We have not used parentheses in the expression year % 4 == 0 because the % has a higher precedence
than == (Table 7.1). The modulus operation is thus performed first. The else part contains the code
for handling a non-leap year. Parentheses are left out in the expression 4 - year % 4 also because
the % has a higher priority than -.
In the last two printf statements, the first argument actually comprises two concatenated strings.
C allows this concatenation (9.12.2), but note that the combined string is actually a single argument
even if the strings are located on two physical lines.
214 Computer Fundamentals & C Programming

Tip: Always look for potential errors right at the beginning of the program. It’s pointless proceeding
with program execution with erroneous data. Use the if statements to check for them and then
use return with specific values to prematurely terminate the program.

7.7 MULTI-WAY BRANCHING WITH if-else-if ...


Nothing prevents us from using a second if statement (in any form) inside another if statement.
When the second if is inducted in the “if ” section, we have a nested if structure (if-if-else).
When the same is done in the “else” section, we have a ladder (if-else-if) instead. Both nested
and ladder structures belong to the domain of multi-way decision making. The nested form is
discussed in Section 7.10. The ladder structure is discussed here (Fig. 7.3).

if (expression is true) if (expression is true) {


statement; statements;
else if (expression is true) ...
statement; }
... else if (expression is true) {
else statements;
statement; ...
}
...
else {
statements;
...
}

FIGURE 7.3 The Ladder if-else-if Statement (Form 3)


The test on the control expression is carried out sequentially from the top downwards, and
terminates as soon as it evaluates to true. The last else statement takes care of “the rest” and is
executed only when all of the previous tests fail. C imposes no restriction on the number of else-if
sections that can be used in this manner.
Let’s now use this construct to compute the tariff for the 4G Internet service offered by a mobile
operator. The tariff is Rs 255 for up to 1 GB, Rs 455 for up to 2 GB, Rs 755 for up to 4 GB and Rs 995
thereafter (simplified from live data). This logic is easily implemented using the if-else-if form:
if (usage <= 1)
tariff = 255;
else if (usage <= 2)
tariff = 455;
else if (usage <= 4)
tariff = 755;
else “The rest”
tariff = 995;
This chained construct uses three different relational expressions that enable the variable tariff
to have one of four possible values. When using constructs that implement multi-way branching,
Control Structures—Decision Making 215

you must ensure that the expressions are mutually exclusive. This means that two expressions must
never evaluate to true in a single traversal of the structure.

/* irctc_refund.c: Computes refund amount on ticket cancellation. Negative


value for hours_left is valid. Maximum price of ticket = Rs 10,000 */
#include <stdio.h>
int main(void)
{
short hours_left, rate;
float ticket_price, refund_amount;
printf(“Enter price of ticket: “);
scanf(“%f”, &ticket_price);
printf(“Number of hours before train departure: “);
scanf(“%hd”, &hours_left);
if (ticket_price <= 0) {
printf(“Price can’t be negative\n”);
return 1;
}
else if (ticket_price > 10000) {
printf(“Price can’t exceed Rs 10,000.\n”);
return 1;
}
else if (hours_left > 48)
rate = 0;
else if (hours_left > 6)
rate = 25;
else if (hours_left > -2) /* 2 hours after departure */
rate = 50;
else
rate = 100; /* No refund */
refund_amount = ticket_price * (100 - rate) / 100;
printf(“Refund amount = %.2f\n”, refund_amount);
return 0;
}

PROGRAM 7.3: irctc_refund.c


Enter price of ticket: 11000
Number of hours before train departure: 45
Price can’t exceed Rs 10,000.

Enter price of ticket: 1000


Number of hours before train departure: 4
Refund amount = 500.00

Enter price of ticket: 1000


Number of hours before train departure: -1
Refund amount = 500.00

PROGRAM OUTPUT: irctc_refund.c


218 Computer Fundamentals & C Programming

Note: The return 0; statement at the bottom of the program is never executed because the program
exits through the paths provided by the other return statements placed in the inner ladder.
This return statement placed at the bottom should be removed.

7.10 MULTI-WAY BRANCHING WITH NESTED if (if-if-else)


We have seen the induction of an if-else section in the else part of the main if statement.
When the same section is inducted in the main if part itself (Fig. 7.4), we have a nested if (if-if-else)
structure. Like with the ladder, this is not a separate feature of the language but is derived from
the if syntax.

if (expression is true)
if (expression is true)
if (expression is true)
statement;
...
else
statement;
else
statement;
else
statement;
FIGURE 7.4 The Nested if-if-else Statement (Form 4)

The figure shows the indentation you must adopt when using nested if structures. Without proper
indentation, it is easy to visually pair an if with the wrong else. The pairing scheme is quite simple:
The innermost if pairs with the innermost else, the immediate outer if pairs with the immediate
outer else, and so on until the outermost if pairs with the outermost else. The symmetry breaks
down when you drop an else for one of the ifs, but more of that soon.
Let’s look at a simple example that demonstrates the usefulness of this derived construct. Consider
the need to print the sum of three positive integers obtained from user input, but only after validation.
Here’s the code snippet that handles the logic:
if (a > 0)
if (b > 0)
if (c > 0)
/* Cleared the validity check */
printf(“Sum of three integers = %d\n”, a + b + c);
else
printf(“c is not a positive integer\n”);
else
printf(“b is not a positive integer\n”);
else
printf(“a is not a positive integer\n”);
Control Structures—Decision Making 219

When code is presented in this way, there should be no problem in pairing an if with its else.
The first three if statements represent the AND condition (a > 0 && b > 0 && c > 0), so the first
printf signifies the action to take when all three expressions evaluate to true. The other printf
statements are associated with the else clauses and they tell us what happens when each of the
variables is less than or equal to zero.
In some cases, we need to drop some of the else keywords, or even all of them. The latter is easy
to handle, but we have to handle the other situations with care. Before we do that, let’s have a look
at a program which contains a symmetric nested if structure.

7.11 right_angle_check.c: PROGRAM TO CHECK PYTHAGORAS’ THEOREM


Our knowledge of geometry tells us that the square of the hypotenuse of a right-angled triangle
is equal to the sum of the squares of the other two sides. This means that given three numbers a,
b and c, if a * a + b * b is equal to c * c, then a right-angled triangle can be formed with these
three sides, where the largest side represents the hypotenuse.
Program 7.5 runs an infinite while loop to accept three integers (a, b and c) from the keyboard.
It allows a user multiple chances to key in non-zero values, and then tries all possible combinations
of a, b and c to see if one of them fits the formula. It’s good to know how nested if constructs work
even though a better and more intuitive approach would be to use the if-else-if ladder.
The program first validates the integers for non-zero values; it terminates when all of them are zero.
After successful validation, control moves to the nested construct comprising four if statements.
The first if (the outermost one) allows only positive integers to pass through. If this test fails,
the matching else at the bottom uses printf to display a non-specific error message. Because this
entire construct runs in a loop, control then moves up to the beginning of while to accept the next
set of integers.
Once the three integers pass the second validation test, the program tries out three possible
combinations (a-b-c, a-c-b and b-c-a) that will not fit the theorem. In other words, if all three
relational expressions evaluate to true, then a right-angled triangle cannot be formed with any
combination of these integers. If one of the expressions fails, its corresponding else section uses
printf to tell us that a right-angled triangle can be formed, and it also tells us what the hypotenuse is.

7.12 PAIRING ISSUES WITH if-if-else NESTED CONSTRUCTS


We didn’t face any pairing problems in the previous program (Program 7.5) because the nested if
construct had an equal number of if and else clauses. It was easy to see which else paired with
which if. Sometimes, program logic may not need one or more else clauses. A small code fragment
having an else missing is shown in Figure 7.5 with both misleading and correct indentation.
The indentation of the else part in the form shown on the left is deceptive. It gives the impression
that the else is paired with the first if, which it is not. The form on the right correctly shows the
else paired with the inner if. Even though the compiler doesn’t look at indentation, we can’t
afford to ignore it.
220 Computer Fundamentals & C Programming

/* right_angle_check.c: Uses nested if statements. Doesn’t catch


exact cause of failure. Formula used: a*a + b*b = c*c */
#include <stdio.h>
int main(void)
{
short a, b, c;
while (1) {
printf(“Enter three integers a b c: “);
scanf(“%hd %hd %hd”, &a, &b, &c);
if (a == 0 && b == 0 && c == 0) {
printf(“All values zero. Quitting ...\n”);
return 1;
}
/* RAT represents right-angled triangle */
if (a > 0 && b > 0 && c > 0)
if (a * a + b * b != c * c) /* Whether RAT can’t be formed */
if (a * a + c * c != b * b) /* Ditto */
if (b * b + c * c != a * a) /* Ditto */
printf(“RAT not possible.\n”);
else
printf(“RAT with %hd as hypotenuse.\n”, a);
else
printf(“RAT with %hd as hypotenuse.\n”, b);
else
printf(“RAT with %hd as hypotenuse.\n”, c);
else
printf(“At least one input is invalid.\n”);
}
}

PROGRAM 7.5: right_angle_check.c


Enter three integers a b c: 5 0 9
At least one input is invalid.
Enter three integers a b c: 3 5 4
RAT with 5 as hypotenuse.
Enter three integers a b c: 5 2 6
RAT not possible.
Enter three integers a b c: 6 10 8
RAT with 10 as hypotenuse.
Enter three integers a b c: 0 0 0
All values zero. Quitting ...

PROGRAM OUTPUT: right_angle_check.c


Control Structures—Decision Making 221

Misleading Indentation Correct Indentation


if (a > 0) if (a > 0)
if (b > 0) if (b > 0)
valid_int = ‘y’; valid_int = ‘y’;
else else
valid_int = ‘n’; valid_int = ‘n’;

FIGURE 7.5 Code with a Missing else


But what if you actually wanted an else part for the first if and not for the second? There are
two solutions to this problem. One is to use curly braces to eliminate ambiguity. The other is to
use a dummy else containing a solitary semicolon. The ; placed by itself on a line signifies a null
statement—one that does nothing. The two solutions are placed side-by-side in Figure 7.6 for you
to choose the one you prefer. The curly brace solution is the one that is preferred by most.

Null Command Solution Curly Brace Solution


if (a > 0) if (a > 0) {
if (b > 0) if (b > 0)
valid_int = ‘y’; valid_int = ‘y’;
else }
; else
else printf(“a is <= 0\n”);
printf(“a is <= 0\n”);

FIGURE 7.6 Two Solutions for Missing else

7.13 leap_year_check2.c: PROGRAM USING THE if-if-else STRUCTURE


Let’s conclude our discussions on the if statement by improving a previous leap year checking
program. The revised version (Program 7.6) includes the special check for the “century” years
(like 1900, 2000, etc.). We use a symmetric nested if-if-else structure where there is no else
missing. The use of the variable is_leap_year should be an eye-opener because of the remarkable
way it has been used.
You are aware that all years that signify a change of century are not leap years even though they
are all divisible by 4. Years like 1900 and 2000 have to be divisible by 400, which means that 2000
is a leap year but 1900 is not. To identify a leap year, we must follow this course of action:
1. Check whether the number is divisible by 4. If it is not, the number is not a leap year and the
program terminates.
2. If the previous check succeeds, divide the number by 100 to determine whether the year
represents a change of century. If it is not, then the number represents a leap year and the
program terminates.
3. If we have come here, it means that we are dealing with a century year. Now check whether
the number is divisible by 400. If it is, the number is a leap year and the program terminates.
4. If we have come here, the number doesn’t represent a leap year.
224 Computer Fundamentals & C Programming

The combination of the three expressions itself constitutes a larger expression—the conditional
expression. This expression has the value exp2 if exp1 evaluates to true, and exp3 otherwise. Even if
this construct has been discussed in Chapter 6 as a special form of expression, it is also important
for decision making. In fact, this operator easily replaces those if-else statements that merely set a
variable. Consider the following if statement which is shown side-by-side with the ternary version:
if-else Version Equivalent Conditional Expression
if (total > 1000) rate = total > 1000 ? 0.90 : 0.95;
rate = 0.90;
else
rate = 0.95;

Using a conditional expression, the leap year problem becomes even shorter. Simply set the variable
is_leap_year to ‘y’ or ‘n’ depending on the value of a logical expression. Use parentheses if they
help to provide clarity:
char is_leap_year;
is_leap_year = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
? ‘y’ : ‘n’;
Now, printf and scanf are also expressions because both return a value, so can we use printf in
this way?
count = inta < 0 ? printf(“Invalid value\n”) : printf(“Valid integer\n”);

Yes, this is a valid statement. In the process of evaluation of printf, the side effect (printing) also
shows up. The variable count stores the return value of printf, i.e., the number of characters printed.
Note that the ; at the end is the terminator of the assignment statement. The first printf doesn’t
have a terminator, so why should the second printf have one?

7.15 THE switch STATEMENT


We use the equality test so often that C has a special construct to handle it. It’s the switch statement
which implements multi-way branching with a compact and structured construct. If you need to
match 10 integer values for making 10 different decisions, use switch whose syntax is shown in
Figure 7.7.

switch (exp) {
case value1 : statements1;
break;
case value2 : statements2;
break;
...
default : statements;
}

FIGURE 7.7 Syntax of switch Statement


Control Structures—Decision Making 225

switch first evaluates exp which must be an integer or character variable or constant. It next tries to
match exp with value1. If the match succeeds, switch executes one or more statements represented
by statements1. If the match fails, switch attempts to match exp with value2 and so on until control
moves to the keyword default (if present). This option is thus invoked when all previous matching
operations fail. For this purpose, switch is assisted by three keywords—case, break and default.
The entire set of options and their associated action are enclosed in curly braces.
Every case represents an option that specifies the statements to be executed in case matching is
successful. In most cases, a case is followed by break. When it is encountered, further matching is
halted and control moves past the end of switch to the statement following the }. In the absence of
break, however, control “falls through” to the next case option for the next break, or past the end
of switch if it doesn’t find one. Program 7.9 clearly shows us why switch was designed to behave
in this unusual manner.
Here’s a simple code fragment that shows the working of switch. It validates a user-input integer
for the values 1 and 2:
printf(“Enter a 1 or 2: “);
scanf(“%d”, &response);
switch (response) {
case 1: printf(“You entered 1\n”);
break;
case 2: printf(“You entered 2\n”);
break;
default: printf(“Invalid option\n”); No break required ...
} ... will break anyway
The default keyword (if present) doesn’t need a break since control will anyway break out of the
construct at the point where it could have occurred. However, switch operates with the following
restrictions:
exp can be any expression as long as it evaluates to an integer or character constant like
1, 2 or ‘y’.
The labels value1, value2, etc. can only be integer or character constants or constant expressions
(like 3 + 5). Floating point values and variables are not permitted here.
The label is always followed by a colon, and the statements associated with it must be
terminated by semicolons.
The default label is optional, but if present, is usually placed as the last option (not
mandatory though).
Two or more labels can have one break statement. This feature gives switch the power of the
logical OR operator.
It is permissible to have an empty case (one without any statement).
Can a relational expression be used as exp? Sure it can, but the evaluation would yield only one
of two values. You can use switch to match the labels 0 and 1, but then if is ideally suited for this
task. Program 7.9 uses a relational expression for exp.
8 Control Structures—Loops

Chapter 8, Sections 8.1-8.3, and 8.5-8.6.


WHAT TO LEARN

Principles of entry and exit in loops.


Concept of the key variable used in a control expression and loop body.
Working of the while construct as an entry-controlled loop.
Working of the do-while construct as an exit-controlled loop.
Significance of three expressions for controlling iterations in a for loop.
Nesting of all loops.
Interrupting loop processing with break and continue.

8.1 LOOPING BASICS


Many activities that we perform daily are repetitive in nature. Repetition makes us breathe, moves
automobiles and drives production lines. In most cases, the repetition framework also includes the
mechanism to halt itself. Repetition also extends to areas that computer programs handle with ease.
Consider the following tasks that involve repetition:
Computation of average—requires repeated addition.
Computation of the power of a number—requires repeated multiplication.
Initialization of an array—requires repeated assignment.
Decimal-to-binary conversion—requires repeated division.
Drawing a line—requires repeated printing of a character.
In addition to decision making, a language must be able to repeatedly execute a a set of statements.
Constructs that do that are known as loops, and C offers three of them—while, do-while and
for. Before we get down to the specifics, we need to know in generic terms how a loop works.
Figure 8.1 shows the working of a generic loop which here prints the entire English alphabet in
uppercase.
Control Structures—Loops 237

1. Initialize key variable (x) outside loop.


(say, x = 65)
2. Test control expression at loop beginning.
(Is x <= 91?)
3. If answer to 2 is yes, perform steps 4 to 6.
Beginning of Loop
4. Execute loop body to print one letter.
(printf(“%c “, x))
5. Assign the next higher value to key variable.
(x = x + 1)
6. Go back to 2 to re-test control expression.
End of loop
7. If answer to 2 is no (i.e. x > 91), continue execution after loop
(say, printf(“\nJob over\n”)).

FIGURE 8.1 How a Loop Works

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Job over

Output of Pseudo-Code

Before a loop begins, a key variable is initialized (1). This variable is used to evaluate a control
expression (2). If the evaluation is true (3), the loop body is executed (4 to 6), otherwise control moves
to the next statement after the loop (6). The key variable is then changed in the loop body (5) before
control moves back to the beginning of the loop (2). The control expression is now re-checked (2)
before the next iteration (traversal or repeat) can begin. If the loop is designed to run a finite
number of times, the control expression will eventually turn false and cause the loop to terminate.
Even though all of the three loops offered by C broadly conform to the above scheme, there are
minor differences between them. First, the initialization may be done in the loop construct itself
(for loop) instead of being done outside. Second, the control expression may be evaluated at
the end (do-while loop) instead of at the beginning. Finally, a control expression can’t prevent
a do-while loop from performing at least one iteration even though it can prevent the other loops
from running at all.
A loop iteration can either be suspended (to start a new one) or terminated altogether without
re-testing the control expression. This is achieved with the continue and break keywords that can
be invoked anywhere in the loop body. You have already used this feature in one program of the
previous chapter, so you’ll find it a convenient alternative to the traditional method of using the
control expression to determine loop continuity.
238 Computer Fundamentals & C Programming

A loop is generally designed to execute a finite number of times, but many applications need
a loop to run all the time. Such loops are known as infinite loops. We have seen one of them in the
previous chapter (while (1)). Many Internet services use infinite loops to check whether a mail
has been received or a user has keyed in text in a messaging application. However, even infinite
loops can be terminated by other means.

8.2 THE while LOOP


You have seen the while loop at work in some of the previous chapters. The syntax, featuring both
single and compound statements in the loop body, is shown in Figure 8.2. Don’t forget to enclose
the control expression in parentheses.

while (expression is true) while (expression is true) {


statement; statement1;
statement2;
...
}

FIGURE 8.2 The while Statement

The while loop is virtually identical to the generic loop that was depicted in Figure 8.1. An expression
containing a key variable is evaluated to true or false before loop iteration. If false, the loop is not
entered at all, otherwise the body of the loop is executed. After execution, control reverts to the top to
test expression that will determine whether the next iteration can begin. Since the key variable changes
with every iteration, eventually its value causes expression to turn false and terminate the loop.

8.2.1 while_intro.c: An Introductory Program


Let’s examine a small program (Program 8.1) that computes the sum of the first 10 integers and
prints both a progressive and final sum. The key variable here is i, which is used three times in
a while loop. Since this loop is finite, i will eventually exceed 10. The control expression will then
turn false and prevent loop re-entry. The progressive sum is printed inside the loop and the final
sum outside.
The while loop is an entry-controlled loop, which means that loop entry is not guaranteed. If the
control expression is false to begin with, then the loop is not entered at all. This is also true of
the for loop, but not the do-while loop which is an exit-controlled loop. The control expression in
a do-while loop is tested at the end of the loop, which means that initial loop entry is guaranteed
but not its subsequent iterations.

8.2.2 The Control Expression


The control expression is the nerve center of any loop. C lets you use any expression (relational,
logical or constant) as long as it evaluates to an integer value. Any control expression used with
if in Chapter 7 will work in identical manner with all loops. Here are some examples of usage of
these expressions:
Control Structures—Loops 239

/* while_intro.c: A simple while loop that prints the sum of the


first 10 integers. Also prints progressive sum. */
#include <stdio.h>
#define NO_OF_INTEGERS 10
int main(void)
{
short i = 1; /* Key variable initialized */
short sum = 0;
printf(“Progressive sum shown below:\n”);

while (i <= NO_OF_INTEGERS) { /* Key variable tested */


sum += i;
i++; /* Key variable updated */
printf(“%hd “, sum);
}
/* Loop has terminated */
printf(“\nSum of first %d integers = %hd\n”, NO_OF_INTEGERS, sum);
return 0;
}

PROGRAM 8.1: while_intro.c


Progressive sum shown below:
1 3 6 10 15 21 28 36 45 55
Sum of first 10 integers = 55

PROGRAM OUTPUT: while_intro.c

while (reply == ‘y’) Relational expression


while (++num <= power) As above, but with a difference
while (base > 0 && power >= 0) Logical expression
while (quot) Expression is a variable
while (1) Expression is a constant
while (printf(“Enter an integer: “)) Expression is a function
The second expression is an eye-opener; it contains the updating of the key variable (num) in the
control expression itself. The statement while (quot) is the same as while (quot > 0). By the same
token, while (1) is always true. The last expression using printf is also true because it evaluates
to the number of characters printed.

8.2.3 Updating the Key Variable in the Control Expression


As discussed before, most loop applications are based on the initialize-test expression-update
key variable model:
Initialize the key variable.
Test it in the control expression.
Update the key variable in the loop body.
240 Computer Fundamentals & C Programming

However, we can sometimes update the key variable in the control expression itself and get rid of one
statement in the loop body. The following code related to the program while_intro.c will also work:
short i = 0, sum = 0; i initialized to 0 not 1
while (++i <= 10) { ++i instead of i
sum += i;
printf(“%hd “, sum);
}
printf(“\nSum of first 10 integers = %hd\n”, sum); Prints 55
The updating of the key variable i has now been moved to the control expression, but this movement
needs the initial value of i to be changed as well.
Note: When you change the key variable in the control expression to one having prefix or postfix
operators, you also need to change either the relational operator (say, > to >=) or the initial value of
the key variable.

8.3 THREE PROGRAMS USING while


Before we continue exploring this loop in C, let’s examine three simple programs that use simple
control expressions. All of these programs are revised in subsequent chapters to use arrays and
functions. Two of the later versions also use the recursive technique in contrast to the iterative
approach of loops that is pursued here.
8.3.1 factorial.c: Determining the Factorial of a Number
The factorial of a positive integer n (denoted by !n) is the product of all positive integers less than
or equal to n. Thus, !4 = 4 ¥ 3 ¥ 2 ¥ 1 = 24. Program 8.2 uses a while loop to compute the factorial
of an integer supplied by the user. Section 11.13.1 shows an alternative way of solving the same
problem using a function.
/* factorial.c: Program to determine factorial of a number
without using a function. */
#include <stdio.h>
int main(void)
{
short num1, num2;
unsigned int factorial = 1; /* unsigned doubles the maximum value */
printf(“Factorial of which number? “);
scanf(“%hd”, &num1);
num2 = num1; /* Saving num1 before it changes */
while (num1 >= 1) {
factorial *= num1;
num1--;
}
printf(“Factorial of %hd = %d\n”, num2, factorial);
return 0;
}

PROGRAM 8.2: factorial.c


Control Structures—Loops 241

Factorial of which number? 4


Factorial of 4 = 24
Factorial of which number? 6
Factorial of 6 = 720
Factorial of which number? 1
Factorial of 1 = 1

PROGRAM OUTPUT: factorial.c


8.3.2 extract_digits.c: Program to Reverse Digits of an Integer
Program 8.3 performs the dual task of reversing the digits of an integer and printing their sum.
Using 10 as the divisor, each digit is extracted from the right using the / and % operators. The program
has the demerit of not saving the digits, so you can’t perform any other task with them once printing
is complete.
/* extract_digits.c: Reverses the digits of an integer. Prints and sums
the digits using the % and / operators. */
#include <stdio.h>
int main(void)
{
unsigned int number;
short last_digit, sum = 0;

printf(“Key in an integer: “);


scanf(“%d”, &number);

while (number != 0) {
last_digit = number % 10; /* Extracts last digit */
number = number / 10; /* Removes last digit */
sum += last_digit;
printf(“%hd “, last_digit);
}
printf(“\nSum of digits: %hd\n”, sum);
return 0;
}

PROGRAM 8.3: extract_digits.c


Key in an integer: 13569
9 6 5 3 1
Sum of digits: 24

PROGRAM OUTPUT: extract_digits.c


In every iteration of the while loop, the / operation removes the last digit and the % operation saves
this digit. This is how the values of the variables last_digit and number change with every loop
iteration:
242 Computer Fundamentals & C Programming

Iteration 1 Iteration 2 Iteration 3 Iteration 4 Iteration 5


last_digit 9 6 5 3 1
number 1356 135 13 1 0

The program is inflexible; we can’t use this technique to print the digits in the sequence they
occur in the number. We need to save every digit, not with five variables, but with an array of five
elements. We’ll revisit the program in Section 10.7.1.

8.3.3 fibonacci_ite.c: Printing and Summing the Fibonacci Numbers


Every student of computer science would have encountered the Fibonacci sequence of numbers
at some point in their learning curve. In this sequence, which starts with the values 0 and 1, each
subsequent term is the sum of the previous two. This relationship can be seen in the first 12 terms
of this sequence:
0 1 1 2 3 5 8 13 21 34 55 89

Here, 55 is the sum of its two predecessors, 34 and 21, while 21 is the sum of 13 and 8. Program
8.4 prints and sums n terms of the sequence where n is provided by the user. The program employs
a while loop that runs n times. In each iteration, the sum of the variables prev1 and prev2 are saved
in sum and printed. Note that the first two terms are generated from the logic which has not treated
them in an exceptional manner. This has been made possible by careful selection of the initialized
values of four variables.
/* fibonacci_ite.c: Uses the iterative technique to generate terms of
the Fibonacci series and compute the sum of terms. */
#include <stdio.h>
int main(void)
{
short num;
unsigned long prev1 = 0, prev2 = 0, sum = 1, sum_terms = 0;
short i = 0;
printf(“Enter last term of series: “);
scanf(“%hd”,&num);
while (i++ < num) {
printf(“%ld “, sum); /* Prints each term */
sum_terms += sum; /* Sums all terms */
prev1 = prev2;
prev2 = sum;
sum = prev1 + prev2; /* Sums previous two terms */
}
printf(“\nSum of first %hd terms = %ld\n”, num, sum_terms);
return 0;
}

PROGRAM 8.4: fibonacci_ite.c


Control Structures—Loops 243

Enter last term of series: 15


1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
Sum of first 15 terms = 1596

PROGRAM OUTPUT: fibonacci_ite.c

The Fibonacci sequence can also be generated by using a recursive function, i.e., a function that
calls itself repeatedly. Section 11.14.3 examines this function and its demerits compared to the
iterative approach used here.
8.4 LOADING THE CONTROL EXPRESSION
Repetition creates opportunities for using functions as control expressions. For instance, the getchar
function, a member of the I/O family of functions, fetches a character from the keyboard and
returns its ASCII value. The putchar function uses this value to display the character. Consider
the following code snippet:
int c;
c = getchar(); Gets the first character
while (c != EOF) { EOF is end-of-file
putchar(c); Displays the character
c = getchar(); Fetches the next character
}

This appears to be a simple way of retrieving a string of text from keyboard input. But because
getchar returns the ASCII value of the fetched character, you can move the getchar statement
itself to the control expression:
while ((c = getchar()) != EOF)
putchar(c);
This works perfectly and looks clean as well because a single getchar is used. The != has a higher
precedence than the =, so parentheses must be provided around the assignment (c = getchar()). This
ensures that the return value of getchar is assigned to c before c is compared to EOF (end-of-file).
Many functions return a value. scanf returns the number of items successfully read and printf
returns the number of characters written, so these functions can also be used as control expressions:
while (scanf(“%d”, &num) == 1)
while (printf(“Enter the amount: “))
Both getchar and putchar are discussed in Chapter 9 which examines the major I/O functions.
The scanf and printf functions are also examined in the same chapter.

8.4.1 Merging Entire Loop Body with the Control Expression


A previous example merged both calls to getchar to a single call in the control expression. But putchar
also returns a value (that of the character written), so why not move it too to the control expression?
Yes, we can do that as well:
244 Computer Fundamentals & C Programming

while ((c = getchar()) != EOF && putchar(c))


; Null statement
The value of the expression putchar(c) is c. This value is true for all ASCII characters except the
NUL character, so the putchar expression is always true. This compound expression will turn false
only when the input to getchar is exhausted. Since this loop has no body, don’t forget to follow the
while statement with a semicolon, preferably on a separate line.

Tip: Don’t merge too many statements with the control expression if that destroys clarity. Program
clarity is more important than compactness. In the preceding case, you should stop after moving
getchar to the control expression. The putchar statement is better left in the loop body.

8.4.2 When You Actually Need a Null Body


There are situations when you actually need to have a null body. When scanf is used with the
%c format specifier in a loop, whitespace characters often get trapped in the buffer. Subsequent
invocations of scanf get these trapped characters rather than fresh user input. So, before you call
scanf again, call getchar repeatedly to empty the buffer:
scanf(“%c”, &reply); \n generated by [Enter] remains in buffer
while (getchar() != ‘\n’) \n removed from buffer
; No further action needed
Here, getchar continues reading from the buffer until it encounters the newline. This ensures that
the next scanf call operates with an empty buffer. A null body is mandatory because the control
expression itself has done the job of clearing the buffer.
Caution: When using while (or any loop) without a formal body, remember to place a semicolon
immediately below it to indicate that the loop runs a null command. If you fail to provide the ;,
the statement following while will be considered as the body of the loop, which obviously is not intended.

8.5 NESTED while LOOPS


Just as an if statement can contain another if statement, nothing prevents us from using a second
while inside an outer while construct. Figure 8.3 presents two views of the structure of a nested
while loop. The view on the right highlights the way it is normally used. It shows the locations of
initialization and updating of key variables of both loops.
The nested loop starts with the initialization of key variable1, followed by evaluation of expression1.
If the result is true, the outer loop is entered. Before entry into the inner loop, key variable2 is
initialized. If and after expression2 evaluates to true, the inner loop is entered. When this loop
terminates, key variable1 of the outer loop is updated before control moves to the top of this loop
for re-evaluation of expression1.
Thus, each iteration of the outer loop leads to multiple iterations of the inner loop. The three
programs that follow make use of this one-to-many relationship that exists between the inner and
outer while loops.
Control Structures—Loops 245

Syntax Form Usage Form


while (expression1) { initialization1
... while (expression1) {
while (expression2) { ...
... initialization2
} while (expression2) {
} ...
Update key variable2
}
Update key variable1
}

FIGURE 8.3 Structure of a Nested while Loop

8.5.1 nested_while.c: Printing a Multiplication Table


Consider printing a table of x rows and y columns. This is done by printing all y columns of one
row before taking up the next row. The task is completed with the printing of the yth column of
the xth row. A nested while construct is thus the perfect choice for handling two-dimensional data
like tables and arrays as the following program (Program 8.5) shows.
/* nested_while.c: Uses nested while loop to print multiplication table
for positive integers up to 5. Maximum size of multiplier = 12 */
#define ROWS 5
#define COLUMNS 12
#include <stdio.h>
int main(void)
{
int x = 0, y; /* Key variable x initialized here ... */
while (x++ < ROWS) { /* ... and updated in control expression */
y = 0; /* Key variable y initialized here ... */
while (y++ < COLUMNS) /* ... and updated in control expression */
printf(“ %3d”, x * y);
printf(“\n”);
}
return 0;
}

PROGRAM 8.5: nested_while.c


1 2 3 4 5 6 7 8 9 10 11 12
2 4 6 8 10 12 14 16 18 20 22 24
3 6 9 12 15 18 21 24 27 30 33 36
4 8 12 16 20 24 28 32 36 40 44 48
5 10 15 20 25 30 35 40 45 50 55 60

PROGRAM OUTPUT: nested_while.c


246 Computer Fundamentals & C Programming

8.5.2 half_pyramid.c: Printing a Half-Pyramid with Digits


You need to use nested loops to print predictable two-dimensional patterns like pyramids and half-
pyramids. Consider the following pattern that uses digit-characters where the digit used is also the
sequence number of the row:
1
2 2
3 3 3
4 4 4 4
Even though the for loop is an automatic choice for tasks such as the one above, the next program
(Program 8.6) uses two while loops to print this half-pyramid. The number of rows to be printed
is taken as user input. Observe that one key variable (col) is updated in the control expression but
the other (row) is not. That is because row is used in both control expressions.

/* half_pyramid.c: Uses a nested while structure to print a half-pyramid. */


#include <stdio.h>
int main(void)
{
short row = 1, col, rows_max;
printf(“Number of rows? “);
scanf(“%hd”, &rows_max);
while (row <= rows_max) {
col = 1; /* Key variable col initialized here ... */
while (col++ <= row) /* ... and updated in control expression */
printf(“%d “, row); /* Prints row number in each row */
row++;
printf(“\n”); /* Moves to next row */
}
return 0;
}

PROGRAM 8.6: half_pyramid.c


Number of rows? 5
1
2 2
3 3 3
4 4 4 4
5 5 5 5 5

PROGRAM OUTPUT: half_pyramid.c

Since there is no limit on the depth of nesting (i.e., the number of inner loops possible), you can
handle multi-dimensional data quite easily.
Control Structures—Loops 247

Takeaway: In a nested while structure there is a one-to-many relationship between the outer
and inner loop. For every iteration of the outer loop, the inner loop must complete its set of
iterations before the next iteration of the outer loop can begin.

8.5.3 power.c: Computing Power of a Number


Program 8.7 uses an outer loop to take two integers (x and y) as user input. It then uses an inner
loop to compute the value of x raised to the power y. The program handles only positive integers but
quits when the user keys in a zero (for the first number) or a negative integer (for both numbers).

/* power.c: Computes power of multiple sets of numbers using a nested


while loop. Repeatedly multiplies a number by itself. */
#include <stdio.h>
int main(void)
{
short num, base = 1, power = 1;
unsigned long total;
while (base > 0 && power >= 0) {
printf(“Enter base and power (0 0 to exit): “);
scanf(“%hd%hd”, &base, &power);
if (base <= 0 || power < 0) { /* Invalid Input */
printf(“Invalid Input. Quitting ...\n”);
return 1;
}
num = 0; total = 1; /* Initialization before inner loop */
while (++num <= power)
total *= base; /* Multiply base by itself */
printf(“%d to the power %d = %lu\n”, base, power, total);
}
return 0;
}

PROGRAM 8.7: power.c


Enter base and power (0 0 to exit): 2 0
2 to the power 0 = 1
Enter base and power (0 0 to exit): 2 10
2 to the power 10 = 1024
Enter base and power (0 0 to exit): 0 0
Invalid Input. Quitting ...

PROGRAM OUTPUT: power.c


248 Computer Fundamentals & C Programming

The outer while loop repeatedly prompts for two integers. After performing the necessary validation,
the program passes on valid numbers to the inner loop. For each iteration of this loop, total is
assigned the values base, base * base, base * base * base, and so forth until the key variable num
equals power that is set by user input.
Note: The maths library supports the pow function which does the same job as this program.
Chapter 12 features a program that uses this function. For a program containing pow, the compilation
process is different: the linker must be explicitly invoked to include the code of pow in the executable.

8.6 USING while WITH break AND continue


The while loops seen so far used the control expression to determine whether it should iterate
or terminate. However, some situations demand immediate suspension of the current iteration
to either break out of the loop or restart a new iteration. C supports two keywords—break and
continue—that can perform these two tasks (Fig. 8.4).
When the program sees break, control breaks out of the loop to resume execution from the statement
immediately below the end of the loop. On the other hand, continue doesn’t terminate a loop but
suspends loop execution to resume the next iteration. Both situations usually occur when a variable or
expression used in the loop acquires a value that makes further execution of statements undesirable.

while (expression is true) {

statements;
break;

statements;
continue;

statements;
}

statements;

FIGURE 8.4 How continue and break Behave in a while Loop


The next program (Program 8.8) uses an infinite while loop to enable repeated entry of three positive
numbers. It uses continue to ask for a new set of numbers in case at least one of them is found to be
negative. If the numbers are all positive, then the break statement terminates the loop. The printf
statement below the closing brace of while then prints the sum of the numbers. The program also
terminates when zero is input for all three numbers.
Control Structures—Loops 249

/* break_continue.c: Uses break and continue in a loop. */


#include <stdio.h>
int main(void)
{
short a, b, c;
while (1) {
printf(“Enter three integers a b c: “);
scanf(“%hd %hd %hd”, &a, &b, &c);

if (a == 0 && b == 0 && c == 0) {
printf(“All values zero. Quitting ...\n”);
return 1;
}
else if (a < 0 || b < 0 || c < 0) {
printf(“Negative integer not allowed\n”);
continue; /* Goes to while (1) */
}
else
break; /* Goes to next printf statement */
}
printf(“Sum of %d %d %d: %d\n”, a, b, c, a + b + c);
return 0;
}

PROGRAM 8.8: break_continue.c


Enter three integers a b c: 3 4 -5
Negative integer not allowed
Enter three integers a b c: 3 4 5
Sum of 3 4 5: 12
Enter three integers a b c: 0 0 0
All values zero. Quitting ...

PROGRAM OUTPUT: break_continue.c


Structured programming theorists disapprove of the use of break and continue. They feel that
loops should provide a single point of exit (where the control expression is tested). But this
arrangement comes at a price; a number of variables need to be set and tested at various points in
the loop. After you have used break and continue, you may not be willing to pay that price.

8.7 prime_number_check.c: MORE OF break AND continue


We end our discussions on the while loop with Program 8.9, which checks whether a number
is prime or not. A prime number is one that is not divisible by any number other than itself
and 1. The program accepts multiple numbers from the keyboard and terminates when zero is
input. The exercise is carried out in a nested while loop structure that uses break and continue.
The program is grouped into four sections.
Lesson 3: For Loop

Control Structures—Loops 253

Enter a decimal integer: 35


Chapter
1 1 08,0 Sections
0 1 8.10-8.15 Binary of 35 is 100011
Number of iterations = 6
Enter a decimal integer: 128
0 0 0 0 0 0 0 1
Number of iterations = 8

PROGRAM OUTPUT: decimal2binary.c


The number obtained from user input is subjected to repeated division and modulo operation in
a do-while loop. The loop terminates when quot, which progressively decreases with each iteration,
eventually drops to zero. Because the generated bits are not saved, the program can’t reverse them.
To correctly represent the binary equivalent of the number, we need to use an array to store the bits
and then read the array backwards. We’ll improve this program in Section 8.12.

8.10 THE for LOOP


Like while and do-while, the for loop also uses the key variable/control expression paradigm to
determine loop entry and continuity. It is a compact construct that gathers three expressions at
one place (Fig. 8.6). Any while loop can be replaced with a for loop, and for many, the latter is
the preferred loop.

for (exp1; exp2; exp3) for (exp1; exp2; exp3) {


statement; statement1;
statement2;
...
}

FIGURE 8.6 The for Statement

The while loop works on the principle of initializing a key variable before the loop, testing it in
the control expression and updating the key variable in the body. The three expressions in for do
the same work but at one place. The initialization is done by exp1, the key variable check is done
by exp2, and updating of the key variable is done by exp3. These expressions, which are delimited
by semicolons, determine whether a loop will run, and if so, how many times.
The left side of Figure 8.7 shows the use of a for loop to print the ASCII values of the uppercase
letters. The right side shows the equivalent code using while. All three expressions in for are not
evaluated in every loop iteration, so let’s understand the sequence of operations used by for in
this program:
1. The first expression (chr = 65 and exp1 in syntax) initializes the key variable (chr).
2. The second expression (chr < 91 and exp2), which is the control expression for this loop, is
evaluated. If the evaluation is true, the loop is entered.
254 Computer Fundamentals & C Programming

#include <stdio.h> #include <stdio.h>


int main(void) int main(void)
{ {
int chr; int chr = 65;
for (chr = 65; chr < 91; chr++) while (chr < 91) {
printf(“%c=%d “, chr, chr); printf(“%c=%d “, chr, chr);
return 0; chr++;
} }
return 0;
}

PROGRAM OUTPUT:
A=65 B=66 C=67 D=68 E=69 F=70 G=71 H=72 I=73 J=74 K=75 L=76 M=77 N=78 O=79 P=80
Q=81 R=82 S=83 T=84 U=85 V=86 W=87 X=88 Y=89 Z=90

FIGURE 8.7 Difference in Form Between for (left) and while (right)
3. If the result of evaluation in Step 2 is true, the printf statement is executed.
4. After an iteration has been completed, control moves to the third expression (chr++ and exp3)
where the key variable is incremented.
5. Control now goes back to Step 2 where the control expression is re-tested. The loop starts the
next iteration if chr is still less than 91.
6. chr will eventually have the value 91. The control expression will then turn false and prevent
loop re-entry. The return statement terminates the program.
Like while, for is an entry-controlled loop because exp1 and exp2 jointly determine whether
the loop will be entered at all. This loop is special because of the flexibility of expression usage.
Any of these three expressions can comprise comma-separated multiple expressions, or it can be
dropped altogether. Using this feature, you can dismantle the set of three expressions to provide
a “while”-like look to for:
int chr = 65; exp1
for (; chr < 91;) { exp2
printf(“%c=%d “, chr, chr);
chr++; exp3
}
Here, exp1 and exp3 have been moved out, leaving their slots with null expressions. The semicolons
are mandatory even if the expressions are absent. Like in while, some statements of the loop body
can also be moved to the control expression.

Takeaway: In the first iteration of a for loop, the sequence is exp1 followed by exp2.
In subsequent iterations, it is exp3 followed by exp2. exp1 is evaluated only once—before the
loop is entered.
Control Structures—Loops 255

8.10.1 ascii_letters.c: A Portable ASCII Code Generator


Program 8.11 generates the machine values of the lowercase letters. It prints on each line the
values of nine letters in the form letter=value. The program is fully portable because, unlike the
previous program which works with specific ASCII values, this program works with the character
representation of the letters. As a consequence, the program will run without modification on both
ASCII and EBCDIC systems.

/* ascii_letters.c: A portable version of the ASCII list generator.


Runs without modification on EBCDIC machines. */
#include <stdio.h>
int main(void)
{
short chr, count = 0;
short diff = ‘a’ - ‘A’; /* Will be different in EBCDIC and ASCII */
for (chr = ‘A’; chr <= ‘Z’; chr++) {
printf(“%c = %3hd “, chr + diff, chr + diff);
if (++count == 9) { /* Nine entries in one line only */
printf(“\n”);
count = 0;
}
}
return 0;
}
PROGRAM 8.11: ascii_letters.c
a = 97 b = 98 c = 99 d = 100 e = 101 f = 102 g = 103 h = 104 i = 105
j = 106 k = 107 l = 108 m = 109 n = 110 o = 111 p = 112 q = 113 r = 114
s = 115 t = 116 u = 117 v = 118 w = 119 x = 120 y = 121 z = 122

PROGRAM OUTPUT: ascii_letters.c

The machine values of A and a in ASCII are 65 and 97, respectively. Machines that support other
character sets (like EBCDIC) have different values. For the program to be independent of the
character set, we need to store the difference of these two values in a separate variable (diff).
We can then derive the lowercase value of a letter from knowledge of its uppercase value.
The program is portable because instead of using chr + 32, we have used chr + diff.
The for loop initializes the key variable chr to ‘A’ before it evaluates the control expression
chr <= ‘Z’. Since this is true and will remain true for the next 25 iterations, the loop is entered.
Next, printf prints the value of ‘a’ in a space 3 characters wide (because of %3hd) before beginning
the next iteration. A simple equality test in the loop ensures the printing of nine entries in each line.
After completing one iteration, control moves to the top where chr is incremented and the control
expression re-evaluated. The entire cycle is repeated until the value of chr exceeds ‘Z’, when the
loop terminates. However, in every ninth iteration, printf prints a newline and count is reset to zero.
256 Computer Fundamentals & C Programming

Tip: When designing programs that change the case of letters, follow the preceding technique
of storing the difference between a lowercase and uppercase letter in a variable, and then use
this value for case conversion. Your programs won’t be portable if you derive the value of ‘a’ by
adding 32 to ‘A’ simply because this relation is true only for ASCII systems.

8.10.2 print_terms.c: Completing a Series of Mathematical Expressions


You can use a for loop to complete a series comprising expressions (terms) where a fixed relation
exists between two consecutive terms. Consider the following series comprising the squares of
consecutive integers where the odd terms are added and even terms are subtracted:
1 - 4 + 9 - 16 + 25 ...

The nth term in the series is represented as n2. The following program (Program 8.12) completes the
series up to n terms where n is set by the user. The program also evaluates the complete expression
by choosing the + and - operators correctly. The annotations adequately explain the working of
the program.

/* print_terms.c: Program to complete a series of squares of integers.


Adds even terms but subtracts odd terms. */
#include<stdio.h>
int main(void)
{
short n, n_max, sum = 0;
printf(“Enter max number of terms: “);
scanf(“%hd”, &n_max);
for (n = 1; n <= n_max; n++) {
if (n % 2 == 0) /* For even integers ... */
sum += n * n; /* ... add to sum */
else /* For odd integers ... */
sum -= n * n; /* ... subtract from sum */
printf(“%hd “, n * n); /* Displays square of integer */
}
printf(“\nSum = %hd\n”, sum);
return 0;
}

PROGRAM 8.12: print_terms.c


Enter max number of terms: 5
1 4 9 16 25
Sum = -15
Enter max number of terms: 10
1 4 9 16 25 36 49 64 81 100
Sum = 55

PROGRAM OUTPUT: print_terms.c


Control Structures—Loops 257

8.11 for: THE THREE EXPRESSIONS (exp1, exp2, exp3)


Each component of the combined expression exp1; exp2; exp3 need not be a single expression.
You can use a comma-operated expression to represent any of these expressions. You can drop any
expression or take the extreme step of dropping all of them! The following paragraphs discuss some
of the ways these three expressions can be tweaked to our advantage.

8.11.1 Moving All Initialization to exp1


To appreciate the usefulness of the comma operator, have a look at the two initializations made
before the loop in the program ascii_letters.c (Program 8.11):
diff = ‘a’ - ‘A’;
count = 0;
Since exp1 is evaluated only in the first iteration, there’s no reason why we can’t move these
assignments to exp1. The for statement should now look like this:
for (diff = ‘a’ - ‘A’, count = 0, chr = ‘A’; chr <= ‘Z’; chr++)

The value of exp1 (containing the two comma operators) is now ‘A’ (the value of the right-most
expression), but that is of no consequence to us in this program. What matters to us is that the
three expressions together can be treated as exp1.

8.11.2 Moving Expression Statements in Body to exp3


Let’s now consider the program, while_intro.c (Program 8.1), which adds the first 10 integers
using a while loop. The equivalent code using for is shown below:
short i, sum;
for (i = 1, sum = 0; i <= 10; i++) {
sum += i;
printf(“%hd “, sum);
}
printf(“\nSum of first 10 integers = %hd\n”, sum); Prints 55
The loop body contains two statements, which along with i++ (exp3), constitute the body of the
equivalent while loop. So if i++ can represent exp3, why not sum += i? Yes, we can move this
operation to exp3, but we also need to change two things: initialize i to 0 (instead of 1) and change
the relational operator to < (instead of <=):
for (i = 0, sum = 0; i < 10; ++i, sum += i)
printf(“%hd “, sum); Also prints 55
But then printf is also an expression, so why leave it out?
for (i = 0, sum = 0; i < 100; ++i, sum += i, printf(“%hd “, sum))
; Also prints 55
This for loop has no body as shown by the null statement (the solitary ;) on a separate line. This ;
is necessary, otherwise the subsequent printf statement will be considered as the body of the loop,
which it is not.
258 Computer Fundamentals & C Programming

8.11.3 Dropping One or More Expressions


Program 8.13 uses the scanf function to convert a line of keyboard input from lower- to uppercase.
This function, when used with %c, saves the fetched character in a char variable. The for loop in
this program has a component missing, while the other two components are unrelated.

/* lower2upper.c: Converts a line of keyboard input to uppercase. */


#include <stdio.h>
int main(void)
{
char c, diff;
for (diff = ‘a’ - ‘A’; scanf(“%c”, &c) == 1; ) {
if (c >= ‘a’ && c <= ‘z’)
printf(“%c”, c - diff);
else
printf(“%c”, c);
}
return 0;
}

PROGRAM 8.13: lower2upper.c


dont forget to use the & prefix with the variables used in scanf.
DONT FORGET TO USE THE & PREFIX WITH THE VARIABLES USED IN SCANF.
[Ctrl-d]
PROGRAM OUTPUT: lower2upper.c

The expression exp2 of this for loop behaves in an unusual manner. Every iteration of the loop
runs scanf to assign the next input character to the variable c. The loop thus reads a complete
line of input and terminates when it encounters EOF. Because there is no key variable here,
we don’t need to use exp3 at all. Its absence is indicated by the solitary ; at the end of the complete
expression. Like Program 8.11, this program is also portable.
Note: The return value of the scanf expression acts as a key variable in this program. Its side effect
is also utilized by the program.

8.11.4 The Infinite Loop


Depending on the nature of the problem, any of the three expressions can be dropped. As a special
case, all of them can be dropped to create an infinite loop:
for (;;) Infinite loop; like while (1)
Though this isn’t quite intuitive, the absence of all expressions signifies a perpetually true condition.
Since exp2 needs to be permanently true, the statement for (;1;) also creates an infinite loop, but
for (;0;) doesn’t. Like in the while loop, any infinite loop can be terminated with break.
Control Structures—Loops 259

Note: In most cases, the for loop uses the same variable as the three components of the combined
expression. However, this is not the case in Program 8.13; the variables diff and c are completely
unrelated to each other. Be prepared to see for loops where exp1, exp2 and exp3 are represented by
three different variables.

8.12 decimal2binary2.c: CONVERTING A DECIMAL NUMBER TO BINARY


Program 8.14 is an improved version of its predecessor (Program 8.10). It saves in an array the
remainders generated by repeated division of a number by 2. The program then reverse-reads the
array to generate the binary equivalent of the decimal integer. The program uses two for loops for
saving and reading the bits.

/* decimal2binary2.c: Stores remainders generated by repeated integer


division in an array and then reverse-reads the array. */
#include <stdio.h>
int main(void)
{
unsigned long quot;
char binary_digits[80]; /* Adequate for remainders of 80 divisions */
short rem, index;
printf(“Enter a decimal integer: “);
scanf(“%lu”, &quot);
for (index = 0; quot > 0; index++, quot /= 2) {
rem = quot % 2;
binary_digits[index] = rem == 1 ? ‘1’ : ‘0’;
}
/* All remainders stored ... */
for (index--; index >= 0; index--) /* ... Now reverse-read the array. */
printf(“%c “, binary_digits[index]);
return 0;
}

PROGRAM 8.14: decimal2binary2.c


Enter a decimal integer: 8
1 0 0 0
Enter a decimal integer: 35
1 0 0 0 1 1
Enter a decimal integer: 128
1 0 0 0 0 0 0 0

PROGRAM OUTPUT: decimal2binary2.c


260 Computer Fundamentals & C Programming

Repeated division by 2 in the first for loop generates the values 0 and 1 of type short. Because the
array, binary_digits, is of type char, these short values are transformed to char using a conditional
expression. After the first for loop has completed its run, binary_digits has all the remainders.
A second for loop now reads this array backwards by repeatedly decrementing the value of index
which was set by the previous loop.

8.13 NESTED for LOOPS


One for loop can enclose any number of inner for loops. For reasons of compactness, nested for
loops are often preferred to nested while loops. For instance, the multiplication table created with
a nested while loop in Section 8.5.1 can also be printed with a compact nested for loop structure.
In the following equivalent construct, for each value of x (1 to 5), y can take on 12 values (1 to 12):
int x, y;
for (x = 1; x <= 5; x++) {
for (y = 1; y <= 12; y++)
printf(“ %3d”, x * y);
printf(“\n”);
}
Nested for loops are the perfect tool to use for printing patterns. We have used nested while loops
to print a half-pyramid (Program 8.6), so let’s use a nested for loop to print a complete pyramid
with asterisks. Program 8.15 prints the following pattern when the number of rows is set to 6 by
user input:
*
***
*****
*******
*********
***********

/* pyramid.c: Prints a pyramid pattern with the * using nested for loops. */
#include <stdio.h>
int main(void)
{
short i, j, k, rows;
printf(“Number of rows? “);
scanf(“%hd”, &rows);
for (i = 1; i <= rows; i++) {
for (j = 1; j <= rows - i; j++)
printf(“ “); /* Prints spaces before pattern */
for (k = 0; k != 2 * i - 1; k++)
printf(“*”);
printf(“\n”);
}
return 0;
}

PROGRAM 8.15: pyramid.c


Control Structures—Loops 261

Each line is printed by increasing the number of asterisks by 2 and decreasing the number of leading
spaces by 1. This logic is captured by the control expressions of two for loops in Program 8.15.
The expression j <= rows - 1 controls the number of spaces, and the expression k != 2 * i - 1
controls the number of asterisks.

8.14 USING for WITH break AND continue


The keywords, break and continue , apply to all loops including for. break terminates the current
loop and moves control to the statement following the end of the loop. continue suspends the
current iteration to start the next iteration. Figure 8.8 shows small code fragments that highlight
their usage in the for loop.
You have already experienced the convenience of using break and continue in a while loop. It’s
clear that, without them, one would need to use additional code to bypass subsequent processing.
You would need to set special flags to force the control expression to change normal loop behavior
(8.6). A previous caveat applies here as well: a break statement in the inner loop can’t take control
out of the enclosing loop.

for (div = 3; div < num; div += 2) { for (;;) {


if (num % div == 0) { printf(“Number for prime test: “);
is_prime = ‘n’; scanf(“%d”, &number);
break; if (number < 0) {
} printf(“Invalid input\n”);
} continue;
}
... rest of code ...
FIGURE 8.8 Use of break and continue in a for Loop

8.15 all_prime_numbers.c: USING break AND continue


The final program of this chapter (Program 8.16) generates all prime numbers not exceeding
a user-set limit. In Program 8.9, we determined whether a number is prime by dividing it by all
smaller integers. That program unnecessarily included even numbers but the current program skips
them. For reasons of convenience, the program doesn’t print 2, the first prime number.
This program considers only odd values of both divisor and dividend. The outer for loop cycles
through odd values of the dividend and the inner loop does the same for the divisor. The program
works on the principle that a number is prime unless proved otherwise.
262 Computer Fundamentals & C Programming

/* all_prime_numbers.c: Uses a nested for loop to generate all prime numbers


not exceeding a user-set limit. Starts from 3. */
#include <stdio.h>
int main(void)
{
int num, max_num, div;
char is_prime;
printf(“Maximum number to be tested for prime: “);
scanf(“%d”, &max_num);
for (num = 3; num <= max_num; num += 2) { /* Tests odd integers only */
is_prime = ‘y’;
for (div = 3; div < num; div += 2) { /* Divisor is also odd */
if (num % div == 0) {
is_prime = ‘n’; /* Number is not prime so ... */
break; /* no further check required */
}
}
if (is_prime == ‘y’) /* If prime number found ... */
printf(“%d “, num); /* ... print it */
}
printf(“\n”);
return 0;
}

PROGRAM 8.16: all_prime_numbers.c


Maximum number to be tested for prime: 100
3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

PROGRAM OUTPUT: all_prime_numbers.c

WHAT NEXT?

All work in C is performed by functions, and we must know the ones that interact with the terminal.
The next chapter makes a detailed examination of the essential input/output (I/O) functions.

WHAT DID YOU LEARN?

A loop generally operates by first initializing a key variable, testing it in the control expression and
then updating it in the loop body.
A loop may run a finite or infinite number of times. An infinite loop is created if the control expression
never turns false.
Functions in a C Program
Reading Objective:

In this reading, you will learn about functions and their usage and advantages. You will also learn how to declare, define,
and call functions. In addition, you will also get an insight into how the program flow execution proceeds or changes with
a function call.

Main Reading Section:

Role and Need of Functions


Functions are entities in C programs that consist of well-defined C statements, executed when required or called. A
function can take an optional list of input parameters and perform computations over them by executing the statements
defined in it. A function can also return a value that is computed in it.

In C, the execution of any program starts from the main function. The following is a simple program with only the main()
function.

More formally, a function is a self-contained program segment that carries out some specific, well-defined task.

A function:

• Processes information passed to it from the calling portion of the program

• Returns a single value

Every C program has one or more functions. As we have seen earlier, any program will have at least one function, that is,
the main function. Now, let us see an example of a function that finds the sum of two numbers.

Figure 1 gives a program that contains two functions: the main function and the sum function. The sum function is
defined in lines 3 to 8, wherein the block within curly braces within lines 4 to 8 consists of the function's body. Line 3
defines the function sum, which takes two integer values, ‘a’ and ‘b,’ as input parameters. sum function computes the
sum of ‘a’ and ‘b’ and stores the result in an integer variable ‘total’ in line 6. It then returns the value of ‘total’ in line 7.

The sum function is called in line 14 from within the main function. In this call, the values of ‘x’ and ‘y’ are passed into
the function sum as arguments. Essentially, the values of ‘x’ and ‘y’ are respectively copied into ‘a’ and ‘b’ of the sum
function, wherein the sum of those two values is computed and returned; that is, 5 + 4 = 9 is the value returned by the
function sum. This value is captured into the variable ‘z’ in the main function. ‘z’ can now be used like any other variable
in the main function to perform other computations and operations.
Figure 1: Usage of a function sum to get the sum of two given numbers

Why are functions useful?


The main advantage of using functions is enhanced code reusability. Functions can be called multiple times to perform
the same task on different inputs. For example, consider the program in Figure 2. The function sum is called two times on
two different sets of inputs (lines 14 and 15). The program logic to compute the sum of two numbers doesn’t need to be
repeated each time we want to compute the sum of two numbers. Instead, the function sum can be called multiple times
as required.

Another advantage of using functions is the enhanced modularity of the code. By modularity, we mean separation of
concerns. In the program given in Figure 2, the task of computing the sum of two numbers has been exclusively given to
the function sum. No other code snipped in the program does this job for us. The tasks of the main function and the sum
function are defined distinctly. This is an example of a modular code.
Figure 2: Multiple usage of function sum

Declaration and Definition of a Function:

Functions in C are declared, defined, and called. Let us examine each of them in detail.

1. Function Declaration: A function is declared in C to tell the compiler about a function that may be later
defined/used in the program. It specifies the function’s name, datatypes, the number of parameters it takes,
and the datatype of the value returned by it. Functions declarations are always done before their definitions
or calls. Although function declarations are OPTIONAL in many compilers, using them is a good practice.

Syntax of Function Declaration:

Examples:

Return types: In the above examples, float, int, and void are return types. Please note that void is a keyword that
indicates this function does not return anything.

Function name: In the above examples, the names of functions are indicated in yellow. The name of a function can be
any but must be a valid identifier in C, as discussed in the variable declarations.

List of typed parameters: In the above examples, the types of the parameters are indicated in blue. In function
declarations, the names of the input parameters are optional, as can be seen in the third example above. It has two
parameters of type float, which are used without any variable name, unlike in the first and second examples. All three
examples show the correct ways to declare functions.

Note that the list of input parameters can be empty as well. Moreover, if there is more than one parameter, they are
separated by a comma. Figure 3 illustrates this.

Figure 3: Function declaration in C

2. Function Definition: The statements to be executed by a function are placed in the function definition. Functions are
defined only once. Each time a function is called, the statements inside the function body are executed using the optional
list of input parameters. Here is the syntax of a function definition:

Example:
In the above example, a definition of the function average is given. Note that this function was previously declared in
figure 3. The return type of function average is int. It takes two float values as parameters, performs a few operations to
compute their average, stores the average in the variable ‘avg,’ and returns it.

The input parameters (type-name pairs) are compulsory in the case of function definitions. The parameters can’t just be
a list of types like in the case of a function declaration. Figure 4 illustrates this.

Figure 4: Function definition in C

Here is another example of a function definition where the function print_nums takes two int values, prints them, and
returns nothing.

Matching Function Declaration and Definition:

Table 1: Relationship between a function’s declaration and its definition


The following should exactly match between a function declaration and a function definition:

• Name of the function : In table 1, it is ‘average’.

• Number of parameters : There are two parameters separated by a comma.

• Type of each parameter: Both parameters are of type float.

Please note that the names of the parameters need not match!

• The return type: In the declaration and the definition, the return type is int.

Also, in the definition, the last line returns a variable avg of type int.

1. Function Call: To use a function, it needs to be called or invoked. It can be called either from the main function
or any other function in the program. Figure 5 gives an example of the sum function that is declared, defined,
and called. The function sum is declared in line 3, defined in lines 4 to 9, and called in line 15 from the main
function.

Figure 5: Function declaration, definition, and call

Consider the program given in figure 6. It has two functions apart from the main function. These are sum and sum_call.
These functions are declared in lines 2 and 3, respectively. In line 16, the function sum_call is called from the main
function, and the function sum_call, in turn, calls the function sum in line 11.
Figure 6: Calling functions in C

Here is the formal syntax of a function call, illustrated in Figure 7

Figure 7: Syntax of calling a function in C

Examples:

Note that calling the function sum with sum(10,15); is syntactically correct. But it is not useful as the value returned by
the function sum is not stored anywhere. Some functions do not return a value, or we don’t need them. In such cases, we
shall not need to store the value returned by the function call. For example, the printf is a library function whose return
value is not stored in any variable in common practice, although it returns the number of characters printed.

Try yourself:

Print the value returned by the printf function:

We generally do not store the value returned by the printffunction.


Matching Function Call with Function Definition:

Consider the program in figure 6. The definition of the function sum_call is in lines 10 to 12, and the function call is in line
16:

The following should be an exact match between a function definition and a function call:

• The name of the function: sum_call.

• The number of arguments in the function call must match the number of parameters in the function
definition: two parameters in the definition of sum_call and two arguments in its call.

• Type of each argument in the function call must match the type of the corresponding parameter in the function
definition: both are of type int.

Names of parameters need NOT match!Yes, names are not matching.

• The return type: return type in the definition is int, and the value returned is stored in an int variable ‘z’.

Function Invocation and Flow of Program Execution:

Let us examine the flow of program execution with the example of a program given in Figure 8:

Figure 8: (duplicate of figure 5) Example of a function declaration, definition, and call in C

The execution of a C program always starts from the main function.

• Thus, the first line that gets executed is line 13, wherein four integer variables are declared: ‘x’, ‘y’, ‘z’, and ‘k’.

• Next, line 14 is executed wherein ‘x’ is assigned with value 5 and ‘y’ with 4.
• Next, the line 15 is executed, where function sum is called with the values of variables ‘x’ and ‘y’ passed as
arguments.

• Now, the flow of execution shifts from line 15 to line 4, which is the definition of the function sum. The value of ‘x’
gets copied to variable ‘a’, and value of ‘y’ gets copied to variable ‘b’. Hence ‘a’ holds 5, and ‘b’ holds value 4.

• The execution flow goes to the next statement in line 6, where int variable ‘total’ is declared, then to line 7 where
sum of values in ‘a’ and ‘b’ is computed and stored in ‘total’, that is, ‘total’ = 5+4 =9.

• Next, line 8 is executed, which returns the value of total. This means that the value of total is returned to the
calling function. In line 15, the value returned is copied into the variable z. Making the value of z equal to 9.

• Then, line 16 is executed, where the value of sum of ‘x’ and ‘y’ stored in variable ‘z’ is printed, and the program
exits.

• The above program, when executed, gives the following output:

Reading Summary
In this reading, you have learned the following:

• How to declare, define, and call a function

• How to use functions to write modular programs

• How to match function declaration, definition, and a call

• The program flow execution of programs that use functions when a function is called
Lab Preread: Multi-File Execution in C
Topic: Multi-File Execution in C

Reading Objective:

This reading introduces you to multi-file execution. You will learn how to write programs and functions using multi-files.
You can use and apply it for lab practice sessions and assignments.

Main Reading Section

Explanation of Multi-File Execution

Until now, you were writing your C program in a single file, for example, sample.c. In this file, you had written your main()
function, compiled it using gcc sample.c, generated the output file a.out, and executed it by using ./a.out command.

We have learned the concept of functions and function calls. We have also learned that functions are modular and have
standalone existence or highly promote reuse of code or functions are written to perform a certain task. Now, we will
apply that understanding to practice.

Normally, we would write your main() function and all other user-defined functions in the same file. That is slightly going
to change now. We will have a file called main.c where we have written the main() function. We will read all the necessary
inputs and perform a function call from the main() function to the user-defined functions written to perform a certain task
(or your objective, in this case). So along with the function call, we will pass all the necessary inputs you have read in the
main() function.

We will NOT write the function definition in the same file where the main() function is written, that is, we will write the
function definition in a separate file.

To gain more clarity, let us consider an example program. Let us assume we are working on a program to calculate the
sum of two numbers. But the sum calculation will be performed by a function sum(). The two number inputs will be read
in the main() function. Then, we will call the function sum() from the main() function by passing the input values as
arguments. After calculating the sum inside the sum() function, the result will be returned to the main() function, and the
result will be displayed.

So, a normal program for the above task would look like this:

main.c
To write the same program as a multi-file execution program, we will have to make small changes to the program.

First of all, we will have two .c files in the folder or directory where we are working on or planning to save our code.

• One is main.c
• The other one is sum.c, which basically has the definition of the sum() function.
The next change will be in the include statements of both files. We may write the include statements only in the main.c
file. We will also need to include the sum.c file in the main.c file using the #include statement written as #include sum.c.

In the sum.c file, we need not write any header files and write only the function definition.

So the program, when converted to multi-file execution, will get changed as follows:

main.c

sum.c

It is worth noting that there are no header files or function declarations in sum.c.

Similarly, main() also does not have the function declaration of sum() anymore. Also, we want to draw your attention to
the #include statements in main(), where we have included the sum.c file so that the function definition will be available
in the main() function.

These two files need to be present in the same folder or directory. For example, the files “main.c” and “sum.c” are present
inside the question’s directory, which is part of the PracticeLab directory of the week.
Now, for compiling the program, we want you to open the terminal and set the path to this directory in which the files
main.c and sum.c are present using the cd command as follows:

Pay attention to the cases, underscores, capital letters, small letters, and everything while typing the name of the
folders/directories.

If the path setting is successful. The above text will also change as follows: (pay attention to the blue text)

Now, it is time to compile the program. We must compile only one file, and that is the main.c file (the file in which we have
written the main() function). We should not compile sum.c as it is already included in main.c.

Compile using: gcc main.c

After successful compilation, a.out file will be generated in the directory. To view the output, we need to type ./a.out in
the terminal.

Reading Summary:

This reading introduces you to multi-file execution. You will learn how to write programs and functions using the multi-
files. You can use and apply it for lab practice sessions and assignments.

NOTE:

When you are attempting Practice Labs, mostly in multi-file execution programs, both files, main.c and the
functionfile.c, should be available in the corresponding folder. Therefore, you need not create extra files. You would only
be required to write the code in the required places of either main.c or functionfile.c as required.
C Programming Practice Lab 1

Assignment Brief

PracticeLab1_Question1

Write a program that takes an integer value n from the user and checks if it is even or odd using a function.
If it is even, that is, the remainder is 0 when divided by two, the program prints 'Input number is even.'
Otherwise, it prints 'Input number is odd.'

PracticeLab1_Question2

A function average() is called from the program. You need to complete its definition. The function prompts
the user to "Enter 4 numbers:" and it stores the numbers in its local variables. It computes the average of
the numbers and returns the value.

Take the hint of parameters and return types from the function declaration and calling, and return the
average value in the required datatype.

*NOTE: Do NOT add any statement in the main function.

Testcase 1
The average of 4 numbers:

Enter 4 numbers: 3 5 6 8

Average is: 5.500000

PracticeLab1_Question3

The program aims to print an inverted triangle pattern of the height given by the user.

You must find and fix the syntactical and logical errors in the code. The function takes an integer n and
returns nothing. The outer loop runs N times, and the inner loop runs to put either a leading space or a '*.'
The row length is considered in characters till the last '*,' including spaces. You need to check logical
errors and errors in the syntax of declaring, defining, and calling a function.
Pros and Cons of Functions in C
Reading Objective:

In this reading, you will learn about the pros and cons of using functions in our program. You will also learn how functions
reside in the memory when the programs containing various functions are executed.

Main Reading Section:

Pros of Using Functions


1. Modularity:

Functions increase the modularity of the code. Modularity implies that the code is very well organized so that the
separation of concerns is achieved. This means that the specific task we want to perform with our program is subdivided
into multiple subtasks, each assigned to a separate function.

1. Code Reusability:

Modularity in the code achieved by using functions also enhances code reusability. This is because a function can be
called multiple times from different calling functions to perform similar subtasks on a different set of data. We will not
need to rewrite the same code snippet each time we want to do the same subtask. Instead, we can call the corresponding
function.

1. Reduced coding time:

The usage of the function also reduces coding time. Modularity leads to code reusability, reducing the time and effort
required to write the code.

1. Easier to debug:

Functions also make code debugging easier because the code becomes more readable, clean, well-organized, and
simple.

1. Ease of understanding:

The use of functions improves ease of understanding. If the code is well-organized and uses many functions, any third-
party person who has never seen it before can easily comprehend it. Codes with minimal function usage appear complex
and are often difficult to understand.

Cons of Using Functions


1. Reduced execution speed of the program:

Each time a function is called, the operating system (OS) allocates some space in the memory. This allocation adds an
additional time overhead to the program execution time, reducing the overall speed of the program execution.

Memory Snapshot During Function Execution:

We learned earlier that whenever a function is called, there is a time overhead of space allocation in the memory. Let us
now understand how functions reside in the memory when they are called in programs.

Figure 1 illustrates our original block diagram of the program execution, wherein a program is written and saved to disk.
The executable gets loaded into the memory and executed line by line on the CPU when the program is compiled and
executed.
Figure 1: Memory usage and snapshot during function execution in a program

Let us now delve deeper into the memory allocated to the program. Whenever a program is executed, the executable file
is loaded into the memory (RAM), which means some memory space is allocated to the entire program. Refer to the
program and memory diagram in Figure 2.

Figure 2: How functions are stored in Main Memory (RAM) when the program is under execution
All the program components, such as variables and functions, will be stored within the space allocated to the program.
We have main and sum functions in this program (Fig. 2). It is important to note that the sum function is called from the
main function, and each function has a disjoint space allocated to it, as shown in Figure 2.

The variables ‘x’, ‘y,’ and ‘z’ are stored within the space allocated to the main function. In the space allocated to the sum
function, the variables ‘a’, ‘b,’ and ‘total’ are stored.

Figure 2 depicts a basic idea of the memory allocated to a program. A deeper and refined view of memory allocated to a
program will be covered later in this course.

Reading Summary

In this reading, you have learned about the following:

• Benefits of functions to a programmer in terms of achieving modularity, reusability, and allied advantages
• How functions affect the overall performance of the program
• How functions and variables defined in the functions are stored in the memory when the program is under
execution
Using Functions in C
Reading Objective:

In this reading, you will learn about implementing and using functions in a C program to achieve a certain task.

Main Reading Section:

We will take two examples of C programs that use functions. The complete implementations of these programs are
provided at the end of this reading in the Appendix section.

Functions Example 1: Computing factorial of a given number N

Let us write a program to compute the factorial of a user-given number.

The factorial of a number is the product of all the numbers from 1 to that number.

factorial of n = 1 * 2 * 3 * .. * n-1 * n

For example:

• factorial of 1 = 1

• factorial of 2 = 1 * 2 = 2

• factorial of 3 = 1 * 2 * 3 = 6

• factorial of 4 = 1 * 2 * 3 * 4 = 24

• factorial of 4 = 1 * 2 * 3 * 4 * 5 = 120

and so on.

To write a program for factorial, we will take an integer number input from the user and pass it to a function that will
compute the factorial and return its value. Let the name of the function be fact.

So, the function declaration would look like this:

The name of the function is fact and it takes an integer ‘n’ as the parameter. The function returns the factorial number;
thus, the return type is also int. This declaration is a single-line statement ending with a semicolon that tells the compiler
that, somewhere, a function fact shall be defined after this declaration, which takes one integer and returns an integer
value.

Now let us define this function. The definition must match the declaration as explained in the previous readings. The
definition of fact is as follows:
Initially, an integer variable temp is declared and initialized to 1. This variable will store the factorial value. Then a loop is
created that will iterate n times with n varying from n, n-1, n-2, … 3, 2, 1. And at each iteration, we compute the product
and store it in temp.

• Iteration 1: temp = temp * n = (1) * n = n

• Iteration 2: temp = temp * n-1 = (n) * n-1

• Iteration 3: temp = temp * n-2 = (n * n-1) * n-2

• And so on.

Thus, at each step, we compute the product, and by the last iteration, when n equals 1, we have the product of n * n-1 * n-
2 * … * 2 * 1 stored in temp. This is the factorial value of n. So, after the loop exits, we can simply return the value stored
in temp.

Thus, the function fact takes an integer ‘n’ and returns the integer value of the factorial of ‘n.’

Now, we need to call the function fact from another function to use it. Any program needs a main function, thus let us
create the main function, and call a fact from within the main:

First, we ask the user to input a value for ‘n,’ and we capture it using scanf. Then, we call the fact function with the value
stored in ‘n’ as the argument. This function computes the factorial of ‘n’ and returns it, which is stored in the variable
‘factorial.’ Then, its value is printed.

After compiling the program, we can run the executable multiple times with different inputs. Following are some
examples:
Functions Example 2: To check whether the given number is prime or not

Let us create a function that checks whether a given number is a prime number. Since we don’t need to return anything
from the function, the return type would be void. The following is the declaration of theisPrime function that takes an int
variable ‘n’:

Now, let us define the function. To check whether the number is prime or not, we will check for every number from 2 to n-
1 if it divides n. If any of these numbers divide n (with remainder 0), then n is not prime. For this purpose, we have
initialized two variables, ‘i’ for looping from 2 to n-1 and a flag, ‘notPrimeFlag’. As soon as we find a number that divides
‘n,’ with remainder 0, we need not check any other numbers since it is verified that ‘n’ is not a prime number. Thus, in the
loop, when n%i == 0 (i divides n completely), we set the ‘notPrimeFlag’ to 1 and break out of the loop.
After the loop exits, there is an if-else condition on the flag because the loop can exit in two ways: with or without
entering the if block inside the loop. If ‘n’ is prime, the program flow execution will never enter the if block, and the
‘notPrimeFlag' will remain 0. The function will print “n is a prime number.” But if the number is not prime, there will be
some value of ‘i’ for which ‘i’ divides ‘n’ with the remainder 0. In such a case, the if block inside the loop is entered, and
‘notPrimeFlag’ will be set to 1. The loop then terminates with the ‘break’ statement.

The outer if-else block checks which of the two ways the loop has exited by checking the value of ‘notPrimeFlag.' If its
value is 1, it prints that ‘n’ is not a prime number. Otherwise, it prints that ‘n’ is a prime number.

For example, if the number is 9:

• When i is equal to 2, 2 does not divide 9: flow will not go inside the inner if block.

• When i is equal to 3, 3 divides 9 with 0 remainder: flow will go inside the inner if block, sets ‘notPrimeFlag’ to 1,
and ‘break’ the loop.

• Then the ‘notPrimeFlag’ is checked for value 1, and the function correctly prints “n is not a prime number.”

Following are some test cases of the program:

Reading Summary

In this reading, you have learned the following:

• How to write programs using functions

• Example of a function that takes an integer value and returns its factorial

• Example of a function that checks whether a given number is prime or not

Appendix

Complete code for example 1:


Complete code for example 2:
C Programming Practice Lab 2

Assignment Brief

PracticeLab2_Question1

The program checks if the given number is:

1. Armstrong number:

Armstrong number is a number that is equal to the sum of cubes of its digits. For example, 0, 1, 153, 370,
and 371 are the Armstrong numbers.371 = 3^3+7^3+1^3 (^ means the power of).

2. Perfect number:

In number theory, a perfect number is a positive integer that is equal to the sum of its positive divisors,
excluding the number itself. For instance, 6 has divisors 1, 2, and 3, and 1 + 2 + 3 = 6, so 6 is a perfect
number.

Testcase1 Testcase2 Testcase3


Input a number to check: 371 Input a number to check: 496 Input a number to check: 40

371 is an Armstrong number. 496 is not an Armstrong number. 40 is not an Armstrong number.

371 is not a perfect number. 496 is a perfect number. 40 is not a perfect number.

PracticeLab2_Question2

Complete the code to find if the given number is a palindrome or not.

A number is a palindrome when its digits in reverse order form the same number. The reverse digits of
12321 are 12321, which equals the same number. Hence, 12321 is a palindrome. The reverse digits of
12301 are 10321, which are not equal to the same number. Hence, 12301 is not a palindrome.

Testcase1 Testcase2 Testcase3


Please enter a number: 12321 Please enter a number: 12 Please enter a number: 121

12321 is a palindrome. 12 is not a palindrome. 121 is a palindrome.

Testcase4

Please enter a number: 11

11 is a palindrome.
Scope Rules and Memory Layout of a C Program
Reading Objective:

In this reading, you will learn about different storage classes for variables in C and how they differ in terms of scope,
default value, storage, and lifetime. You will also gain a deeper view of the memory allocated to a program and how it is
divided into various segments.

Main Reading Section:

Scope and Storage Class of a Variable


While declaring a variable in C, we define its datatype and storage class. We have already studied various datatypes
defined in C. Now, let us study storage classes of variables.

The storage class of a variable gives us information about the following:

• Scope of the variable or the sections of code in which we can read/modify the value of the variable

• The physical location in the memory where the variable is stored

• The initial value of the variable, or the default value of a variable when it is declared (not initialized, that is, int
x;)

• The lifetime of the variable or how long the variable resides in the memory

Now, let us try to illustrate the scope of a variable. Consider the program in figure 1. ‘y’ is a variable declared outside all
the functions. Hence, it’s scope is the entire program. In other words, it can be accessed and modified in all the functions
of the program. The variable ‘a’ is defined inside the main function, so its scope is the main function. Whereas ‘b’ is
defined inside the block in red braces inside the main function, its scope is limited to that block only. In addition, ‘x’ and
‘c’ are defined inside the functionf1; their scope is the functionf1.

Figure 1: Illustrating the scope of a variable

Now, let us consider another program given in figure 2. There is a variable ‘defined’ in the main function. At the same
time, another variable with the same name is taken as an input parameter in function f1. Please note that these variables
are different, i.e., both share the same name but are two distinct variables defined inside two different functions. When
we print the value of ‘a’ in the main function, the variable ‘a’ defined inside the main function is printed, and when we
print the value of ‘a’ inside f1 function, the variable ‘a’ defined inside the f1 function is printed.
Figure 2: Another example illustrating the scope of a variable

C supports four storage classes of variables, namely, auto, global, static, and register. We will learn about these in the
subsequent readings. We will now understand the detailed layout of the memory allocated to a C program.

Memory Layout of a C Program

Figure 3: Steps of a program compilation and execution

Figure 3 shows the various steps involved in the compilation and execution of a C program, as explained in previous
modules. We write a program and store it on the disk. Then, it is compiled into an executable file, which is also stored on
the disk. When we run the executable, the OS loads the program onto the RAM, and then the CPU executes it line by line.
Now, let us see more deeply how the memory allocated to a program is divided into various segments. Figure 4 illustrates
this.
Figure 4: Various memory segments inside the memory allocated to a program on the RAM

Figure 4 shows a detailed view of the memory allocated to a program in the RAM and how it is logically divided into four
segments:

• The Stack segment stores the memory allocated to the functions and variables declared inside the functions.

• The Heap segment stores dynamically allocated memory, i.e., variables declared using malloc, calloc, and
realloc functions. We will learn more about this in the subsequent modules.

• The Data segment stores global and static variables declared in the program.

• The Text segment stores the executable code that we are executing.

Observe the two red arrows carefully in the figure that indicate the stack segment starts from the top of the allocated
memory and grows downwards. The heap segment starts from somewhere in between and grows upwards toward the
stack segment.

We will see the details of how variables are stored in these memory segments in the next reading.

Reading Summary:

In this reading, you have learned the following:

• What are the different storage classes in C

• How to identify the scope of the variable used in a C program

• Various segments in the memory allocated to a program on the RAM


C Programming Practice Lab 3

Assignment Brief

PracticeLab3_Question1

The C program in the file Question1.c contains an error. Please point out the error, the type of error,
and how to fix the error, and then understand how the error-free program gives an output 4.

PracticeLab3_Question2

The C program in the fileQuestion2.c contains many variables, some with the same variable names
also. Show the datatype, storage class, and scope of each variable in the C program, and also give the
final output of the program.

PracticeLab3_Question3

For the C program in file Question3.c, you need to show in which segment of the RAM will each of the
variables and functions declared in the program get stored. What will be the function frame in which
the variables are stored inside the RAM? Solve this question by making a diagram of the memory
layout of a C program.
Storage Classes of Variables in C
Reading Objective:

In this reading, you will learn about auto, global, and static storage classes in C. You will also learn how variables of
different storage classes are stored in different memory segments allocated to our program.

Main Reading Section:

There are four kinds of storage classes in C – auto, global, static, and register. We will describe auto, global, and static
classes in detail as follows:

Auto Storage Class:

• This is the default storage class assigned to any variable that is declared inside a function or block.

• The scope of an auto variable is limited to the block in which it is declared.

• An auto variable is stored in the stack segment of the memory.

• The initial value of an auto variable is a garbage value. Garbage values can be anything without meaning.

• An auto variable remains in the memory until the execution of the block in which it has been declared is
completed.

Figure 1: Illustrating auto variables in a C program

Let us look at figure 1, where an auto variable ‘a’ is defined within the main function. This is local to the main function,
can be accessed and modified anywhere within the main function, and cannot be accessed outside the main function.
This variable’s default value before being initialized to 5 is a garbage value. And it exists in the memory until the main
function is executed and destroyed when the main function or the program is completed.

Similarly, another auto variable, ‘b,’ is defined inside the block in red braces. This is local to those red braces. It can only
be accessed and modified within those red braces and not elsewhere. It exists in the memory until the block in red braces
completes execution. As a result, the printf statement of the main function, which attempts to print the values of
variables ‘a’ and ‘b,’ would give an error. This is because the variable ‘b’ is not accessible outside of the block in which it is
defined (red braces).
Similarly, another auto variable, ‘a,’ is defined inside the function f1. This can only be accessed and modified inside the
function f1 and not accessible outside of f1.

It is important to note in the above example that there are two variables with the name ‘a.’ One is red ‘a’ in the main
function, and the other is grey ‘a’ in the f1 function. Note that both are distinct and occupy different locations in the
memory.

Global Storage Class:

• Global variables are variables declared outside all the blocks or all the functions.

• The scope of a global variable is the entire program. It can be accessed and modified from anywhere or any
function in the program.

• The initial default value of a global variable is 0.

• All global variables are stored in the data segment inside the memory.

• A global variable’s lifetime is throughout the program execution; that is, they exist in the memory during the
entire program execution.

Figure 2: Illustrating global variables in a C program

Let us look at the program in Fig. 2. The variable ‘a’ in yellow color is a global variable defined outside all the functions.
Another variable, ‘a,’ in grey color, is defined inside the function f1. Then, a third variable, ‘a,’ in blue color, is defined
inside the main function. All three are distinct. The first printf statement in the function f1 prints the value of global ‘a.’
Then the subsequent statement defines the grey ‘a.’ So, for all subsequent statements inside the function f1, variable ‘a’
refers to the grey ‘a’ defined inside f1. So, the second printf statement inside the function f1 prints the value of grey ‘a’.
The value of ‘a’ printed inside the main function refers to the value of blue ‘a’.
Now, let us see how the auto and global variables are stored in various segments of the program memory. Figure 3 shows
that the global variable ‘a’ in yellow is stored inside the data segment. The auto variable ‘a’ in grey is stored inside the
stack segment, specifically inside the frame allocated to the function f1. The auto variable ‘a’ in blue is also stored inside
the stack segment, specifically inside the frame allocated to the function main.

Figure 3: Auto and global variables in memory

Let us now discuss the static storage class in C.

Static Storage Class:

• The variables of the static storage class are declared using the keyword static. Example: static int a;.

• The scope of a static variable is limited to the function or the block in which it is defined.

• All static variables are stored in the data segment of the memory.

• Static variables have 0 as their initial default value.

• The lifetime of a static variable is until the program terminates. The value assigned to a static variable remains
even after the function or block in which it is defined terminates.

Let us illustrate static variables with the program. Figure 4 contains two programs. Both programs have similar structures
except that the variable ‘y’ defined inside the for loop is an auto variable in the program on the left. However, it is a static
variable in the program on the right.
Figure 4: Example 1Illustrating static variables’ behavior

The variable ‘y,’ as an auto variable in the left program, is created and destroyed for each iteration of the for loop.
Because ‘y’ resides in the for loop’s stack frame, which is destroyed and created with each iteration. Therefore, variable
‘y’ cannot persist data stored in it between multiple iterations of the loop. In the program on the right, the variable ‘y,’ as
a static variable, is created only once in the first iteration of the loop. It is stored in the data segment that is not destroyed
after each iteration of the loop, and the value stored in it persists between iterations. This static variable ‘y’ is not created
and destroyed for each iteration of the loop; therefore, the output is 10 20 30 and not 10 10 10 as produced for the case
where ‘y’ was an auto variable.

Figure 5 shows another example illustrating the behavior of a static variable. The main difference between the programs
shown in Figures 4 and 5 is that the for loop performs a function call. The rest of the behavior is essentially the same. The
value of static variable ‘i’ persists across function calls. We recommend you take the program given in Figure 5 as an
exercise and understand how the programs behave.

Figure 5: Example 2 Illustrating the static variables’ behavior

Let us now examine how static variables are stored in the memory.

Figure 6 illustrates how the static variable ‘i’ resides in the data segment of the memory allocated to our program.
Figure 6: Static variables in the program memory

Let us consider Table 1 below, which summarizes the storage classes in C.

Storage Storage Default


Scope Lifetime
Classes Place Value

garbage Local to a block Until the block or the function in which


auto stack
value or a function declared is executing.

Global to all Until the entire program finishes


global data zero
functions execution.

Until the entire program finishes


Local to a block
static data zero execution, they also retain values betwe
or a function
function calls.

Table 1: Summary of storage classes in C

Consider the following exercise for reflect and practice: Given a program below. Please compile and execute it to observe
the output.
Reading Summary:

In this reading, you have learned the following:

• The differences between auto, global, and static variables in C

• How variables of different storage classes are stored in appropriate segments inside the memory allocated to a
program on the RAM

• Some programs that use variables of various storage classes


C Programming Practice Lab 4

Assignment Brief

PracticeLab4_Question1

Many variables are declared in the C program in the file Question1.c with the name b. Figure out which
printf will print the value of which b variable and what will be the program output.

PracticeLab4_Question2

The C program given in the file Question2.c is intended to print a dot three times and terminate. But due
to some error, it prints a different number of dots. Figure out what is causing the error and fix it.

Expected Output

...

PracticeLab4_Question3

For the C program in the file Question3.c, you need to figure out if there is some error in the program or if it
will execute successfully. In case it executes successfully, you need to show what will be the output of the
program, and if it has an error, what is the error?
Solutions to Practice Labs

Practice_Lab_1/Q1

main.c

oddeven.c

Practice_Lab_1/Q2

main.c
average.c

Practice_Lab_1/Q3

// Calls a function that prints an inverted triangle of height N, where N is


asked from user and supplied to the function.

#include <stdio.h>

void printInvertedTriangle(int);

int main(void)
{
int n;
printf("Enter the height N: ");
scanf("%d", &n);

printInvertedTriangle(n);

return 0;
}

void printInvertedTriangle(int n)
{
int spaces = 0;
int rowLen = 2*n-1;
for(int i=1 ; i <= n ; i++){
for(int j=1 ; j<= rowLen ; j++){
if(j<=spaces)
printf(" ");
else
printf("*");
}
spaces++;
rowLen = rowLen - 1;
printf("\n");
}
}
Practice_Lab_2/Q1

// To check if the number is an armstrong number and a perfect number.

#include <stdio.h>

int isArmstrongNumber(int);
int isPerfectNumber(int);

int main()
{
int num;
printf("Input a number to check: ");
scanf("%d", &num);

if(isArmstrongNumber(num)){
printf("%d is an Armstrong number.\n", num);
}
else{
printf("%d is NOT an Armstrong number.\n", num);
}

if(isPerfectNumber(num)){
printf("%d is a Perfect number.\n", num);
}
else{
printf("%d is NOT a Perfect number.\n", num);
}

return 0;
}

int isArmstrongNumber(int num)


{
int lastDigit, sum = 0, n = num;
while(n!=0)
{
lastDigit = n % 10; // find the last digit of the number
sum += lastDigit * lastDigit * lastDigit; //calculate the cube of the
last digit and adds to sum
n = n/10; // remove the last digit if the number
}
return (num == sum); // returns 1, i.e. True, if it is Armstrong
}

int isPerfectNumber(int num)


{
int i, sum = 0;
// there can not be any divisor of n greater than n/2, excluding n itself.
for(i=1; i<=num/2; i++)
{
// if the number i is divisor of num
if(num%i == 0){
sum += i;
}
}
return (num == sum);
}
Practice_Lab_2/Q2

// program to check if the provided number are palindrome


#include <stdio.h>

int reverseOfNumber(int);
int isPalindrome(int);

int main() {
int n;
printf("Please Enter an number: ");
scanf("%d", &n);

if (isPalindrome(n))
printf("%d is a palindrome.", n);
else
printf("%d is not a palindrome.", n);

return 0;
}

int reverseOfNumber(int n){


int remainder, n_reverse = 0;
while (n != 0) {
remainder = n % 10;
n_reverse = n_reverse * 10 + remainder;
n /= 10;
}
return n_reverse;
}

int isPalindrome(int n){


if(reverseOfNumber(n)==n)
return 1;
else
return 0;
}
Practice_Lab_3/Q1
The scope of the variable a is limited to the for loop in the program given in the question. This is
beca e he fo loop in he q e ion c ea ed a block of i o n hich con ained he ingle
statement that declared the variable a and initialized it to i. To correct the code, we need to pull
the declaration of the variable a out of the for loop. The corrected code is as below

#include <stdio.h>
int main()
{
int i, a;
for (i = 0;i < 5; i++)
a = i;
printf("%d", a);
}

Practice_Lab_3/Q2

The scope of various variables alongwith their storage class and datatypes are given in the
table below
Variable name Datatype Storage Class Scope

i (declared on line7) double global entire program

i (declared on line9) int auto function f1

j (declared on line 14) int auto function main

k (declared on line15) int auto for loop in main

k (declared on line int auto Block inside for loop


20) in main function

Practice_Lab_3/Q3

Stack Segment
frame allocated to function f1
(no variable stored inside the frame allocated to f1)
frame allocated to function a_char
int variable j
short int variable k
frame allocated to function main
char variable b
double variable constant
Data Segment
double variable pi
char variable A
Text Segment
This stores the compiled code corresponding to our C program
Heap Segment

Practice_Lab_4/Q1
Carefully see which printf will access which variable b by using the scope rules. The output of
the program is
b = 65
b = 15
b = 1
b = 7
b = 2
b = 7

Practice_Lab_4/Q2

The variable i declared and initilaised outside the for loop is not making any contribution to the
variable i mentioned in the for loop since we have declared a variable i again in the for loop
initialisation part. So that initialisation is made equal to 0 by the compiler by default and hence
printing the dot 5 times. To get the desired output, properly initilaise i inside for loop to value 2,
then correct answer will be displayed. Here understand the difference in the scope of variables
declared outside the for loop and in the initialisation part of for loop. The fixed code is as given
below

#include <stdio.h>
int main()
{
int i = 1;
for (int i = 2;i < 5; i++){
printf(".");
}
return 0;
}

Practice_Lab_4/Q3

Observe carefully that the loops in functions f1 and f2 are changing the value of the global
variable i. Work out the entire code keeping this in mind. Output of the program given in the
question is
5
11
Lesson 1: Functions in a C Program

11 Functions

Chapter 11, Sections 11.1-7, 11.11, and 11.15


WHAT TO LEARN

Techniques of declaring, defining and invoking a function.


Exploring various ways of handling the return value of a function.
Mechanism of transfer of data from arguments to parameters.
Whether C passes arguments by value or by reference.
Significance of side effect of a function.
Passing single- and two-dimensional arrays as function arguments.
Consequences of calling one function from another.
Techniques of using a function in a recursive manner.
Using storage classes to determine the scope and lifetime of a variable.

11.1 FUNCTION BASICS


C programmers decompose a complex task into independent and manageable modules called
functions. Code sections that are used repeatedly—even by other programs—are also implemented
as functions. It is easier to develop and maintain a C program that delegates most of its work to
separate functions compared to one that uses a monstrous main function to do everything. While
functions like printf and scanf are meant to be treated as black boxes, you must know how to
create functions of your own and integrate them into the main program.
A function is a statement that represents a named body of program code. When it is called or
invoked, the code associated with the function is executed. A function can optionally (i) accept one
or more values as arguments, and (ii) report the outcome of its action by returning a single value to
the caller. A function is called mainly in one of these two ways:
message_of_day(); No argument, returns no value
fahrenheit = c2f(40.2); One argument, returns a value
332 Computer Fundamentals & C Programming

A function is easily identified from the matched pair of parentheses that follow its name.
The message_of_day function is neither passed an argument nor does it return a value. But the c2f
function accepts an argument (40.2) and also returns a value, which here is saved in the variable
fahrenheit. This saved value can be used later in the program. While all functions don’t accept
arguments or return a value, the vast majority of them do.
A function must be declared and defined before it is called or invoked. The declaration specifies
how to invoke the function correctly. The definition provides the implementation, i.e. , the body
of code that will be executed on invocation. Unlike message_of_day which behaves in identical
manner every time it is invoked, the behavior of c2f is determined by the value of the argument
(here, 40.2) that is passed to it.
The functions you create can also call other functions. A function can even call itself, a property that
has important applications in the programming world (like calculating the factorial of a number).
For all of the functions we have used so far, we had main as the caller. This is a special function
that every standalone C program must have because a program commences execution by calling
main. We have more to say on the main function later in this chapter.
In this chapter, we’ll create some functions from scratch and rewrite some of our previous programs
using them. In each case, the implementation (i.e., definition) of a function occurs in the same
file that calls it. So a function defined in one program can’t be reused in another program without
replicating the declaration and definition. We’ll use the techniques mandated by ANSI for creating
functions and ignore the techniques originally proposed by K&R C. Chapter 17 examines the
techniques of isolating functions into separate files so they can be used by all programs.

Takeaway: A function must be declared and defined before it can be used. The declaration
specifies the usage and the definition provides the code that is executed.

11.2 first_func.c: NO ARGUMENTS, NO RETURN VALUE


Let’s now consider our first program, (Program 11.1), that features a simple function named
message_of_day. The function is declared before main, called from the body of main and defined
after main. The program prints four lines—two each by main and the function.
The function, message_of_day, is declared right after the preprocessor directive and before main
in this manner:
void message_of_day(void); Function declared
The declaration or prototype doesn’t specify what message_of_day does, but only how to invoke it.
Since every declaration is also a statement, it must be terminated with a semicolon. This function uses
no arguments (second void) and returns no value (first void) and must thus be called in this manner:
message_of_day(); Function called or invoked
What message_of_day is designed to do is specified in its definition that follows main. By convention,
all function definitions are located after main, even though they may be placed before main as well.
Functions 333

/* first_func.c: Uses a function that has no arguments and returns nothing. */


#include <stdio.h>
/* Function declaration or prototype */
void message_of_day(void); /* Try deleting this statement */
int main(void)
{
printf(“Invoking function ...\n”);
message_of_day(); /* Function invocation or call */
printf(“Returned from function.\n”);
return 0;
}
/* Function definition or implementation */
void message_of_day(void) /* The header */
{
printf(“This function uses no arguments and returns nothing.\n”);
printf(“The next program uses one argument and returns a value.\n”);
return;
}

PROGRAM 11.1: first_func.c


Invoking function ...
This function uses no arguments and returns nothing.
The next program uses one argument and returns a value.
Returned from function.

PROGRAM OUTPUT: first_func.c

The definition contains both a header and a body enclosed by a pair of curly braces. This body
comprises two printf statements and a return statement without a value.
The compiler finds the function declaration consistent with the definition and invocation. When
message_of_day is called, program control moves to the function body where it executes all statements
placed there. The return; statement in the function moves control back to the caller (i.e., main).
Execution then continues with the next statement following the function call.
The return; statement in the function has the same significance as the one we have all along used
in main. It terminates the function and returns control to the caller. However, this function doesn’t
return a value (return; and not return 0;). Even though a return here is not necessary, it’s good
programming practice to include it in every function even though it may return nothing.

Takeaway: A function name occurs in at least three places in a program. The declaration
occurs at the beginning, the invocation takes place in the body of main, and the definition is
placed after main. The compiler will output an error if their forms do not match.
334 Computer Fundamentals & C Programming

Note: We don’t need to provide the definition for functions of the standard library, but we still need
their declaration. The file stdio.h contains the declarations for printf and scanf, the reason why
we have included this file in every program so far.

11.3 THE ANATOMY OF A FUNCTION


A function is an identifier, which implies that the rules related to the naming of variables and arrays
also apply to functions (5.2.1). Thus, the name can comprise letters, digits and the underscore
character where the first character cannot be a digit. Case is significant, so, area and AREA are two
separate functions. Let’s now examine the three main attributes of functions: declaration, definition
and invocation.

11.3.1 Declaration, Prototype or Signature


A declaration is an authentic statement that tells the compiler how the function is invoked.
The declaration is also known as prototype or signature. Since it is placed before main, the compiler
sees it first and then compares it to the invocation and definition. For this comparison, the compiler
determines whether the function
uses any arguments, and if so, the number of such arguments along with their data types.
returns a value, and if so, its data type.
This creates four possible situations—with or without arguments, and with or without return
value. The following syntax and supporting example show how to declare a function that neither
has an argument nor a return value:
void function_name(void);
void message_of_day(void); Used in first_func.c
The first void indicates that the function doesn’t return a value. The second void signifies that the
function uses no arguments. However, most functions use one or more arguments and also return
a value using the following generalized syntax:
return_type function_name(type1 arg1, type2 arg2 ...);
Here, return_type represents the data type of the return value. The function arguments are
represented as arg1, arg2 and so on, where each argument is preceded by its data type, type1, type2
and so forth. An argument can be a variable, constant or expression. Here’s an example:
double area(float length, float breadth);

The area function accepts two arguments each of type float and returns a value of type double.
Since the compiler simply matches the type and number of arguments in the declaration with those
used in the definition, it doesn’t need to know these variable names, and C lets you omit them:
double area(float, float); Not recommended
Functions 335

The prototype has been compacted at the expense of readability. (You can’t do the same in the
definition.) The declarations are the first things we see when viewing the source code. A single-line
declaration should tell us what the function does and what input it needs. By using meaningful
names for both the function and its arguments, we can avoid using separate comment lines.
Note: A function prototype or signature serves as a good reference and prevents you from wrongly
invoking a call. Never fail to include it even if you don’t encounter an error on its exclusion.

11.3.2 Definition or Implementation


The function definition or implementation specifies what the function does when invoked.
It comprises a header and a body where all the work gets done. For a function that accepts arguments
and returns a value, the definition takes this generalized form:
return_type function_name(type1 arg1, type2 arg2 ...) Header
{ Beginning of body
statements;
return expression;
} End of body
The header (the first line) is virtually identical to the declaration except that there’s no semicolon
as terminator. The body is represented by a simple or compound statement that is compulsorily
enclosed within curly braces. The return statement transmits the value of expression back to the
caller. The area function that we just declared would have the following as its definition:
double area(float length, float breadth)
{
double product; Local variable of function
product = length * breadth;
return product;
}
This header contains a comma-separated list comprising the two parameters, length and breadth.
These parameters, which are actually variables, are assigned by their corresponding arguments used
in the invocation. That is, if the function is invoked as rect_area = area(len, width);, the values
of the arguments, len and width, are passed to the parameters, length and breadth, respectively.
The body declares a local variable named product to save the result of a computation. The return
statement finally passes this result back to the caller of the function. The caller often uses a variable
to save this value before it disappears after termination of the function. The area function doesn’t
print anything but simply returns a value.
For functions that don’t return a value (like message_of_day), return is used (if at all) without
expression. In that case, return_type must be specified as void both in the declaration and definition.
11.3.3 Invocation or Call
Once a function has been declared and defined, you know what it does and how it does it. You can
then call it as many times as you want, either from main or from any other function. If the function
doesn’t return a value, it is invoked simply like this:
336 Computer Fundamentals & C Programming

message_of_day();
For functions that return a value, the method of invocation is often different. The programmer
must invoke the function in a way that enables the program to “see” the value. Typically, the return
value is saved in a variable or used as an argument to another function:
rect_area = area(10.0, 20.0); Saves 200.0 in rect_area
printf(“Area = %f\n”, area(10.0, 20.0)); area evaluated first
The compiler normally takes a serious view of type mismatches, but it is a trifle lenient when it
looks at the invocation. If area is called with two ints rather than two floats, the compiler won’t
complain. It is your job to take care of any possible data loss when invoking a function with non-exact
data types in its arguments.
There is one more mismatch that you need to take care of. If a function that returns a value (say, float)
in the function body but doesn’t specify a return type in the declaration, the compiler assumes that
the function returns an int. C11 is strict though; it requires the return type to be explicitly specified.
Note: Both printf and scanf return a value, but more often than not, we ignore this value. We use
these functions mainly for their side effect. However, you would have noticed that in some cases
(9.10.4, 10.4, 10.5.2, 10.10.2), these functions have been used both for their return value and side
effect.

HOW IT WORKS: How Standard Library Functions Are Declared and Defined
The signatures for functions of the standard library are distributed across several “include” files
(having the extension .h) that are read by the preprocessor. One of them, stdio.h, contains—
either directly or indirectly—the prototypes of all functions used for I/O operations. We include
this file simply because we always use an I/O function in our programs. When we use the
string-handling functions, we’ll include string.h.
The definitions for these functions are available in compiled form in a library from where they
are extracted by the linker during the compilation process. For all ANSI C functions, we need
their compiled code and not the source code. This is consistent with the black-box approach
that we need to adopt for functions “designed by others.”

11.4 c2f.c: ONE ARGUMENT AND RETURN VALUE


Program 11.2 uses a function named c2f to convert a temperature from Celsius to Fahrenheit
using the well-known formula that was used in Chapter 6. This function accepts the temperature
in Celsius as argument and returns the converted value in Fahrenheit to main.
The c2f function has been invoked in two ways: (i) by saving its return value, and (ii) as an argument
to printf. When c2f is invoked, the value of its argument, celsius, is passed to its corresponding
parameter, f_celsius. The function also uses a local variable (fheit) to return the converted value
to main. In the first invocation of c2f, this value is assigned to a variable. The next invocation uses
c2f as an expression in printf.
Functions 337

/* c2f.c: Uses a function that accepts an argument and returns a value. */


#include <stdio.h>
float c2f(float cgrade); /* Function declaration */
int main(void)
{
float celsius, fahrenheit;
printf(“Enter a temperature in Celsius: “);
scanf(“%f”, &celsius);
fahrenheit = c2f(celsius); /* Function called here */
printf(“%.2f Celsius = %.2f Fahrenheit\n”, celsius, fahrenheit);
printf(“%.2f Celsius = %.2f Fahrenheit\n”, celsius, c2f(celsius));
return 0;
}
float c2f(float f_celsius) /* Function definition */
{
float fheit; /* Local variable of function */
fheit = f_celsius * 9 / 5 + 32;
return fheit; /* Value returned to main */
}

PROGRAM 11.2: c2f.c


Enter a temperature in Celsius: 40.5
40.50 Celsius = 104.90 Fahrenheit From 2nd printf statement
40.50 Celsius = 104.90 Fahrenheit From 3rd printf statement
Enter a temperature in Celsius: 0
0.00 Celsius = 32.00 Fahrenheit
0.00 Celsius = 32.00 Fahrenheit
Enter a temperature in Celsius: -40
-40.00 Celsius = -40.00 Fahrenheit
-40.00 Celsius = -40.00 Fahrenheit

PROGRAM OUTPUT: c2f.c


Note: The name used for the function argument (celsius) need not be different from the one used
for the parameter (f_celsius). Even though Section 11.5.2 explains why that is so, you should still
use different names to avoid confusion.

11.5 ARGUMENTS, PARAMETERS AND LOCAL VARIABLES


A function uses its own set of variables for carrying out its tasks. In the definition of c2f
(Program 11.2), they are seen in the parameter (f_celsius ) and function body (fheit ).
These variables are created in a separate region of memory that is almost exclusively reserved
for functions. When a function terminates, this space is deallocated by the operating system and
made available for the next function call. The following sections explain how these factors affect
the accessibility and lifetime of variables used in a function.
338 Computer Fundamentals & C Programming

11.5.1 Parameter Passing: Arguments and Parameters


To understand the concept of parameter passing, we need to look at function arguments from
two viewpoints—the caller and called function. Consider the following function definition and
invocation taken from the previous program, c2f.c:
float c2f(float f_celsius) {...} Definition
fahrenheit = c2f(celsius); Invocation
When the caller (here, main) invokes a function, it assigns values to the arguments or actual arguments of
the function. The called function accepts these values into its parameters or formal arguments.
When we invoke c2f as shown above, the value of celsius (the argument) is assigned to f_celsius
(the parameter). This transfer of value from argument to parameter is known as parameter passing.
Unfortunately, the terms argument and parameter are often used interchangeably. In this text,
however, argument means the “actual argument” and parameter means the “formal argument” to
a function.
Note: An argument and parameter represent two sides (the caller and called function) of the same
coin. A function call causes data to flow from the argument to the parameter. The reverse is, however,
not possible (though we can simulate the reverse flow using pointers).

11.5.2 Passing by Value vs Passing by Reference


We must now clearly understand how arguments are passed to parameters of the called
function. There are some conflicting points of view that have often been fiercely debated by the
C community. The question is whether arguments are passed by value or by reference, or whether
there is a mix of both. We’ll attempt to resolve this conflict in this section.
In C, all function arguments are passed by value, i.e., they are copied to their corresponding
parameters. There is no exception to this principle. When c2f is called, the value of celsius is copied
to f_celsius. Since a parameter resides in its own memory space, celsius and f_celsius occupy
separate memory locations (Fig. 11.1). You can’t subsequently change celsius by changing
f_celsius, i.e., you can’t change an argument by changing its copy, the parameter.
It is thus not necessary to maintain separate names for arguments and parameters in the declaration,
definition and invocation. In the program c2f.c, we could have used the same variable name
(celsius) in the function definition as well.
Unlike C++, C doesn’t support passing by reference, where both argument and parameter occupy
the same memory location. However, in C, a variable (p) can store the memory address of another
variable (x), and you can use p to indirectly change the value of x. Thus, if you invoke a function
with p as argument, its copied parameter will still contain the address of x. You can then use this
copy of p in the function to change x that is defined in the caller. Working inside a function you
can change a variable defined outside the function!
Some like to think of the above feature as passing by reference or “the effect” of passing by reference,
but we’ll not take this view and continue to maintain that C passes arguments by value only. Let’s
now summarize our observations in the following manner:
Functions 339

Function Arguments in Caller Function Parameters in Called Function

result = power(base, exponent); long power(short f_base, short f_exponent)


{
short inta = 0;
...
...
return f_result;
}

Memory Layout

FIGURE 11.1 How Function Arguments and Parameters Are Organized in Memory
When a function is called, its arguments are copied to the parameters. But if the function changes
a parameter, then the change is not seen in the caller. However, if an argument contains the address
of a variable, then the corresponding parameter of the argument can be used to change that variable.
The preceding paragraphs relating to the use of memory addresses provided a gentle introduction
to pointers, touted as the strongest feature of C. We’ll unravel the mystery of pointers in Chapter 12,
but we’ll encounter the topic in a disguised form when we use arrays as function arguments in
Section 11.8.
Note: Even if the changed value of a parameter is not seen by the caller, a function can make it
available through the return statement. However, a function can return only one value, so you can’t
return multiple parameters in this manner.

11.5.3 Local Variables


A function has its own set of local variables that are defined in its implementation. For instance,
the c2f function uses a local variable, fheit, to compute the result it returns to its caller. Unlike
parameters, local variables cannot be assigned by the caller. However, parameters and local variables
have the following features in common:
Their names don’t conflict with identical names defined in the caller or any other function.
They can be accessed only in the function they are defined in. They are neither visible in the
caller nor in any other function.
They last as long as the function is active. This means that they are created every time the
function is called.
340 Computer Fundamentals & C Programming

From the above and as shown in Figure 11.1, we can conclude that every function call has a separate
area earmarked for it. This area is deallocated after the function terminates. But since main is the
first function to be called and the last to terminate, its variables have a longer lifestime compared
to variables and parameters of other functions. Visibility and lifetime are important attributes of
variables that we need to address, and soon we will.

11.5.4 swap_failure.c: The “Problem” with Local Variables


Program 11.3 demonstrates the limited visibility of function parameters and local variables.
It makes an unsuccessful attempt to swap the values of two variables that are passed as arguments
to a function. In the process, it establishes that the area of memory allocated to function arguments
is separate from that used by the parameters and local variables of a function.

/* swap_failure.c: Establishes the impossibility of swapping two values


using parameters and local variables of a function. */
#include <stdio.h>
void swap(short x, short y); /* Function returns no value */
int main(void)
{
short x = 1, y = 100, temp = 0;
printf(“In main before swap: x = %hd, y = %hd\n”, x, y);
swap(x, y);
printf(“In main after swap: x = %hd, y = %hd\n”, x, y);
printf(“In main temp seen as %hd\n”, temp);
return 0;
}
void swap(short x, short y) /* x and y local to swap */
{
short temp; /* Local variable */
temp = x;
x = y;
y = temp; /* Values swapped inside function */
printf(“In swap after swap: x = %hd, y = %hd\n”, x, y);
printf(“In swap temp set to %hd\n”, temp);
return;
}

PROGRAM 11.3: swap_failure.c


In main before swap: x = 1, y = 100
In swap after swap: x = 100, y = 1
In swap temp set to 1
In main after swap: x = 1, y = 100
In main temp seen as 0

PROGRAM OUTPUT: swap_failure.c


Functions 341

To reinforce our point, we have used identical variable names (x, y and temp) in both calling and
called function. The swap function interchanged the values of x and y all right, but it changed the
parameters and not the original arguments. In main, x and y remain unchanged at their initial
values (1 and 100) even after swapping. The function also failed to change the the value of temp in
main by changing its own local variable of the same name.
Because the variables of a function have their own memory space, changes made to them can’t
impact the variables used in the caller. However, we had observed in Section 11.5.2 that a function
can change the value of a variable defined in its caller only if it knows the address of that variable.
Using this property, we’ll modify this swap function in Chapter 12 to make it finally work. You’ll soon
find that this restriction also vanishes when the argument is an array.

11.6 THE RETURN VALUE AND SIDE EFFECT


For a function that returns a value, C considers the transmission of this value to be its primary
task. Any other action performed by the function body is considered to be the side effect. Displaying
a message, fetching a line of text, evaluating an expression or updating an external file are some of
the common side effects seen in functions. From this viewpoint, the printing of a string with printf
and accepting keyboard input by scanf are considered by C to be side effects.
Some functions return a value while others don’t. A function that returns a value is evaluated as
an expression, whose value is the return value of the function. We often save this return value for
further processing or directly use it as an argument to another function. We have done both using
the c2f function:
fahrenheit = c2f(celsius);
printf(“%.2f Celsius = %.2f Fahrenheit\n”, celsius, c2f(celsius));
If a function that returns a value also has a side effect, it is for the programmer to determine whether
the function is to be used for its side effect, or return value, or both. In all of the programs we have
used so far, printf and scanf have always been used for their side effect. It’s only in a handful of
cases that we have taken advantage of their return value as well:
while (scanf(“%hd”, &arr[i++]) == 1) (10.4)
while (printf(“Integer to search (q to quit): “)) (10.10.2)
Here, scanf is used both for reading keyboard input (side effect) and also detecting the end of
input (return value). Similarly, printf is used for displaying a message (side effect) and to return
a true value so that the while loop runs forever.
There are times when you would want the return value to be interpreted as true or false.
For instance, if you want a function to conduct a leap year check in the following manner:
if (is_leap_year(2016)) Function may return either 0 or 1
it means that the is_leap_year function simply needs to return a 0 or 1. The possible definition
of this function could then be this:
342 Computer Fundamentals & C Programming

int is_leap_year(short year)


{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
return 1; /* Returns true */
else
return 0; /* Returns false */
}

A function that doesn’t return a value can’t be used in any of the ways shown previously.
Because the message_of_day function (11.2) doesn’t return a value, you can neither assign it to
a variable nor use it in a control expression. A function that belongs to this category can’t have any
side effect because whatever it does is its only effect. Most functions of the standard library return
a value but there are three that don’t: abort, exit and assert.

11.7 prime_number_check2.c: REVISED PROGRAM TO CHECK PRIME NUMBERS


Program 11.4 modifies a previous version (Program 8.9) to demonstrate some features of the return
value. It takes a number from a user, uses one function (is_not_prime) to check for prime and
then another one (to_continue) to determine whether to continue or not. Since the algorithm for
determining a number for prime has already been discussed, we’ll confine our discussions mainly
to the way the return values of the two functions are captured and used.

/* prime_number_check2.c: Modifies a previous version to use a function for


determining a prime number. Shows intelligent use of return value. */
#include<stdio.h>
int is_not_prime(int num);
int to_continue(void);
int main(void)
{
int number, divisor;
while (printf(“Number to test for prime: “)) {
scanf(“%d”, &number);
while (getchar() != ‘\n’) ; /* Note ; is on same line this time */
divisor = is_not_prime(number); /* Is 0 for prime number ... */
if (divisor > 0) /* ... the lowest factor otherwise */
printf(“%d is not prime, divisible by %d\n”, number, divisor);
else
printf(“%d is prime\n”, number);
if (!to_continue()) /* If not true, i.e. is 0 */
break;
}
return 0;
}
Functions 343

int is_not_prime(int num)


{
int divisor = 2;
while (divisor < num) {
if (num % divisor == 0)
return divisor; /* divisor is non-zero */
divisor++; /* Try next higher number */
}
return 0; /* Executed when number is prime */
}
int to_continue(void)
{
char answer;
printf(“Wish to continue? “);
answer = getchar();
if (answer == ‘y’ || answer == ‘Y’)
return 1;
else
return 0;
}

PROGRAM 11.4: prime_number_check2.c


Number to test for prime: 13
13 is prime
Wish to continue? y
Number to test for prime: 377
377 is not prime, divisible by 13
Wish to continue? n

PROGRAM OUTPUT: prime_number_check2.c


The printf function always returns a non-zero value (the number of characters written), so it
is a valid control expression for an infinite loop. The number input with scanf is checked for
prime by the function is_not_prime. The return value is saved in the variable divisor because it
is subsequently used twice.
Once a number has been established as prime or otherwise, the to_continue function is invoked
to determine whether the user wants to input another number. The function definition shows
0 and 1 as the only possible return values, which implies that the function can be used as a logical
expression. When this expression is used with the NOT operator (!), the while loop in main is
exited only if to_continue evaluates to false.

11.8 USING ARRAYS IN FUNCTIONS


Functions also use arrays as arguments. You can pass both an individual array element and the name
of an array as argument to a function. The principles of parameter passing apply to arrays too, but
350 Computer Fundamentals & C Programming

11.11 CALLING A FUNCTION FROM ANOTHER FUNCTION


Just as main calls a function, a function can also call another function. The called function can in turn
call another function, and so on. In this scenario, the returns of all intermediate functions are kept
pending until the innermost function returns. The remaining functions then return in the reverse
sequence of their invocation. Ultimately, the outermost function returns control to main (Fig. 11.2).
As long as the return of a function is kept pending, its parameters and local variables continue
to occupy space in memory. Freeing of memory begins with the return of fn and continues
progressively as the other functions return. However, the return value, if any, of every function is
captured by its caller before the called function is extinguished.

Call Call Call Call


main f1 f2 f3 ... fn
Return Return Return Return

FIGURE 11.2 Functions Calling Other Functions

11.11.1 time_diff.c: Program to Compute Difference Between Two Times


Let’s now take up Program 11.8, which computes the difference between two times input by the user,
each in hh mm format. The main function calls the time_diff function, which computes the elapsed
time in minutes. This function in turn calls the absolute function to ensure that a negative value
is converted to positive before it is returned to main. The function call sequence is shown below:
main time_diff absolute

Even though the absolute function is called from time_diff, it must be declared before main.
This is because a function can’t be declared in the body of another function. scanf accepts the two
times as four variables, which are then passed as arguments to time_diff. After time_diff returns,
its return value is printed. Note that main can’t return until time_diff returns, which can do so
only after absolute returns.
The time_diff function converts the two times to minutes elapsed since 00:00 hours (on the same
day) before they are subtracted. (The UNIX clock is maintained as the number of seconds elapsed
since January 1, 1970, 00:00 hours.) Because the times are not validated, a negative value is possible,
so a call to the absolute function is made. This function simply returns the absolute value of the
number using a conditional expression.
Note: Most implementations of C allocate a fixed chunk of memory (the stack) for function variables
and parameters. This space, which is usually adequate for multiple invocations of functions, is
progressively eaten up as one function calls another, which in turn calls another, and so on. The space
may be totally consumed when a function calls itself by recursion and fails to terminate. The functioning
of the stack is taken up later in the chapter.
Functions 351

/* time_diff.c: Calculates the difference between start and end times.


Calls one function from another function. */
#include <stdio.h>
int time_diff(short h1, short m1, short h2, short m2);
int absolute(short a_mins);
int main(void)
{
short mins, hours1, hours2, mins1, mins2;
printf(“Enter start & endtimes as HH MM HH MM: “);
scanf(“%hd%hd%hd%hd”, &hours1, &mins1, &hours2, &mins2);
mins = time_diff(hours1, mins1, hours2, mins2); /* Calls function1 */
printf(“Difference between the two times = %hd minutes\n”, mins);
return 0;
}
int time_diff(short h1, short m1, short h2, short m2) /* Function1 */
{
short net_mins;
net_mins = h1 * 60 + m1 - (h2 * 60 + m2);
return absolute(net_mins); /* Calls another function */
}
int absolute(short a_mins)
{
return (a_mins > 0 ? a_mins : a_mins * -1);
}

PROGRAM 11.8: time_diff.c


Enter start & endtimes as HH MM HH MM: 12 35 7 30 12:35 and 7:30
Difference between the two times = 305 minutes
Enter start & endtimes as HH MM HH MM: 14 45 6 15 Negative value
Difference between the two times = 510 minutes
Enter start & endtimes as HH MM HH MM: 2 30 0 0
Difference between the two times = 150 minutes

PROGRAM OUTPUT: time_diff.c

Takeaway: Because each function is allocated separate space in the stack, no conflicts can
occur between variable names used in the calling and called functions.

11.11.2 power_func.c: Computing the Sum of a Power Series


Consider the task of computing the value of an expression that represents the sum of a series
of n terms where the nth term is expressed as nn. The expression is formally represented in the
following manner:
11 + 22 + 33 + 44 + ... + nn
352 Computer Fundamentals & C Programming

HOW IT WORKS: When exit Is Better than return


Except when used in main, return doesn’t terminate a program. When functions are nested and
you need to quit the program from an inner function, it is impractical to use return. In such
situations, use the exit function which bypasses the function hierarchy including main, and
switches control to the operating system. The situation is analogous to the use of goto rather
than break when coming out of a deeply nested loop.
The exit function is invoked with a return value as argument and needs the file stdlib.h,
so you must include this file when using exit. The call exit(0); made from any function
simply terminates the program (after performing the necessary cleanup operations). UNIX-C
programmers (including this author) prefer to use exit rather than return for terminating
a program.

For this purpose, we need to reuse the code for computing the power of a number that we have
developed previously (Program 8.7). The following program, (Program 11.9), uses the same code—
this time as the power function—to evaluate each term of the series. Another function, compute_sum,
calls this function repeatedly to compute the final sum.

/* power_func.c: Computes the sum of n terms where the nth term is n^n.
Calls the power function from another function. */
#include <stdio.h>
long power(short f_base, short f_exponent);
unsigned long compute_sum(short f_terms);
int main(void)
{
short m_terms;
printf(“Enter number of terms to sum: “);
scanf(“%hd”, &m_terms);
printf(“\nSum of %hd terms = %ld\n”, m_terms, compute_sum(m_terms));
return 0;
}
unsigned long compute_sum(short f_terms)
{
short i = 1, base = 1;
unsigned long sum = 0;
while (i++ <= f_terms) {
sum += power(base, base); /* Each term evaluated and summed here */
printf(“%ld “, sum); /* Prints progressive sum */
base++;
}
return sum;
}
Functions 353

long power(short f_base, short f_exponent)


{
short i = 0;
long result = 1;
while (++i <= f_exponent)
result *= f_base; /* Multiply base by itself */
return result;
}

PROGRAM 11.9: power_func.c


Enter number of terms: 3
1 5 32
Sum of 3 terms = 32
Enter number of terms: 5
1 5 32 288 3413
Sum of 5 terms = 3413

PROGRAM OUTPUT: power_func.c

11.12 sort_bubble.c: ORDERING AN ARRAY USING BUBBLE SORT


After having used the selection sort algorithm (Program 10.7), let’s learn to use the bubble sort
technique of ordering an array. For an ascending sort and beginning from the left, two adjacent
elements are compared, and if the left element is greater than the right one, the two elements are
swapped. With each pass, the lower values from the right “bubble” their way to the left. The entire
array is sorted using n - 1 passes where n represents the size of the array. Program 11.10 features
a number of interesting techniques that you should incorporate in other programs.
The program uses the bubble_sort function which, in turn, invokes the compare function to compare
two adjacent elements. The fourth argument of bubble_sort is unusual: it determines whether
the multi-row output representing the progress of sort should be printed. Use of properly named
symbolic constants (like ASCENDING and PRINT_YES) rather than actual numbers makes it easy to
understand what the constants actually represent.
The compare function performs a simple task; it returns a true or false value from the comparison
of two array elements. Also, depending on whether bubble_sort uses ASCENDING or DESCENDING as its
third argument, compare uses either x < y or y > x as the expression for comparison. By tweaking
this function, you can handle complex sorting conditions—say, sorting only odd numbers.
Because we opted for printing the progress of sort (PRINT_YES instead of PRINT_NO), we have
a clear picture of the working of the bubble sort algorithm. Observe how the lowest value (0) needs
11 passes (one less than the size of the array) to move from the extreme right to the extreme left.
In many cases, you’ll find that a lesser number of passes will suffice.
Lesson 2: Scope Rules and Storage Classes of Variables

362 Computer Fundamentals & C Programming

Variables declared inside main are allocated space from the same memory bank that other function
variables and parameters draw upon. Thus, these variables are also local in nature and are not
Chapterto3,other
accessible Sections 11.16-11.17
functions. But they have a longer lifespan because main terminates after all other
functions have terminated. We’ll revisit main in Chapter 13 to understand how it is invoked with
arguments and how its header changes accordingly.

11.16 VARIABLE SCOPE AND LIFETIME


When working with functions, you’ll face situations where two or more functions need to access
the same variable. You have already faced that situation with Program 11.11, where the variable
count was used by both main and the factorial function. Sometimes, you’ll need to hide a variable
even within the same function. Further, the same variable may also be used by multiple program
files. C offers a number of features that adequately address these issues.
Apart from having a data type, a variable has space and time attributes. The space attribute signifies
the scope or visibility of the variable. This is actually the region of the program where the variable
is visible and can be accessed. A variable may have one of four possible scopes: block, function,
file and program. A variable having block scope is visible in the block in which it is declared.
A variable having file scope is visible in the entire file.
The time attribute determines the lifetime of the variable, i.e., its time of birth and death. A variable
declared inside a function lives as long as the function is active. One declared before main is alive
for the duration of the program.
11.16.1 Local and Global Variables
Variables declared inside a function are visible only in the function. They have local scope, the
reason why they are called local variables. Variables declared outside a function are known as global
variables. The scope of a global variable extends from the point of its declaration to the rest of the
file. When a variable is declared before main, it is truly global across the entire program (which
may be spread across multiple files). We declared a global variable (count) in Program 11.11, and
could access it both in main and a function called from main.
We’ll now examine the lifetime and initialization of variables. A global variable has a lifetime over
the complete program. By default, it is initialized to zero at the start of program execution. On the
other hand, a local variable lives as long as its function lives. It has no default value, so it would be
wrong to assume zero or NUL as its default value.
11.16.2 Variables in a Block
Visibility, however, gets affected when a variable is declared inside a block. Though we have not
exploited this concept yet, be aware that you can create a block, enclosed by curly braces, anywhere
in a program. In the code segment shown in Figure 11.4, x is a global variable, y is a local variable
of main and z is a local variable of the inner block.
While x can be accessed in the entire program, y is visible only in main, including the inner block.
The visibility of z is limited to the inner block in which it is declared. Had this block contained
nested blocks, then all of the variables would have been accessible in the nested blocks too.
Functions 363

int x = 2; /* Global variable */


int main(void)
{
int y = 22; /* Local variable of main */
{
int z = 222; /* Local variable of block */
/* x, y and z visible here */
} /* End of block */
/* x and y visible here, but not z */
}
int func(void)
{
int p = 5; /* Local variable of func */
/* x and p visible here, but not y and z */
}

FIGURE 11.4 Scope of Global and Local Variables

11.16.3 Variable Hiding


If an inner block re-declares a variable, then its new value hides both the global value and the old
value assigned in the outer block (Fig. 11.5). A variable can be hidden by creating another one
having the same name in memory. Hiding doesn’t overwrite the previous value, which becomes
visible when the inner block is exited.

int x = 2; /* Global variable */


int main(void)
{
int y = 22; /* Local variable of main */
{
/* x and y visible here but only until they are re-declared */
int x = 4; /* Hides the value 2 */
int y = 44; /* Hides the value 22 */
int z = 88; /* Nothing to hide */
}
/* Original values of x and y visible here, but z is undefined */
}

FIGURE 11.5 Re-Defining Variables


The inner block this time re-declares (or re-defines) two variables, x and y. When the inner block
is exited, none of the values that were seen inside the block are seen outside it. The old values of
x and y become visible once more.

Takeaway: A local or global variable ceases to be visible in an inner block if that block also
declares a variable with the same name. However, the original variable becomes visible when
the block is exited.
364 Computer Fundamentals & C Programming

11.17 THE STORAGE CLASSES


The scope, lifetime and initial value of a variable are determined by its storage class. So far, we have
used the default storage class for variables (automatic), but you also need to know the following
storage classes supported by C:
automatic (auto)
static (static)
external (extern)
register (register)
The storage class also determines the way the compiler allocates memory for a variable. The keywords
shown in parentheses are mutually exclusive, and only one of them can be used as a qualifier in
a variable declaration. Table 11.1 makes a comparison of the key features of the four storage classes.

11.17.1 Automatic Variables (auto)


If a function is invoked repeatedly, its local variables are created and destroyed automatically as
many times as the function is called. For this reason, local variables of a function are also known
as automatic variables. The keyword auto used as a qualifier in a declaration makes a variable an
automatic one. We have never used it though because, by default, a local variable has auto as its
storage class. But we can also explicitly specify a variable as automatic:
int main(void)
{
auto short count = 0; auto can be omitted
count++; Always has the value 1
...
}
An automatic variable has function scope and a junk default value. As seen above, you can’t use an
automatic variable to count the number of times a function is called because every call initializes
the variable to the same value. A global variable partially solves the problem since it is initialized
once, but by depending on a variable that is defined outside main, a function loses its status as
a self-contained unit. C has a simple solution to this problem which is discussed next.
Note: Automatic variables are pushed onto the stack when the function is called and popped out
after execution is complete (11.13.2—Inset—How It Works). For a variable to retain its value
for the duration of the program, it must not be stored in the stack, i.e., it can’t have the auto
storage class.

11.17.2 Static Variables (static)


If a local variable is assigned the static storage class, then it will retain its value between function
calls. A static variable (keyword: static) is created and initialized just once, when the function
containing its declaration is executed. It remains alive (is static) even after the function terminates.
Functions 365

This is how we declared a static variable in Section 11.13 to count the number of times the main
function was called within main:
static long count; Default value is zero
By default, a static variable is initialized to zero, but even if it is initialized explicitly, this is done
just once. The following definition initializes count to 5 and increments it with every call made
to the function:
void f(void)
{
static long count = 5;
count++; count incremented with every call
}
For some reason, if you want a static variable to be initialized every time the function is called,
then this is the way to do it:
static long count; First initialized to zero ...
count = 5; ... and then assigned the value 5
The static qualifier can also be used with a global variable (defined before main), in which case
the variable is accessible only in the current file. The static attribute doesn’t affect the initialization
property because a non-static global variable is initialized once anyway.
A function may also be declared as static, and that would make the function inaccessible in
other files.

Takeaway: A local variable of a function is created and extinguished every time the function
is called and terminated. This default auto feature can be overridden with the static qualifier
in which case the variable retains its value across multiple invocations of the function.

11.17.3 External Variables (extern)


A global or external variable is defined outside a function and is visible everywhere—even in other
files that constitute the program. However, a variable declared after main is not visible in main:
int main(void)
{
...
return 0;
} End of main
int x = 10; Not visible in main
To make matters worse, a variable declared at the end of the file is not visible anywhere—even in
functions that are defined prior to the variable declaration. To make the variable visible at selected
locations in the file, you need to assign the extern storage class to the variable. In the current scenario,
for x to be visible in main, it must be declared there with the extern qualifier:
366 Computer Fundamentals & C Programming

TABLE 11.1 Storage Classes of Variables


Storage Class Visibility Lifetime Default Remarks
Value
auto In function/block Duration of Junk Default class for variable
where declared function/block declared in function/block
static In function/block Duration of Zero Retains value between
(in function) where declared program/block multiple function calls
entry
static (outside From point of Duration of Zero Retains value across
function) declaration to end of program multiple files
file
extern In function/block, file Duration of Zero Must be declared at one
or multiple files program place without extern
register In function/block Duration of Junk Stored in CPU register for
where declared function/block faster access

int main(void)
{
extern int x; x declared here ...
...
return 0;
}
int x = 10; ... and defined here
The extern keyword indicates that the variable x is merely declared here and that the definition
has been made elsewhere (here, after main). This means storage for x is allocated only when it is
defined, and the extern statement simply intimates type information to the compiler. There is only
one variable x here using a single storage location. Without extern, x would be a local variable of main
and would have no relation with the one that is defined later.

Takeaway: If a variable x is not visible in a function or a block, use the extern qualifier to make
it visible there. Also, while a regular variable is declared and defined simultaneously, an extern
variable can only be declared. It must be defined “elsewhere.”

11.17.4 extern.c: Using extern to Control Variable Visibility


Program 11.12 explores the visibility of two variables (x and y) and an array (arr) which are
defined after main. x and arr are visible in main because of extern, but y is not. The y in main is an
uninitialized automatic variable that doesn’t share storage with the y defined after main. Note that
x declared in the block is by default not visible after the block is exited, the reason why it had to
be re-declared with extern.
Functions 367

/* extern.c: Uses extern to access in main variables defined below main.


Makes rare distinction between declaration and definition. */
#include <stdio.h>
int main(void)
{
{
extern int x; /* Declares x without defining it */
extern int arr[ ]; /* Declares arr without defining it */
int y; /* Separate local variable */
arr[0] = 10; arr[1] = 20;
printf(“Initial value of x = %d\n”, x);
printf(“Initial value of y = %d\n”, y);
printf(“Elements of arr = %d, %d\n”, arr[0], arr[1]);
x = 10;
}
extern int x; /* Re-declares x without defining it */
printf(“New value of x = %d\n”, x);
return 0;
}
int x = 5; /* Defines x but without using extern */
int y = 50; /* Not seen in main */
int arr[2]; /* Defines arr but without using extern */

PROGRAM 11.12: extern.c


Initial value of x = 5
Initial value of y = -1209016480 Uninitialized value
Elements of arr = 10, 20
New value of x = 10

PROGRAM OUTPUT: extern.c

The extern feature is the only way multiple files can share a variable so that the updates to the
variable are visible at all desired locations. Security would be compromised if a simple global
variable is used without using extern.

Note: When using extern, there must be a single definition of the variable that doesn’t use the
extern keyword.

Tip: If a variable has to be made accessible in selected functions, define the variable after all
functions and then use the extern qualifier in only those functions that need to access the variable.
368 Computer Fundamentals & C Programming

11.17.5 Register Variables (register)


All program variables are stored in primary memory which is faster than disk but slower then
registers (1.9.4) directly connected to the CPU. The register storage class (keyword: register)
permits a variable to be stored in one of the high-speed CPU registers. A register variable is declared
inside a function or block in the following manner:
register int x;

The compiler will attempt to store the variable x in one of the registers, failing which it will use
primary memory. Because there are a small number of these registers, only those variables that
are frequently accessed (say, a key variable of a loop) should be declared as register variables.
The scope, lifetime and initial value of a register variable is the same as those for an automatic
variable (Table 11.1).
Because a register normally has a size of one word, not all variables can be assigned this storage
class. Generally, register is restricted to char and int variables. However, you can’t obtain the
address of a register variable using &x because registers don’t use the addressing system used by
primary memory.

WHAT NEXT?

We failed to swap two variables using a function. We are yet to explain why scanf needs the & prefix.
We used our scant knowledge of memory addresses to change array elements, but we couldn’t really
assess how important these addresses are in C. The next chapter solves all of these problems and
opens up a new world of opportunities. C is all about pointers.

WHAT DID YOU LEARN?

A function is invoked with or without arguments and may or may not return a value. A function may
be used for its return value or side effect, or both.
A function declaration specifies its usage and the definition provides the code to be executed. Functions
of the standard library are declared in “include” files and their definitions are available as compiled
code in libraries (archives).
When a function is invoked, its actual arguments are passed by value to its formal arguments
(parameters). The function cannot change the actual arguments by changing the parameters.
C doesn’t support passing by reference even though it can use memory addresses to create that effect.
The name of an array represents the address of the first element. A function using an array name as
an argument also needs to know (i) the number of elements for a 1D array, and (ii) the number of
rows for a 2D array.
A function can call another function, which in turn can call another, and so forth. The called functions
return in the reverse order of their invocation.
Reading: Arrays in a C Program
Reading Objective:

In this reading, you will be introduced to the basics of arrays in a C program. You will also learn how to define them, how
they are stored in memory, and how to work with them to solve basic problems.

Main Reading Section:


Arrays in C Programming

Suppose we wish to save the scores or marks for the weekly graded quiz for every student enrolled in this course. One easy
way to do this would be to do the following:

int marks1 = 9;

int marks2 = 7;

int marks3 = 10;

// ... and so on for every single student.

This does seem to work, but now if we wish to compute the average of all the above scores, that would be really difficult. If
we have 100 students, we will have to manually write down each of their marks variables to compute the sum for the
average (marks1 + marks2 + marks3 + ...). Furthermore, we have multiple variables referring to a similar thing. It is best to
have a means for storing the above sequentially, making access and modification convenient for the programmer.

Furthermore, up to this point, we have not seen any means of enabling a variable to hold multiple values at a time. If we
try to do that with ordinary variables, the older values are lost (overwritten).

This is accomplished using arrays in C.

What Are Arrays?

Arrays are a fixed-size collection of data of the same data type, stored sequentially or contiguously in memory, enabling
access and modification through an index.

int arr[10];

float percentages[100];

char alphabet[26];

Each of the above is an array in C. percentages is one name that refers to one hundred float values. The square braces after
the names of our arrays are used to denote the number of values that we want our array to store.

We can also use the square brackets to assign and use values from our array. For instance, consider the following examples:

arr[0] = 1;

This assigns the value 1 to the first element of arr (indexed at position 0).

float last = percentages[99];

This retrieves the 100th element from our percentages array (indexed at position 99) and places it into a new float variable
last.

Note how arrays are zero-indexed. This means that indexing starts at 0. Our first element is stored at index 0, the second
element at index 1, and so on. Zero-indexing (in other words, starting count from 0) is closer to the language of computers,
and hence, it is the convention used for C arrays. You may draw an analogy between this convention and how sometimes
multi-story buildings are said to have a ground floor, and then the first floor, and so on.

Initializing Arrays

There are multiple valid ways of initializing arrays. Let us briefly go through them:

int intArray[6] = {1, 2, 3, 4, 5, 6};

Here, we declare an integer array of six elements and immediately assign those six elements to it (by making use of the
curly braces { }).

float floatArray[10] = {1.387, 5.45, 20.01};

This is very similar to the previous example, except that here we assign fewer values than the length of our array. In this
case, C will directly assign the given values to the first three elements of the array respectively, and then assign the default
values to the remaining elements (default float value is 0.000000).

double fractions[] = {3.141592654,1.570796327,0.78539813};

Here, although we are not specifying the size of our array, since we are performing initialization in the same line, our
compiler understands exactly what we desire and creates an array of doubles containing three elements.

Suppose we declare an array without initializing any element (using either of the above curly brace notations). In that case,
the compiler effectively reserves some space for us in memory. This array now contains only garbage values. These values
will only get overwritten when we initialize the array (if we use the curly brace notation, then all uninitialized elements will
take up the default value). If we initialize element-by-element using the square bracket notation, only those particular
elements will get modified.

Another very useful way to initialize elements of an array is by using a loop. We can clearly see that we have a loop that runs
for 10 iterations, and in each iteration, we read a value for arr[i], where i is the loop variable.

Figure 1 illustrates how the array elements are stored in memory.


Figure 1: Array elements in memory

So, for instance, if we wish to access the fifth element of our array, we will use the expression arr[4]. (Recall once again that
arrays in C use zero-indexing.)

Note that we cannot assign one array to another; that would lead to a compile-time error.

Having understood the basics of arrays, let us turn our attention back to our original problem. We wish to store the marks
of each student conveniently and then compute the average of the marks. Let us solve this problem using arrays. The
following is the program that performs this:

In the above program, we first declare our marks array and initialize it by taking input from the user (using and for-loop and
scanf). We then use another for-loop to run through all the elements to compute their sum. This sum divided by the number
of marks entries (100 in this case) gives us the required average. Note how difficult this problem would be if we had one
hundred different variables for each student’s marks.

Arrays in Memory

Arrays are stored contiguously in memory, quantized by the size of each individual element.

Figure 2 gives a memory diagram, where we see an array of four elements, each occupying two bytes stored in memory.
The array's starting address is 1001010, and each element occupies two bytes. Each individual element is color-coded
differently to show its occupancy in the memory.
Figure 2: Arrays in memory

Arrays in C are always stored contiguously, as shown in Figure 2.

The name of an array refers to the starting address of the array (1001010 in this case). Performing arithmetic on this name
gives us the ability to run through the elements of the array. For instance, if arr is our array, arr gives us a reference (or
address) for the first element of the array, arr+1 gives us the same for the second, arr+2 for the third, and so on. By induction,
arr+(n-1) gives us the address of the last element (assuming n is the number of elements in the array).

The sizeof Operator

sizeof() is a compile-time operator in C that returns the total number of bytes occupied by a variable. Let us take a look at
an example to understand this.
The above code simply prints all the elements of the array arr. The highlighted section of the code performs a clever trick
to return the number of elements in the array to us. The number of bytes occupied by the entire array divided by the number
of bytes occupied by one of the elements of the array (the first element) will, by the unitary method, give us the total number
of elements in the array. This helps us by not requiring that we always know and hard code the lengths of arrays because,
given the array, we can always find its length.

Array Bounds Checking in C

The compiler can check array bounds while indexing to ensure that the index entered by the programmer is valid. This can
be done at two times: while reading from an array or while writing to an array. The designers of C decided to save up on the
extra internal computation time that it would take to perform bound checking while reading, thereby giving C a speed
boost. However, bound checking becomes imperative while writing to an array because a programmer might accidentally
(or intentionally!) try to write to a location in memory that they do not have a valid access to. Thus, in C, we have bound
checking for arrays while writing to them but not while reading from them.

For instance, the following code works fine (beyond the valid range, it will just print the unknown garbage values):

But the following piece of code returns a compile time error (stack smashing—core dumped) as we try to assign values to
the array elements that lie outside the valid range:

Copying or Duplicating an Array


C does not allow us to assign one array name to another. That results in a compile-time error. So, for instance, the following
piece of code would generate a compile-time error (the problematic statement is highlighted):

If we wish to duplicate an array, we must copy all the elements of one array, individually, into the other. This is so because
arrays do not behave like ordinary variables. The following code will accomplish what the above code wishes to do:

Passing Arrays to Functions

Consider the program given below:

In this program, we see that the average of an array of integers is computed by using a function. Interesting to note here is
that we are passing intArray as a parameter to the function where we have allowed for this array parameter by writing int
list[] as our second parameter in the function header.

This is not like an ordinary variable being passed to a function. Arrays in C are passed by reference instead of being passed
by value. This means that any modifications performed to the array within the called function will be reflected to the actual
array present in the calling function. This is unlike ordinary variables, for which no modifications persist once the called
function terminates unless explicitly returned back or unless pointers are used (you will learn about this soon).

We must keep this distinction in mind while programming functions with arrays because any modifications made to arrays
passed as above will persist even after the function call terminates.

Note that we cannot return an array that is defined within a function. The first reason for this is the fact that arrays cannot
be assigned (with an assignment statement) directly. So even if we could return an array, it would be useless as we could
never assign it to another array in the caller function. Secondly, an array defined within a function exists only in the stack-
frame of the function, which is destroyed as soon as the function ends. To return an array effectively like this, we would
have to come up with a construction mechanism to copy each element of the array in the function one by one and send
them back to the caller function. Instead of this, we simply enable pass-by-reference and make use of the same.

Reading Summary:

In this reading, you have learned the following:

• What are arrays, and how to initialize them


• How to access elements stored in an array
• How arrays are stored in memory
• The use of the sizeof operator and bounds checking in arrays
• Duplicating an array
• Passing arrays to functions
• Solving problems using the above concepts
Practice Lab 1: Arrays in a C Program

PracticeLab1_Question1

Write a program that takes an integer value n from the user and creates an array of integers of size n. You
must ensure that it populates the above array by taking values as input from the user in a loop. You also
need to ensure that the program prints the second last element of the above array.

For example, if our array is int arr[5] = {1,2,3,4,5}, then your program should print 4, which is the second last
element of the array.

PracticeLab1_Question2

Consider a program that takes an integer value n from the user and creates an array of integers of size n. It
then populates the above array by taking values as input from the user in a loop. The program is then left
blank. You are required to write code to compute the bitwise AND of all the elements in the array. This result
should then be displayed to the user.

For example, if our array is int arr[5] = {1,2,3,4,5}, then you should compute 1 & 2 & 3 & 4 & 5 and display the
result as 0 of that computation.

Testcase1
Input - {12,25}
Output - 8

PracticeLab1_Question3

Consider a program that takes an integer value n from the user and creates an array of integers of size n. It
then populates the above array by taking values as input from the user in a loop. It then declares two
integers even and odd. The program is then left blank. You are required to write code to compute the
number of even elements in the array and the number of odd elements in the array. Your program should
print these values.

For example, if our array is int arr[5] = {1,2,3,4,5}, then your program should display 2 and 3 as the number
of even and odd elements in the array, respectively.
Reading: Search and Sorting
Reading Objective:

In this reading, you will be introduced to the basic concepts of sorting an array and searching for an element in a given
array. These are two of the most important problems surrounding arrays and understanding the same will help you in your
journey through programming.

Main Reading Section:


Linear Search

Let us say that in a given array, we aim to find the index of a particular element stored in it. One simple approach to solving
this would be to simply run through every single element, sequentially, and check if it matches the element we are looking
for. This is, in essence, linear search.

As evident from the above code, we are iterating through every element from the first to the last, looking for a match
between the array element and our “key.” If we find “key” at an index “i”, then we report that we have found it at that index.
If we manage to iterate through the entire array without finding a match, then clearly the element does not exist in the
array.

Introduction to Sorting

Intuitively, we can understand the importance of sorting if we consider a very simple example. Imagine that your phone’s
contact list was jumbled up instead of stored alphabetically. Wouldn’t that make searching for a contact a very
cumbersome operation?

This is why we perform sorting. A sorted array is organized and more convenient to operate on.

There are multiple algorithms for performing sorting (selection sort, bubble sort, merge sort, radix sort, etc.). Some are
better than others. Of those, we would be studying one of the simplest algorithms for the same: selection sort.

Selection Sort
The algorithm for selection sort is quite simple.

We maintain two parts of the array, the sorted part (on the left) and the unsorted part (on the right). Initially, the sorted
part is empty, and the entire array is the unsorted part. In each iteration, the smallest element from the unsorted part of
the array is added to the sorted part. This process is repeated until we no longer have an unsorted part, i.e., our sorted part
has completely enveloped the array.

We can understand this with the following diagrammatic explanation. Consider the following array:

We first find the smallest element, bring it into its correct position, and interpret that as the sorted part of the array.

Now, for the remaining part, we do the same.

Then, we repeat the above process until completion.

Upon completion, we obtain our sorted array (as shown above).

We implement the selection sort algorithm in C code in the following way:


The above function takes in an array of integers and their length as input. It then runs a for loop through the length of the
array and iteratively keeps adding to the sorted part of the array. In each iteration, our code finds the minimum element
from the remaining unsorted part (using a for loop), and then it places that element into its appropriate location (end of
the sorted part of the array).

Here is a sample main program that uses the above function to sort an array nums of integers:

Reading Summary:

In this reading, you have learned the following:

• How to perform linear search to find an element in an array


• The intuition behind sorting
• The working and code of selection sort algorithm
Practice Lab 2: Search and Sorting

Assignment Brief

PracticeLab2_Question1

Consider a program that takes an integer value n from the user and creates an array of integers of size n. It
then populates the above array by taking values as input from the user in a loop. The remainder of the code
is left blank. You are required to write the code to calculate the total number of positive integers in the array.
Your program should print the result.

For example, if our array is int arr[10] = {-5,-4,-3,-2,-1,0,1,2,3,4}, then you should be printing 5, which is the
number of positive integers in the array.

PracticeLab2_Question2

Consider a program that takes an integer value n from the user and creates an array of integers of size n. It
then populates the above array by taking values as input from the user in a loop. The program is then left
blank. You need to write code to find the third smallest element in the array. Note that this is not a
straightforward problem, as the elements of the array may be in any order. [Hint: Use selection sort first.]

For example, if our array is int arr[8] = {17, 65, 4, 23, 12, 0, 2, 22}, your program should display 4, which is the
smallest element in the array after 0 and 2. Your program should work for any input array.

PracticeLab2_Question3

Consider a program that takes an integer value n from the user and creates an array of integers of size n. It
then populates the above array by taking values as input from the user in a loop. We then declare a variable
found. The program is then left blank. You need to write code to check whether the pair [2,3] exists in the
entered array consecutively in that sequence. If it does, your program should set the found variable to 1. If
it does not exist, your program should set the found variable to 0. The program then prints whether or not
the pair has been found.

For example, if our array is int arr[5] = {1,2,3,4,5}, then you should set found = 1. If the array is int arr[5] =
{2,4,3,4,5}, then you should set found = 0.
Reading: Character Arrays
Reading Objective:

In this reading, you will learn about character arrays and strings in C programming. You will explore how they are created,
used, and operated on.

Main Reading Section:


Character Arrays

In C, if we want to store a string of characters, we can use a character array.

Consider the following two-character array declarations:

• char color[3] = "RED";


• char color[] = "RED";
Looking closely, we can see that the above two declarations are different. C implicitly treats the second declaration as
“strings” and inserts a special character denoted by ‘\0’, called the NULL terminating character or simply the NULL
character, at the end of the character array. This means we have four characters in the second declaration: ‘R’, ‘E’, ‘D’, and
‘\0’. ‘’This is the convention followed by C for strings. All strings must terminate with a '\0'. This lets the compiler know
where strings end when it reads them from memory.

In other words, strings in C are defined as an array of characters that terminate with a '\0'.

There are two functions in C that come built-in to the stdio.h header, namely getchar() and putchar(). These functions are
respectively used for “getting” a character as an input from the user (through the keyboard) and for “putting” a character
from a variable to the output device (monitor).

Now, consider the following problem: Suppose we want to read a one-dimensional character array and display it with each
character converted into its uppercase equivalent. We can solve this using the toupper() function from the ctype.h header.
Let us take a look at the detailed solution.
The inclusion of the ctype.h header in this program enables us to use the toupper() function, which returns the uppercase
equivalent of the character it takes in as an input. It is worthy to note that there also exists a tolower() function in the same
header file. Along with toupper(), the program effectively uses getchar and putchar functions to perform the desired task.

Note that everything that can be done with the getchar and putchar functions can also be done simply by using printf and
scanf. Let us take a look at an example to demonstrate the same:

Reading Summary:

In this reading, you have learned the following:

• Character arrays in C
• Strings in C
• getchar and putchar functions
• Operating on character arrays using toupper and tolower functions from the ctype.h header
Practice Lab 3: Character Arrays

Assignment Brief

PracticeLab3_Question1

Consider a program that creates a string (a character array that ends with a \0) by taking it as input from the
user. For this, a construct has been used that you are not familiar with yet, but you shall learn it in the next
module. The remainder of the code is left blank. You are required to write the code there to count the
number of vowels in the character array (vowels are the characters 'a', 'e', 'i', 'o', and 'u').

For example, if our character array is char arr[15] = {'f','a','d','e',' ','t','o',' ','b','l','a','c','k','\0'} then you
should be printing 4, which is the number of vowels in the character array.

PracticeLab3_Question2

Consider a program that creates a string (a character array that ends with a \0) by taking it as input from the
user. For this, a construct has been used that you are not familiar with yet, but you shall learn it in the next
module. The remainder of the code is left blank. You are required to write a program that finds the average
of the ASCII values of all the characters in the given character array (excluding the final \0). Then your
program should display the character equivalent of that average ASCII value computed.

For example, if our character array is char arr[6] = {'a','b','c','d','e','\0'}, your program should display c, which
is the character equivalent of the average of the ASCII values of the characters entered by the user.
Solutions to Practice Labs
P _L _1/Q1

Write a program to print the second last element of an arra

include stdio h

int main

int n
printf Enter the number of values
scanf d n

if n

printf Invalid number of elements


return

int arr n
printf Enter the values
for int i i n i
scanf d arr i

printf The second last element of the arra is d arr n


return
P _L _1/Q2

Finds the bitwise AND of ever element in the arra

include stdio h

int main void

int i ans
int n
printf Enter the number of values
scanf d n

if n

printf Invalid number of elements


return

int arr n
printf Enter the values
for int i i n i
scanf d arr i

ans arr
for i i n i
ans ans arr i

printf The bitwise AND of all the elements in the arra is d ans
return
P _L _1/Q3

Counts the number of odd and even integers in an arra

include stdio h

int main void

int n
printf Enter the number of values
scanf d n

int arra n
printf Enter the values
for int i i n i
scanf d arra i

int odd
int even

for int i i n i

if arra i
even
else
odd

printf The number of odd values is d n odd


printf The number of even values is d n even
P _L _2/Q2

Counts the number of positive integers in the arra

include stdio h

int main void

int n
printf Enter the number of values
scanf d n

int arra n
printf Enter the values
for int i i n i
scanf d arra i

int count
for int i i n i
if arra i
count

printf The number of positive values is d count


P _L _2/Q2

Finds the third smallest element in an arra using selection sort

include stdio h

void selectionSort int arr int n

int i j min

for i i n i

min i
for j i j n j

if arr j arr min


min j

int temp arr min


arr min arr i
arr i temp

int main void

int n
printf Enter the number of values
scanf d n

if n

printf Invalid number of elements


return

int arr n
printf Enter the values
for int i i n i
scanf d arr i

selectionSort arr n
printf The third smallest element is d arr
return
P _L _2/Q3

Checks if the pair exists in an arra consecutivel in that sequence

include stdio h

int main void

int n
printf Enter number of elements in the arra
scanf d n

if n

printf Invalid number of elements


return

int arra n
printf Enter the elements of the arra
for int i i n i

scanf d arra i

int i
int j
int found
int pair

for i i n i

if arra i pair arra i pair

found
break
if found

printf The pair exists consecutivel in that sequence n

else

printf The pair does not exist consecutivel in that sequence n

return
P _L _3/Q1

include stdio h

int main

char str
int i
int count
printf Enter a string
scanf s str
while str i

if str i a str i e str i i str i o


str i u

count

printf Number of vowels in the string s is d str count


return
P _L _3/Q2

Finds the average of the ASCII values of all the characters in a given
character arra and then displa s the character equivalent of that average ASCII
value

include stdio h
include ct pe h

int main void

char str
int i sum avg
printf Enter a string
gets str
for i str i i

sum str i

avg sum i
printf Average ASCII value d n avg
printf Character equivalent of the average ASCII value c n avg
Lesson 1: Arrays in a C Program

10 Arrays

Chapter 10, Sections 10.1-10.7;


Chapter 11, Sections 11.8-11.9.

WHAT TO LEARN

Techniques of initializing a single-dimensional array.


Populating an array with scanf.
Inserting and deleting elements in an array.
Reversing an array with and without using a second array.
Understanding and implementing the selection sort algorithm.
Searching an array using sequential and binary search.
Handling two- and three-dimensional arrays.
Applying the concepts of two-dimensional arrays to manipulate matrices.
The C99 feature of determining the size of an array at runtime.

10.1 ARRAY BASICS


Variables can’t handle voluminous data because a hundred data items need a hundred variables to
store them. The solution is to use an array, which is an ordered set of data items stored contiguously
in memory. Each data item represents an element of the array and is accessed with an index or
subscript. For instance, the elements of an array named signal are accessed as signal[0], signal[1],
signal[2], and so forth. The first subscript is 0 and the last subscript is one less than the size of
the array.
The elements of an array have a common data type (like int, char, etc.). An array must be declared
with its type and size to enable the compiler to allocate a contiguous chunk of memory for all array
elements. This is how a array named signal having 32 elements is declared:
int signal[32];
The first element is accessed as signal[0] and the last element is signal[31] (not 32). The subscript
is either zero, a positive integer or an expression that evaluates to an integer value. An array element
can be assigned to a variable (say, int x = signal[2];), which means we can use signal[2] wherever
Arrays 299

we use x. By the same logic, &signal[2] is also a valid scanf argument like &x. It is easy to cycle
through all array elements by using the array subscript as a key variable in a loop.
C doesn’t offer bounds checking for arrays, i.e. it doesn’t validate the index used to access an array
element. For instance, the compiler will not generate an error if you attempt to access signal[35]
even though the array has 32 elements. A negative index (as in signal[-5]) doesn’t generate an
error either. However, these accesses create problems at runtime because the program accesses an
area of memory that was not allocated for the array. C trusts the programmer, so you’ll have to do
the bounds checking yourself.
The data type of an array is not restricted to the fundamental ones. Even derived and user-defined
types like pointers and structures can be stored in arrays. C also supports multi-dimensional arrays
where an element is handled with multiple subscripts. Since there is virtually no limit to the number
of indexes and dimensions an array can have, arrays in C are very powerful data structures that
can easily gobble up a lot of memory.
Because arrays can save a lot of data using a single identifier, there are a number of useful things
you can do with them. You can add or delete array elements, reverse an array, sort and search it
as well. You can also make a frequency count of the items in an array. Furthermore, many of the
routines we develop in this chapter deserve to be converted to functions, a task that we’ll attempt
in Chapter 11.
Note: C doesn’t complain if you use a negative index or one that exceeds its size. For instance,
rate[-2] will clear the compilation phase but it will evaluate to an unpredictable value at runtime.
It may destroy another variable, i.e., a part of the program you are running.

10.2 DECLARING AND INITIALIZING AN ARRAY


Like a variable, an array has to be declared before use. The declaration of an array specifies its
name, type and size, which is normally specified as a constant or symbolic constant. The following
statement declares a single-dimensional array named signal having 32 elements, each of type int:
int signal[32]; Size specified explicitly
This declaration also defines the array by allocating the required memory where the last segment
of memory is used by signal[31]. The sizeof operator, when used on an array, returns the total
usage of all elements, so the above definition allocates 32 ¥ sizeof(int) bytes of memory—typically,
128 bytes. Figure 10.1 depicts the memory layout of a single-dimensional array of 10 elements.
Arrays of other primitive data types are declared in a similar manner:
short months[12]; Unlikely to change size
float rate[10];
char name[SIZE]; Uses symbolic constant
The naming rules for variables (5.2.1) apply in identical manner to arrays. This means that the
name of an array can comprise only letters, numerals and the underscore character, but it cannot
begin with a digit. Also, case is significant, so months[12] and MONTHS[12] are two separate arrays.
300 Computer Fundamentals & C Programming

a[0] a[1] a[2] a[3] a[4] a[5] [a6] [a7] [a8] [a9] Unallocated memory

FIGURE 10.1 Layout in Memory of the Array a[10]

Tip: Arrays are often resized, so it makes sense to use a symbolic constant to represent the size
(like in char name[SIZE];). When the size changes, simply change the #define directive that sets
SIZE. You don’t need to disturb the body of the program.

10.2.1 Initializing During Declaration


A simple declaration doesn’t initialize an array (unless the keyword static is also used), so at
this stage the elements have junk values. An array can be initialized on declaration by enclosing
a comma-delimited list of values with curly braces. Two of the following declarations explicitly
specify the size, but the third one does it only implicitly:
short months[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
float rate[10] = {12.5, 13.7, 5};
char type[ ] = {‘A’, ‘n’, ‘d’, ‘r’, ‘o’, ‘i’, ‘d’, ‘\0’};
In the first declaration, the number of values matches the size of the array. The second declaration
performs a partial initialization by assigning values to the first three elements only. When that
happens, the remaining values are automatically initialized to zeroes (NUL for an array of type char).
The third declaration uses an empty pair of brackets ([ ]) to implicitly specify the size of the array.
This flexibility benefits the programmer and also doesn’t bother the compiler as it can easily
compute the size by counting eight values on the right. This array is a string because it contains
the terminating NUL (‘\0’). We’ll discuss the string as an array of characters in Chapter 13.

10.2.2 Initializing After Declaration


You may not know all initial values at compile time, or you may need to reassign values later in the
program. The technique using curly braces won’t work when values are assigned later, in which
case, each element has to be assigned individually:
months[0] = 31;
months[1] = 28.3; Decimal portion truncated
rate[4] = 3; OK, 3 converted to float
type[5] = ‘\0’; The NUL character having ASCII value 0
A loop is often used to initialize an entire array if all elements are assigned the same value or
values obtained by evaluating an expression. The array index is usually the key variable of the loop.
The following example on the left initializes all elements of the array, rate, to 12.5, while the one
on the right assigns an expression that contains the index itself:
short i; short i;
float rate[10]; float rate[SIZE];
for (i = 0; i < 10; i++) for (i = 0; i < SIZE; i++)
rate[i] = 12.5; rate[i] = i * 1.1;
Arrays 301

The symbolic constant, SIZE, must have been previously defined as #define SIZE 10. To print all
elements, a similar two-line for loop should do the job:
for (i = 0; i < 10; i++) for (i = 0; i < SIZE; i++)
printf(“%f “, rate[i]); printf(“%f “, rate[i]);
The initialization rules for uninitialized and partially initialized arrays apply to arrays that are
defined inside a function like main. In Chapter 11, you’ll find out how arrays are automatically
initialized either by use of additional keywords or by defining them outside a function.

10.3 array_init.c: INITIALIZING AND PRINTING ARRAYS


Program 10.1 features three arrays having different types and degrees of initialization. The program
displays the contents of these arrays, and in doing so, establishes the consequences of using
uninitialized and partially initialized arrays.
/* array_init.c: Shows effects of (i) no initialization, (ii) partial
initialization. Also initializes one array. */
#include <stdio.h>
#define SIZE 7
int main(void)
{
short i;
short short_arr[SIZE];
int int_arr[SIZE] = {100, 200};
long long_arr[ ] = {10, 20, 30, 40, 50, 60, 100};
printf(“Size of short_arr = %d\n”, sizeof short_arr);
printf(“Size of long_arr = %d\n”, sizeof long_arr);
printf(“short_arr contains “);
for (i = 0; i < SIZE; i++)
printf(“%5hd “, short_arr[i]); /* Uninitialized */
printf(“\nint_arr contains “);
for (i = 0; i < SIZE; i++)
printf(“%5d “, int_arr[i]); /* Partially initialized */
printf(“\nlong_arr contains “);
for (i = 0; i < (sizeof long_arr / sizeof(long)); i++)
printf(“%5ld “, long_arr[i]); /* Fully initialized */
for (i = 0; i < SIZE; i++)
short_arr[i] = i * 2; /* Initializing short_arr ... */
printf(“\nshort_arr now contains “);
for (i = 0; i < SIZE; i++)
printf(“%5hd “, short_arr[i]); /* ... and printing it */
return 0;
}

PROGRAM 10.1: array_init.c


302 Computer Fundamentals & C Programming

Size of short_arr = 14
Size of long_arr = 28
short_arr contains -24588 2052 13928 -16479 -31319 2052 -8352
int_arr contains 100 200 0 0 0 0 0
long_arr contains 10 20 30 40 50 60 100
short_arr now contains 0 2 4 6 8 10 12

PROGRAM OUTPUT: array_init.c


Observe that sizeof evaluates the memory allocated for all array elements. The following
observations are made for the arrays used in the program:
short_arr initially has junk values because it was declared without initialization. It is later
assigned the value of an expression that is related to its index.
int_arr is partially initialized. The first two elements are initialized, so the remaining five
elements are automatically set to zero.
The size of long_arr is implicitly declared. The compiler determines it by counting seven values.
This size is also computed by dividing the total space used by the array (sizeof long_arr) by
the size of the long data type (sizeof(long)).
Generally, functions that accept an array as argument also need the number of elements as another
argument. This value should be passed to the function as the expression sizeof array / sizeof(data type)
rather than a constant. Even if you change the size of the array later, you don’t need to change this
expression.

Takeaway: An uninitialized array contains junk values, but a partially initialized array has the
remaining elements set to zero or NUL. Also, sizeof for an array evaluates to the total space
taken up by all elements of the array.

10.4 scanf_with_array.c: POPULATING AN ARRAY WITH scanf


Using the scanf function in a loop, we can populate an array with keyboard input. Program 10.2
takes advantage of the return value of scanf to determine the end of input. The while loop in this
program terminates when scanf returns 0, and that happens when EOF or any non-digit character
(say, q) is keyed in.
You can input as many as 20 integers—on separate lines if necessary—but every invocation of
scanf reads one integer and leaves the remaining input in the buffer. The number of values input
is available in the expression i - 1, which determines the number of times the second loop will
iterate to print all array elements that were populated.

10.5 BASIC OPERATIONS ON ARRAYS


As the previous program clearly shows, an array element can be treated as a variable for all practical
purposes. An element can thus form a component of any arithmetic, assignment, relational or logical
Arrays 303

/* scanf_with_array.c: Populates an array with user input. */


#include <stdio.h>
int main(void)
{
short i = 0, num;
short arr[20];
printf(“Key in some integers followed by EOF: “);
while (scanf(“%hd”, &arr[i++]) == 1) /* Populates array */
;
num = i - 1;
for (i = 0; i < num; i++)
printf(“arr[%hd] = %hd, “, i, arr[i]);
return 0;
}

PROGRAM 10.2: scanf_with_array.c


Key in some integers followed by EOF: 11 22 [Enter]
44 55 77 [Enter]
99
EOF Press [Ctrl-d] or [Ctrl-z] to terminate loop
arr[0] = 11, arr[1] = 22, arr[2] = 44, arr[3] = 55, arr[4] = 77, arr[5] = 99,

PROGRAM OUTPUT: scanf_with_array.c

expression. The following examples drawn from programs in this chapter reveal the various ways
of using an array element in an expression:
arr[i] = arr[i + 1];
temp = arr[i];
arr[c]++;
if (arr[i] > arr[j])
if (num < arr[0] || num > arr[SIZE - 1])

The [ and ] in the element arr[i] are actually a set of operators that serve to evaluate the element.
These symbols have the highest precedence—at the same level as the parentheses. Thus, in the
expression arr[c]++, arr[c] is evaluated before the increment operation is applied.

10.5.1 insert_element.c: Inserting an Element in an Array


To insert an element in an array at a specified location, we need to shift to the right all elements
from that location to the end of the array. Program 10.3 takes user input to initialize an array and
then takes further input to insert an element at a specified location. Finally, it displays the changed
contents of the array. The program is documented well enough to require further elaboration.
304 Computer Fundamentals & C Programming

/* insert_element.c: Inserts element at specified location in an array. */


#include <stdio.h>
int main (void)
{
short i, j, num, new_value, arr[20];
printf(“Number of integers to input? “);
scanf(“%hd”, &num);
printf(“Key in %hd numbers: “, num);
for (i = 0; i < num; i++)
scanf(“%hd”, &arr[i]);
printf(“Enter desired index at insertion and inserted value: “);
scanf(“%hd%hd”, &j, &new_value);
/* Section for shifting elements to right and insertion of element */
for (i = num; i > j; i--) /* arr[num] is last element plus one */
arr[i] = arr[i - 1]; /* Moves a group of elements to right */
arr[i] = new_value; /* Inserts new value at index j */
for (i = 0; i <= num; i++) /* Now <= because of an extra element */
printf(“%hd “, arr[i]);
return 0;
}

PROGRAM 10.3: insert_element.c


Number of integers to input? 6
Key in 6 numbers: 11 22 33 55 66 77
Enter desired index at insertion and inserted value: 3 44
11 22 33 44 55 66 77

PROGRAM OUTPUT: insert_element.c

10.5.2 delete_element.c: Deleting an Element from an Array


Deletion of an element is equally simple and can be achieved with a similar but inverse line of
reasoning. Just move to the specified location as before, and then shift to the left all elements from
that location to the end of the array. Program 10.4 achieves this task. This time we use the technique
discussed in Section 10.4 which populates an array without knowing the number of elements needed.
This program too is well-documented, so no further explanation is provided.
Large arrays are often sorted for faster access. Insertion and deletion of elements in a sorted array
require additional programming effort. Because a user can’t specify the insertion or deletion point
for a sorted array, the program has to locate it. After you have learned to search an array, these tasks
will be within your grasp.
Arrays 305

/* delete_element.c: Deletes element at specified location in an array. */


#include <stdio.h>
int main (void)
{
short i, j, num, arr[20];
printf(“Key in numbers and press [Ctrl-d]:\n”);
num = 0;
while (scanf(“%hd”, &arr[num]) == 1)
num++; /* Number of items read = num */
printf(“\nEnter index at deletion point: “);
scanf(“%hd”, &j);
for (i = j; i < num - 1; i++) /* Now num - 1 because of deleted element */
arr[i] = arr[i + 1]; /* Moves a group of elements to left */
for (i = 0; i < num - 1; i++)
printf(“%hd “, arr[i]);
return 0;
}

PROGRAM 10.4: delete_element.c


Key in numbers and press [Ctrl-d]:
11 22 33 44 55 66 77
[Ctrl-d] or [Ctrl-z]
Enter index at deletion point: 3
11 22 33 55 66 77

PROGRAM OUTPUT: delete_element.c


10.6 REVERSING AN ARRAY
This section discusses two techniques of reversing an array. This is easily achieved with two arrays,
but since large arrays can take up a lot of space, we must also be able to perform the task using
a single array. Both techniques are discussed in the following sections.

10.6.1 Reversing with Two Arrays


The simplest method of reversing the contents of an array is to use a second array to hold the new
values. The sequence goes like this: read the first element of array1 and save it to the last element
of array2, save the next element of array1 to the one previous to the last element of array2, and so
on. This simple code segment does the job:

short i, size = 7, short brr[7];


short arr[7] = {1, 3, 6, 9, 11, 22, 5};
for (i = 0; i < size; i++) /* Reversing array */
brr[size - 1 - i] = arr[i];
306 Computer Fundamentals & C Programming

for (i = 0; i < size; i++) /* Printing reversed array */


printf(“%d “, brr[i]);
OUTPUT: 5 22 11 9 6 3 1
In this and the next example, we have chosen to use a variable (size) to store the number of elements
of the array. A serious programmer, however, will use #define SIZE 7 instead. Since this size is
one more than the value of the last subscript, we need to subtract one from it before assigning an
element of the first array to the second array.
10.6.2 Reversing with a Single Array
An array of n elements can be reversed without using a second array. This technique uses an
intermediate variable to reverse the first and nth (last) element, the second and (n - 1)th element,
and so on. This process is carried out n / 2 times. If n is even, the exchange is completed for all
elements, but if n is odd, the middle element is unaffected. This is the case anyway, so program
logic doesn’t depend on whether n is even or odd. The following code does the job:
short i, j, temp, size = 7;
short arr[7] = {1, 3, 6, 9, 11, 22, 5};
for (i = 0, j = size - 1; i < (size / 2); i++, j--) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
for (i = 0; i < size; i++) /* Printing reversed array */
printf(“%d “, arr[i]);
OUTPUT: 5 22 11 9 6 3 1
The comma operator in exp1 and exp3 of the combined expression of the first for loop is responsible
for the lean loop body. This loop swaps two values using temp as the intermediate variable.
The subscript i is incremented from 0 to (size - 1) / 2, while j is simultaneously decremented
from the last subscript (size - 1). The original array is, however, lost in the process. Because this
technique saves memory, we’ll henceforth use it for any job related to array reversal.

10.7 TWO PROGRAMS REVISITED


Some of the previous programs used a loop to generate one or more values in every iteration.
Because all of these values couldn’t be saved, action on each value was taken the moment it was
generated and before the next iteration could begin. Now that we have an array to store all values,
we can use these values even after the loop has completed execution. Let us now revisit two of these
programs to make them flexible enough to handle multiple requirements.
10.7.1 extract_digits2.c: Saving Digits of an Integer
Program 10.5 is an improvement over its predecessor (Program 8.3), which extracts the digits from
an integer. This program, however, stores all extracted digits in an array and then reverse-reads
Arrays 307

the array to print the digits in the normal sequence. With minor modifications to the code, you
can manipulate these digits in any manner you want. You can print them in any sequence, add or
multiply them.

/* extract_digits2.c: Modifies a previous program to save the digits of an


integer in an array for manipulation in any manner. */
#include <stdio.h>
int main(void)
{
unsigned int number;
short i = 0, digit_arr[10];
printf(“Key in an integer: “);
scanf(“%d”, &number);
while (number != 0) {
digit_arr[i++] = number % 10; /* Saves last digit */
number = number / 10; /* Removes last digit */
}
i--;
while (i >= 0) /* Printing the digits ... */
printf(“%hd “, digit_arr[i--]); /* ... in normal sequence */
printf(“\n”);
return 0;
}

PROGRAM 10.5: extract_digits2.c


Key in an integer: 1357926
1 3 5 7 9 2 6

PROGRAM OUTPUT: extract_digits2.c

10.7.2 decimal2anybase.c: Converting from Decimal to Any Base


The concepts behind the reversal of an array can be gainfully employed in the task of converting
a number from one base to another. In Chapter 8, decimal-to-binary conversion was attempted
twice by reversing-reading an array of remainders. Program 10.6 extends the scope by converting
from decimal to any base not exceeding 16. Instead of reverse-reading an array, the program reverses
the array itself using the technique discussed previously.
The program uses two arrays of type char. digit_arr stores the remainders and digits stores the
character representation of all hex digits. For decimal and octal numbers, the program needs to
use only the first 10 and 8 elements, respectively, of this array.
The number to be converted and the required base are first taken from user input. This number is
repeatedly divided by the base. Using remainder as subscript, the program extracts the appropriate
308 Computer Fundamentals & C Programming

digit-letter from digits. For instance, if remainder is 15 from a division by 16, digits[remainder]
is ‘F’. This value is stored in digit_arr, which is finally reversed by the second for loop. The last
for loop simply reads this converted array.

/* decimal2anybase.c: Extends decimal2binary.c to convert a number to any base.


Reverses an array without using a second one. */
#include <stdio.h>
int main(void)
{
char digit_arr[80];
char digits[16] = {‘0’,’1',’2',’3',’4',’5',’6',’7',’8',’9',
‘A’,’B’,’C’,’D’,’E’,’F’};
short i, j, imax, temp, base, remainder;
long quotient;
printf(“Enter number to convert and base: “);
scanf(“%ld%hd”, &quotient, &base);
for (i = 0; quotient > 0; i++) { /* Divide repeatedly */
remainder = quotient % base;
quotient /= base;
digit_arr[i] = digits[remainder]; /* Select digit from array */
}
imax = i; /* Number of digits to be printed */
/* Reverse the array before printing the digits */
for (i = 0, j = imax - 1; i < imax / 2; i++, j--) {
temp = digit_arr[i];
digit_arr[i] = digit_arr[j];
digit_arr[j] = temp;
}
for (i = 0; i < imax; i++)
printf(“%c “, digit_arr[i]);
return 0;
}

PROGRAM 10.6: decimal2anybase.c


Enter number to convert and base: 12 2
1 1 0 0 23 + 22 = 12
Enter number to convert and base: 127 2
1 1 1 1 1 1 1 Seven 1s—127
Enter number to convert and base: 255 8
3 7 7 3 ¥ 82+7 ¥ 81+7 ¥ 80 = 255
Enter number to convert and base: 255 16
F F 15 ¥ 161+15 ¥ 160 = 255

PROGRAM OUTPUT: decimal2anybase.c


Chapter 11, Sections 11.8-11.9

11.8 USING ARRAYS IN FUNCTIONS


Functions
344 also use arrays
Computer as arguments. &
Fundamentals You
C can pass both an individual array element and the name of an array
Programming
as argument to a function. The principles of parameter passing apply to arrays too, but
they affect array elements and entire arrays in opposite ways. Let’s first consider this function call
that uses an array element as argument:
validate(month[2]);

This is a simple pass by value; the value of month[2] is copied to its respective parameter—probably
a simple variable—in the function. You can’t thus modify this element by changing its copy inside
the validate function. However, this is not the case when the name of the array is passed as
an argument:
init_array(month, 12);

Here, month does not signify the complete array, so the entire array is not copied inside the function.
Instead, month signifies the address of its first element (i.e., &month[0]), and it is this address that
is copied. The function can then compute the address of the remaining elements using simple
arithmetic. This means that the function can change every element of month even though it is
defined in the caller!
However, there is one problem. Even though init_array has been passed an address, it has no way
of knowing that this address refers to an array. But if the size of the array is also passed as a separate
argument, init_array can then use this size to identify a block of memory and treat this block as an
array. It can then compute the addresses of the remaining “elements” without straying beyond the
boundaries of the “array.” A classic case of deception but one that works perfectly!
Finally, in a declaration or definition of the function that uses an array name (say, month) as argument,
the array name is specified as month[ ] or month[n], where n is any integer. But the function call
uses only the array name, as the following declarations and invocation show:
void init_arr(short month[ ], short size); Declaration uses name + [ ]
void init_arr(short month[12], short size); This declaration is also OK
init_arr(month, 12); Invocation uses simply name
In the second example, the compiler doesn’t interpret the subscript 12 as the size of the array. It simply
looks at the [ ] to know that it is dealing with an array. It thus makes no difference whether you use
month[ ], month[12], or month[65000] in the declaration (or header of definition). But the compiler has
no way of knowing the size of this array, so the size must be passed as a separate argument.
If the array contains a string (an array of type char terminated by ‘\0’), a function can determine
the size of the string without knowing the size of the array. Strings are discussed in Chapter 13.

Takeaway: Array elements, when used as function arguments, are copied to their respective
parameters in the usual way. But when the name of an array is passed as a function argument,
what is copied is the address of the first element, and not the entire array. However, because
the function knows this address, it can change the entire array provided it also knows the number of
array elements.
Functions 345

11.8.1 input2array.c: Passing an Array as an Argument


Program 11.5 uses a function to populate an array with keyboard input. This function uses the
name of the array and its size as the two arguments. After the function completes execution, the
program prints the values of all array elements. The output shows that the initialization of the
“array”, f_arr, inside the function has actually changed arr in main, thus confirming our assertion
that the array itself is never copied inside a function.
Since arr is the same as &arr[0], both &arr[0] and &f_arr[0] represent the address of the same
array element. But f_arr is not an array even if the declaration and definition suggest that it is
one (f_arr[ ]). The input2array function, however, treats f_arr as an array and uses the other
argument (SIZE or num) to populate it correctly. Thus, changing f_arr inside the function actually
changes arr outside the function.
Let’s now face the truth once and for all: Both arr and f_arr are pointers. The declaration could
have specified short * f_arr instead of short f_arr[ ], and it would have made no difference to
the existing implementation of the function. (See it for yourself by making this change.) But hold

/* input2array.c: Uses a function to fill up array with keyboard input. */


#include <stdio.h>
#define SIZE 6
void input2array(short f_arr[ ], short num); /* Note the [ ] */
int main(void)
{
short arr[SIZE], i;
input2array(arr, SIZE); /* Array name as argument */
for (i = 0; i < SIZE; i++)
printf(“arr[%hd] = %hd “, i, arr[i]);
printf(“\n”);
return 0;
}
void input2array(short f_arr[], short num)
{
short i;
printf(“Key in %hd integers: “, num);
for (i = 0; i < num; i++)
scanf(“%hd”, &f_arr[i]); /* No array named f_arr actually */
return;
}

PROGRAM 11.5: input2array.c


Key in 6 integers: 1 5 20 100 1024 4096
arr[0] = 1 arr[1] = 5 arr[2] = 20 arr[3] = 100 arr[4] = 1024 arr[5] = 4096

PROGRAM OUTPUT: input2array.c


346 Computer Fundamentals & C Programming

your breath until Chapter 12 when we revisit arrays. We’ll discover that array notation in a function
declaration is merely a convenience, and that a function need not use this notation to access the
array in the function body.
Arrays can take up a lot of space, so it just doesn’t make sense for a function to create a copy and
then lose it immediately after the function terminates. Besides, functions that sort and search an
array can do the job faster if they are spared the burden of creating a copy. True, there remains
the risk of inadvertently changing the array from the function, but the benefits of this design far
outweigh the drawback.
11.8.2 The static keyword: Keeping an Array Alive
A function can create an array as a local variable and return any element to the caller. This is
reflected in the following definition of a function that retrieves from its own database (a local array)
the maximum number of days available in a month:
short days_in_month(short index)
{
short month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return month[index];
}
But what if a function returns the name of the array, i.e., the address of the first element? Can the
array then be accessed outside the function? No, that won’t be possible because even if the calling
function can “see” this address, the array itself ceases to exist after the function has completed
execution. We’ll provide a solution to this problem after addressing a performance issue related to
the use of arrays.
Every call to days_in_month creates the month array, initializes it and finally destroys it. Function
calls have overheads, which can be quite severe when used to create large arrays. Better performance
could be obtained by creating the array in main, but then the function would lose its generality.
We would then need to create this array in every program using this function. A better solution
would be to declare the array as static inside the function:
static short month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
Unlike a normal variable, a static variable is created and initialized once—when the function is
first called. Thus, the month array will retain all values between successive calls to days_in_month.
And if this function is modified to return the starting address of month instead, the caller can access
all elements without repeatedly calling days_in_month! We’ll make this possible using pointers in
Chapter 12. The static keyword, which affects the lifetime of any variable (not limited to arrays),
is examined in Section 11.17.2.

11.9 merge_arrays.c: MERGING TWO SORTED ARRAYS


Program 11.6 uses the services of three functions to merge two sorted arrays. One function takes
care of initializing the two arrays with sorted data. Another one performs the actual task of merging
the data. The third function simply prints the sorted data.
Functions 347

/* merge_arrays.c: Merges two sorted arrays. Uses three functions to


(i) initialize, (ii) merge and (iii) print arrays. */
#define SIZE1 5
#define SIZE2 6
#include <stdio.h>
void input2array(short a[ ], short num);
void merge_arrays(short a[ ], short i, short b[ ], short j, short c[ ]);
void print_array(short a[ ], short num);
int main(void)
{
short arr[SIZE1], brr[SIZE2], crr[SIZE1 + SIZE2];
input2array(arr, SIZE1);
input2array(brr, SIZE2);
merge_arrays(arr, SIZE1, brr, SIZE2, crr);
printf(“After merging: “);
print_array(crr, SIZE1 + SIZE2);
return 0;
}
void input2array(short a[ ], short num)
{
short i;
printf(“Key in %hd integers: “, num);
for (i = 0; i < num; i++)
scanf(“%hd”, &a[i]);
return;
}
void merge_arrays(short a[ ], short m, short b[ ], short n, short c[ ])
{
short i = 0, j = 0, k = 0;
while (i < m && j < n) {
if (b[j] > a[i])
c[k] = a[i++]; /* Assigns c[ ] higher of two element values */
else
c[k] = b[j++]; /* Ditto */
k++; /* Move to next element of c[ ] */
}
/* Once here, it means one array has been totally assigned to c[ ] */
if (i >= m) /* If it is a[ ] ... */
while (j < n)
c[k++] = b[j++]; /* ... assign remaining elements of b[ ] */
else if (j >= n) /* If it is b[ ] ... */
while (i < m)
c[k++] = a[i++]; /* ... assign remaining elements of a[ ] */
return;
}
348 Computer Fundamentals & C Programming

void print_array(short a[ ], short num)


{
short i;
for (i = 0; i < num; i++)
printf(“%d “, a[i]);
printf(“\n”);
return;
}

PROGRAM 11.6: merge_arrays.c


Key in 5 integers: 11 33 55 77 99
Key in 6 integers: 22 44 66 88 95 111
After merging: 11 22 33 44 55 66 77 88 95 99 111

PROGRAM OUTPUT: merge_arrays.c

The compiler permits the use of SIZE1 + SIZE2 to set the size of the third array because the value
of this expression is known at compile time. The action begins by calling the input2array function
twice to populate two arrays. Remember to key in an ascending list of integers.
The merge_arrays function uses five arguments to merge the two arrays. The size is specified
for a and b , but not for c because its corresponding argument, crr , is adequately sized.
The first while loop compares elements of both arrays before assigning them to c in the right order.
The loop terminates when c has all elements of either a or b. The if statement then runs a while
loop to assign c with the leftover elements of the other array. The print_array function finally
prints the merged array.
This program represents a classic case of modularization (3.11). Here all work has been assigned
to individual modules, and the lean main section simply invokes them. This is what you must be
prepared to do as your programs get progressively large. Note that the program is easily maintainable;
you can easily spot the module that requires change.

11.10 PASSING A TWO-DIMENSIONAL ARRAY AS ARGUMENT


A function can also work with the name of a 2D array as an argument. Like with a 1D array,
a 2D array is specified differently in the declaration (and definition) and invocation. In the former
case, you may drop the first subscript (rows), but you can’t drop the second subscript (columns).
The following declarations for the print_2d_array function are thus valid:
void print_2d_array(int arr[3][5], int rows);
void print_2d_array(int arr[ ][5], int rows);
Without knowledge of the number of columns, it would be impossible for the compiler to know
when one row ends and another begins, considering that the rows are placed next to one another
in memory. In other words, the compiler must know that element arr[0][4] is followed by arr[1]
[0] in memory.
Lesson 2: Search and Sorting

Chapter 10, Sections 10.8-10.9. Arrays 309

10.8 SORTING AN ARRAY (SELECTION)


Sorting refers to the ordering of data in ascending or descending sequence. Large lists like
a telephone directory must be sorted for them to be useful. Arrays are often sorted either to present
information in a specified sequence or to fulfill a requirement of other operations (like a search).
The technique used in sorting numbers is different from the one used in sorting strings. In this
section, we’ll discuss the theory and programming techniques related to the sorting of numeric
data. Sorting of character strings is examined in Chapter 13.
There are several algorithms for sorting arrays. Three of the common ones are known
as bubble sort, selection sort and insertion sort. Because they have some common features, knowledge
of one algorithm will help you understand the others. In this section, we’ll examine the algorithm
for selection sort before we develop a program that implements this algorithm.
Consider a numeric array, arr, which needs to be sorted in ascending order. Sorting is done by
repeatedly scanning the array in multiple passes, where the start point of each pass progressively
moves from arr[0] to arr[1], arr[2], and so forth. If a number is found to be lower than the
start element of that pass, then the two numbers are interchanged. Thus, at the end of each pass,
the start element has the lowest number among the elements encountered in that pass. A total of
n - 1 passes are required for an array of n elements, where each pass encounters one fewer element
compared to its previous pass.
Let’s now implement this algorithm in Program 10.7, which sorts a set of integers input from the
keyboard. The sorting operation is carried out with a nested for loop. The outer loop determines
the start point of each pass, while the inner loop scans the elements following the start element.
We’ll examine the exact sequence of events that occur in the first two iterations and the last iteration
of the outer loop.
Outer loop: First iteration The outer loop begins the scan by selecting the first element as the
start point (i = 0). Next, the inner loop scans all remaining elements starting from the second
element (j = i + 1). In each iteration, it compares the value of the scanned element to the start
element and interchanges them if the start element is greater than the other. For this purpose, it
makes use of the temporary variable, tmp. After the inner loop has completed its scan, control reverts
to the outer loop for the second iteration.
Outer loop: Second iteration The outer loop now selects the second element as the start point
(i = 1). The inner loop now performs the scan for all elements except the first and second (j = 2),
comparing each element to the second element and interchanging them if the relational test is satisfied.
Outer and inner loops: Last iteration With every iteration of the outer loop, the start point
progressively shifts from the first element to the one preceding the last element. At this point,
the inner loop iterates only once by comparing this element to the last one and interchanging if
necessary. Both loops have now completed their run and the sort action has been completed.
For your benefit, the program contains an additional for loop which prints the contents of the
array after the inner for loop completes a single pass. The output shows how each pass moves the
lowest number encountered in that pass to the left. Eventually, the last line of diagnostic output
shows a sorted array in place.
310 Computer Fundamentals & C Programming

/* sort_selection.c: Sorts a one-dimensional array using a nested for loop.


Algorithm used: selection sort. */
#include <stdio.h>
int main(void)
{
short i, j, k, imax, tmp;
short arr[100];
printf(“Key in some integers (q to quit): “);
for (i = 0; scanf(“%hd”, &arr[i]) == 1; i++)
;
printf(“Status of array after each iteration ...\n”);
imax = i; /* Number of elements used */
for (i = 0; i < imax - 1; i++) { /* Last element not considered */
for (j = i + 1; j < imax; j++) /* Scanning remaining elements */
if (arr[i] > arr[j]) { /* If start element is larger ... */
tmp = arr[i];
arr[i] = arr[j]; /* ... interchange the ... */
arr[j] = tmp; /* ... elements */
}
/* This for loop displays the array contents after each pass.
You can remove it after you have understood selection sort. */
for (k = 0; k < imax; k++) /* Displaying progress */
printf(“%3hd”, arr[k]);
printf(“\n”);
}
printf(“Final sorted output ...\n”);
for (i = 0; i < imax; i++)
printf(“%3hd”, arr[i]);
printf(“\n”);
return 0;
}

PROGRAM 10.7: sort_selection.c


Key in some integers (q to quit): 55 4 27 1 66 34 11 q
Status of array after each iteration ...
1 55 27 4 66 34 11
1 4 55 27 66 34 11
1 4 11 55 66 34 27
1 4 11 27 66 55 34
1 4 11 27 34 66 55
1 4 11 27 34 55 66
Final sorted output ...
1 4 11 27 34 55 66

PROGRAM OUTPUT: sort_selection.c


Arrays 311

Note: Since a character string doesn’t have a numeric value (even though its individual characters
have), sorting a set of strings is based on a different principle altogether. Starting with the first
character, the ASCII value of each character of string1 is compared to its peer in string2. The sort stops
when the first mismatch occurs. We need to use strcmp (a string-handling function) to make this
comparison, so the topic will be visited in Chapter 13.

10.9 array_search.c: PROGRAM TO SEQUENTIALLY SEARCH AN ARRAY


Searching is a common operation performed on arrays. We often search an array for a number.
Depending on the application, we may ignore this number or increment a counter if the number
exists in the array. If it doesn’t exist, we may add the number to the array. The simplest search
technique sequentially scans all array elements and terminates the search when a match is found.
A more efficient technique uses a sorted array to conduct a binary search. Both techniques are
discussed in this chapter.
Program 10.8 sequentially searches an array that can hold up to 128 integers. A user first keys in
a number of integers before searching the array for a particular integer. In case a match is found, the
program prints the array element along with the index. This technique is adequate for small arrays.
With scanf running in a loop, a set of six integers are keyed in before the input is terminated
with q. Using the notation &arr[i++], scanf saves these integers in the array arr. getchar then
performs its usual cleanup job to get rid of the q and other non-numeric characters that may
remain in the system buffer.
Next, a search routine runs in a nested for loop to search this array for another user-input integer.
If a match is found, the variable is_found is set to ‘y’ and the inner loop is terminated with break.
If no match is found, the loop terminates normally and is_found remains unchanged at ‘n’.
Control in either case moves down to the conditional expression which evaluates the status of
is_found and selects the appropriate printf expression. The next iteration of the outer loop then
prompts the user for another integer.
10.10 BINARY SEARCH
Sequential search is slow and is acceptable only for searching small arrays. When working with
large data volumes, we need to use the binary search mechanism. In this scheme, the array is sorted
before it is subjected to a search. A binary search halves the search domain every time it is invoked.
The number of loop iterations needed is thus reduced, making binary search more efficient than
sequential search. Every serious programmer should understand this search mechanism and know
how to implement it in their programs.
The principle used in binary search is simple, and you may have already used it in a number guessing
game. You ask a person to guess a number between 1 and 100. You then compute the mid-point of
this range (50), and then ask whether the number lies in the lower or upper half of the range. The
next question halves the selected range even further, and in only a few guesses, the range reduces
to a single number—the one that was guessed. The range could typically follow this progression:
1-100, 50-100, 50-75, 63-75, 63-69, 66-69, 66-68, 67-68, 67
Lesson 3: Character Arrays

Chapter 13, Sections 13.1-3

13 Strings

WHAT TO LEARN

Interpreting a string as an array and a sequence of double-quoted characters.


Handling a line of text as a string with fgets and fputs.
Reading and writing a string with sscanf and sprintf.
Manipulating strings using their pointers.
Rudimentary implementation of some string-handling functions of the standard library.
How to use the string- and character-oriented functions of the library.
Handling multiple strings using a two-dimensional array.
Sorting a line of characters and an array of strings.
How to conserve memory using an array of pointers to strings.
Tweaking the main function for invoking a program with arguments.

13.1 STRING BASICS


Programming isn’t only about crunching numbers. It also involves extensive string handling. Unlike
Java, C doesn’t support a primary data type for a string. The language approaches strings through
the “backdoor”—by tweaking the char data type. A string belongs to the category of derived data
types and is interpreted as an array of type char terminated by the NUL character. C offers no
keywords or operators for manipulating strings but its standard library supports a limited set of
string-handling functions.
Even though a string is a collection of characters, they are interpreted differently. A character
constant is a single-byte integer whose equivalent symbol is enclosed in single quotes. Thus, ‘A’
and its ASCII value, 65, can be used interchangeably in programs. But a string constant is a set of
one or more characters enclosed in double quotes. Thus, “A” signifies a string. ‘A’ uses one byte
but “A” needs two bytes which include the NUL character. Functions that operate on characters
don’t work with strings and vice versa.
Strings 413

The pointer introduces an additional dimension to a string. The string constant “Micromax”
represents a pointer to the address of its first character, i.e., 'M'. This string can thus be assigned to
a pointer variable of type char *. Since “Micromax” can also be interpreted as an array of characters,
the name of the array storing the string also represents the address of 'M'. A string can be manipulated
with both array and pointer notation even though they are not entirely equivalent.
Because strings don’t have fixed sizes, every string-handling function must know where a string
begins and where it ends. A function knows the beginning of a string from the pointer associated
with it. The end is signified by the NUL character. Functions of the standard library that
create or modify strings make sure that the NUL is always in place. However, when you create
a string-handling function, it’s your job to append the NUL at the end of the string. NUL has the
decimal value 0 and is represented by the escape sequence ‘\0’.
Note: Because a string must contain NUL, even the empty string “” takes up one byte. The length
of the string is zero though.

13.2 DECLARING AND INITIALIZING A STRING


A string can be declared either as an array of type char or as a pointer of type char *. The two
techniques of declaration are not fully equivalent. You must understand how they differ and when
to use one technique in preference to the other.
13.2.1 Using an Array to Declare a String
We have previously declared and initialized an array with a set of values enclosed in curly braces.
The same technique applies to strings as well. The following declaration (and definition) statements
also include initialization with a set of single-quoted characters:
char stg1[20] = {‘F’, ‘a’, ‘c’, ‘e’, ‘b’, ‘o’, ‘o’, ‘k’, ‘\0’};
char stg2[ ] = {‘T’, ‘w’, ‘i’, ‘t’, ‘t’, ‘e’, ‘r’, ‘\0’};
The last element in each case is NUL and has to be explicitly inserted when you initialize a string
like this. The first declaration wastes space because the string uses only nine elements. But the
compiler automatically sets the size of stg2 by counting eight items in the list.
Keying in single-quoted characters for initialization is a tedious job, so C also offers a convenient
alternative. You can use a double-quoted string to assign an array at the time of declaration:
char stg3[ ] = “Whatsapp”; Array has 9 elements and not 8
The NUL is automatically inserted when an array is assigned in this way. So, even though
sizeof stg1 evaluates to 20 (the declared size), sizeof stg2 and sizeof stg3 evaluate to 8 and 9,
respectively.
13.2.2 When an Array is Declared But Not Initialized
It is possible to declare an array—meant for holding a string—without initializing it. In that case,
declare the array in the same way you declare a variable:
char stg4[30]; Also defines stg4
414 Computer Fundamentals & C Programming

Once an array has been declared in this manner, you can’t assign values to it by using either of the
methods shown previously. This means that the following assignments are invalid:
stg4 = {‘X’, ‘o’, ‘l’, ‘o’, ‘\0’}; Wrong!
stg4 = “Xolo”; Wrong!
You must now assign the elements individually, but don’t forget to include the NUL:
stg4[0] = ‘X’; stg4[1] = ‘o’; stg4[2] = ‘l’; stg4[3] = ‘o’; stg4[4] = ‘\0’;

Most of the space allocated to stg4 are not assigned specific values. But make no mistake: this is not
a partially initialized array because values were not assigned at the time of declaration.
Of the four arrays considered, it’s only for stg3 that NUL was automatically inserted by the compiler.
We need to know why the compiler would have done the same for stg1 even if it was initialized
without including NUL.

13.2.3 Using a Pointer to Declare a String


C supports another way of creating a string. Declare a pointer to char and then assign a double-
quoted string to the pointer variable:
char *p; p currently pointing “nowhere”
p = “Redmi Note”; p contains address of ‘R’
This string comprises multiple words which scanf reads with two %s specifiers, but printf
needs a single %s to print. Like with a regular variable, you can combine declaration and initialization
in one statement:
char *p = “Redmi Note”; NUL automatically appended
The previous declarations that used an array allocated memory only for the array. But the preceding
one creates space in memory for two objects—for the pointer p and for the string constant,
“Redmi Note”. It then makes p point to the first character of the string (Figure 13.1).

R e d m i N o t e \0
2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029
Memory Addresses

2017 P char *p = "Redmi Note";

FIGURE 13.1 Memory Allocation For the Pointer and the String Pointed To
Unlike the array stg4, which signifies a constant pointer, p is a pointer constant. That doesn’t mean
p is a constant but that the object it points to is a constant. At any time, the pointer p can be unhooked
from the current string and made to point somewhere else, which could be another string or an array:
Strings 415

p = “Samsung Galaxy”; p now contains address of ‘S’


p = stg4; p points to the string “Xolo”
Note that arrays have fixed addresses which can’t be changed even though their contents can be.
For instance, you can reassign any element, say, stg4[3] to ‘C’. But you can’t change the value
stg4 evaluates to.

13.2.4 When an Array of Characters Is Not a String


An array of characters is not a string when it doesn’t include the ‘\0’ character as the terminator.
Usually, we don’t bother to include the NUL because the compiler does this job for us, but negligence
on our part can prevent the compiler from doing so. Consider the following declarations:
char stg5[5] = {‘f’, ‘l’, ‘o’, ‘a’, ‘t’}; This isn’t a string ...
char stg6[30] = {‘f’, ‘l’, ‘o’, ‘a’, ‘t’}; ... but this one is.
In this scenario, stg5 has no space left to store the NUL character. When printf encounters this
string, it continues printing beyond the ‘t’ until it encounters a NUL somewhere on the way.
This means looking at a region of memory not reserved for it. You may see extra characters printed
at the end of the word float or the program could even crash.
The NUL character has not been explicitly inserted in stg6, so one would be tempted to conclude
that stg6 is not a string. However, stg6 is a partially initialized array, which by definition, has the
uninitialized elements set to NUL. Hence, stg6 is a string.

Takeaway: The compiler automatically inserts NUL when (i) a double-quoted set of characters
is used as the value, or (ii) the array is partially initialized. When assigning an array, there must
be room for the NUL to be included.

13.3 intro2strings.c: DECLARING AND INITIALIZING STRINGS


Program 13.1 demonstrates the techniques of assigning string values to array and pointer variables.
It also shows how sizeof computes the size of an array containing a string. Finally, the program
runs scanf in a loop to repeatedly assign an array. One of the arrays is not a string, and the output
shows the consequences of not using the NUL character when it should have been used.
The program declares and initializes four arrays in four different ways. It also assigns one of the
arrays to a pointer. Three of the arrays (stg1, stg2 and stg4) are valid strings because they are
terminated—explicitly or implicitly—by NUL. However, stg3 is not a valid string. Observe that
sizeof doesn’t return the string length but the number of bytes used by the array in memory.
Printing of stg3 has created problems on this machine. printf failed to stop after printing char, but
intruded into the territory used by stg2 to print double as well. You may not get the same output,
but the output would still be unpredictable because there’s no guarantee that printf will find NUL
at the end of the character sequence.
Finally, scanf runs in a loop to read multiple words and assign each word in turn to stg1. To read
a line as a single string, scanf needs to use the scan set (9.11.1), but the fgets function (13.4.3) also
does the job. We’ll use both techniques in this chapter.
416 Computer Fundamentals & C Programming

/* intro2strings.c: Declares and initializes strings.


Shows consequence of not inserting NUL in stg3. */
#include <stdio.h>
int main(void)
{
char stg1[30] = {‘f’, ‘l’, ‘o’, ‘a’, ‘t’};
char stg2[ ] = {‘d’, ‘o’, ‘u’, ‘b’, ‘l’, ‘e’, ‘\0’};
char stg3[4] = {‘c’, ‘h’, ‘a’, ‘r’};
char stg4[ ] = “long double”;
char *p = stg4;

printf(“Size of stg1 = %d “, sizeof stg1);


printf(“Size of stg2 = %d “, sizeof stg2);
printf(“Size of stg3 = %d “, sizeof stg3);
printf(“Size of stg4 = %d\n”, sizeof stg4);

printf(“%s\n”, stg1); printf(“%s\n”, stg2);


printf(“%s\n”, stg3); printf(“%s\n”, stg4);
printf(“%s\n\n”, p);

printf(“Enter a string ([Ctrl-d] to terminate): “);


while (scanf(“%s”, stg1) == 1)
printf(“Each word: %s\n”, stg1);

return 0;
}

PROGRAM 13.1: intro2strings.c


Size of stg1 = 30 Size of stg2 = 7 Size of stg3 = 4 Size of stg4 = 12
float
double
chardouble printf accesses space of previous array
long double
long double Dereferenced value of pointer p
Enter a string ([Ctrl-d] to terminate): Lenovo K6 Power
Each word: Lenovo
Each word: K6
Each word: Power
[Ctrl-d]

PROGRAM OUTPUT: intro2strings.c

Note: It’s only when accessing a string that both printf and scanf use the same identifier name
(the name of the array). scanf doesn’t use the & prefix for this purpose.
Multi-Dimensional Arrays in C
Reading Objective:

In this reading, you will learn how arrays themselves can, in turn, be elements of an array, giving rise to an
interesting variant of arrays known as multi-dimensional arrays. You will also gain an insight into the ways of
initializing such arrays and accessing elements from them. Finally, you will also learn how these complex arrays
are stored in memory.

Main Reading Section:

Understanding and Declaring Multi-Dimensional Arrays

C allows us to use arrays of multiple dimensions. Up to this point, we have only seen and used one-dimensional
(1D) arrays. Now, we shall acquaint ourselves with two-dimensional arrays (2D arrays) in C. A 2D array is also
known as a matrix.

In essence, a 2D array is an array whose elements are, in turn, also arrays. This can be conceptualized as a grid
of elements, as we shall soon see. We may define a matrix (or 2D array) of N rows and M columns in C using
the following syntax:

element_datatype matrix_name[N][M];

Following the above syntax, suppose the following 2D array initialization: int number[3][4];

This can be conceptualized as shown in the adjoining figure (Figure 1). Clearly, we see that the number of
elements in a 2D array having “N” rows and “M” columns will be N×M. In this example, N×M = 3×4 = 12
elements.

Here are some other examples of matrix declarations:

float number[3][2]; // 6 floats stored in a grid of 3 rows and 2 columns

char name[10][20]; // 200 chars stored in a grid of 10 rows and 20 columns

Initializing a Multi-Dimensional Array

This is best understood with the help of examples. Consider the following valid ways of initializing some 2D
arrays in C:

int a[2][3] = {{1,2,3}, {4,5,6}};

int a[2][3] = {1,2,3,4,5,6};

int a[][3] = {{1,2,3}, {4,5,6}};

Each of the above examples demonstrates the creation of exactly the same matrix in C, shown in Figure 2.
Let us try to decode the above examples.

The first assignment is explicit and simple and does exactly what it ought to. It assigns the two rows each having
three elements, as described on the RHS, to the variable name on the LHS. The second initialization statement
lacks the explicitness of the first with respect to the RHS, but through the LHS, it tells the C compiler that we
require two arrays, each having three elements. Therefore, implicitly, the RHS of the assignment operation gets
divided (by the compiler) into two triplets, which then get assigned accordingly as the rows of our matrix. The
third initialization is quite puzzling at first due to the absence of the number of rows in the declaration, but that
gets clarified by the RHS of the assignment which explicitly gives us the number of rows.

Note that we absolutely cannot have a declaration where we are either omitting the number of columns or both
the number of rows as well as the number of columns. We can, however, omit the number of rows, as shown
above. This is because, if not explicitly stated, the C compiler takes the number of rows from the RHS of the
assignment. Anything otherwise results in a compile-time error.

Accessing Elements of a Multi-Dimensional Array

Accessing elements from a matrix is very similar to that from a simple 1D array, except that now we shall
require two loops instead of one. Intuitively, this can be understood by the very fact that now we have two
indices to run through for selecting an element; the number of rows and the number of columns gives us an easy
way to perform this run-through.

Let us understand the same with the help of an example that prints our matrix in a grid format:

int N=3; int M=4;

int a[N][M]={1,2,3,4,5,6,7,8,9,10,11,12};

for(i=0; i<N; i++) // outer loop, runs through each row

for(j=0; j<M; j++) // inner loop, runs through elements in a row

printf(“%d\t”, a[i][j]);

printf(“\n”);

}
We see that we are using our first loop (the outer loop) to iterate over each row. Then, for each row, we run our
second loop (the inner loop), which iterates over each element (or column) in that particular row and uses the
(i, j) index pair to print that element.

This is the most typical way to access a complete matrix in C using for-loops.

Memory Maps of 2D Arrays

If we were to think of ways of storing a 2D array in our memory, we could think of two distinct ways of doing
so. Since the memory itself is linear, we would have to make some sort of compromise, that is, we cannot store
our matrix like a grid. Either we can store all the rows of the matrix in sequential order or we can store all the
columns of the matrix in sequence. These alternatives are known as “row-major” and “column-major”,
respectively. We have to make a choice between the two for how we store our 2D arrays in memory, and this is
a choice that has to be made at the language design level. The designers of C have already made this decision
for us, and they decided to go with the row-major format.

Thus, every 2D array in C is stored “row-wise” in memory. Each row is stored sequentially, one after the other,
in the linear storage of the memory.

Reading Summary:

In this reading, you have learned about the following:

• What are multi-dimensional arrays, why they are needed, and how to initialize them

• How multi-dimensional arrays are the programming equivalent of matrices from linear algebra

• How to access elements stored in a multi-dimensional array

• How multi-dimensional arrays are stored in memory


Practice Lab 1: Multi-Dimensional Arrays in C

Assignment Brief

PracticeLab1_Question1

Consider a program that takes two integers, n and m, as input from the user. It creates a 2D array of dimensions
n*m. It then populates that array with integers by taking them as input from the user. Then the program takes
another integer called 'key' as input from the user. Now, your program should find out if this integer key is
present as an element in the 2D array or not.

[Hint: Recall the linear search algorithm from the previous week.]

If the key is found in the matrix, the variable found should be set to 1; if not, found should be reset to 0.

For example, if our 2D array is {{1,2,3},{4,5,6},{7,8,9}}, and key = 10, found should be reset to 0. If, on the
other hand, key = 2, found should be set to 1.

PracticeLab1_Question2

Consider a program that takes two integers, n and m, as input from the user. It creates a 2D array of dimensions
n*m. It then populates that array with integers by taking them as input from the user. Then the program takes
another integer called 'key' as input from the user. Now, given this 2D array, compute a number called 'term'
for the array.

The term can be calculated in the following way: find the product of all elements in a row of the array, calculate
these products for all the rows, and then sum the products of all the rows up.

For instance, for the simple 2D array {{1,2},{3,4}}, the 'term' would be calculated as 1*2 + 3*4 = 14.
Matrix Addition
Reading Objective:

In this reading, you will learn two simple but interesting operations on matrices: addition and subtraction. You
will first gain insight into how matrix addition and subtraction are done in linear algebra and then figure out a
way to perform the same operations through C programming.

Main Reading Section:

Adding Two Matrices

The addition operation on two matrices in linear algebra is defined as an operation, which results in a new
matrix of the same dimension as the operand matrices, having each element as a sum of the corresponding
elements in the operand matrices. Note that the two matrices being added must necessarily have the same
dimensions (that is, both must have same number of rows and same number of columns).

Consider the following example that illustrates matrix addition:

In a similar vein, we can also subtract two matrices, as illustrated below:

We can think of our 2D array in a C program as a matrix in linear algebra. In many practical applications, this
is assumed to be the case. A 2D array is the most popular way of simulating a matrix, and hence it is given that
moniker. As such, it would be useful for us to be able to perform matrix operations on it. Let us try to find a
way to “add” two 2D arrays in the way shown above.

Note that both of the above are operations defined in linear algebra, and we are only trying to simulate the same
using 2D arrays in our program.

Algorithm for Adding Two Matrices

1. Read the two input matrices as A and B (following the precondition that the two input matrices must
necessarily have the same dimension. We can only add two matrices if they have the same number of
rows and the same number of columns) and declare a new matrix C for storing the result.

2. For every row in A and B, perform step 3.


3. For every element in the given row of A and B, add the corresponding elements from A and B and store
the result at the corresponding index in C.
4. Display the C matrix as the result of the operation.
C Program for Adding two Matrices

We achieve the same result through the following code:

int main()

{
// Declaring and initializing the matrices and their dimensions

int M1[ROW][COL], M2[ROW][COL], M3[ROW][COL], i, j;

int row1, col1, row2, col2;

printf(“Enter number of rows for M1\n”);

scanf(“%d”,&row1);

printf(“Enter number of columns for M1\n”);

scanf(“%d”,&col1);

printf(“Enter number of rows for M2\n”);

scanf(“%d”,&row2)

printf(“Enter number of columns for M2\n”);

scanf(“%d”,&col2)

// Checking the precondition about the dimensions

if(row1!=row2 || col1!=col2)

printf(“Invalid Input: Addition is not possible”);

return;

// If the precondition is satisfied, we proceed to fill our matrices

printf(“Enter data for Matrix M1\n”);

for(i=0;i<row1;i++)

for(j=0;j<col1;j++)

scanf(“%d”,&M1[i][j]);

printf(“\n”);

printf(“Enter data for Matrix M2\n”);

for(i=0;i<row2;i++)

{
for(j=0;j<col2;j++)

scanf(“%d”,&M2[i][j]);

printf(“\n”);

// Performing the element-wise addition step

printf(“Addition of Matrices is as follows\n”);

for(i=0; i<row2; i++)

for(j=0; j<col2; j++)

M3[i][j]= M1[i][j] + M2[i][j]);

// Displaying the new matrix after performing addition

for(i=0;i<row2;i++)

for(j=0;j<col2;j++)

printf(“%-3d”,&M3[i][j]);

printf(“\n”);

The flow of the above code can easily be seen through the comments specified in it. The most important step in
the above code is highlighted, which is the matrix addition part. Most crucially, we are adding elements present
at the same index in both matrices and storing the result in the result matrix at the same index. If we do this for
every single element in both matrices, we have performed matrix addition.

Similarly, matrix subtraction can also be implemented. This would require a modification in only one line of
the code above. This is left as an exercise for you (an alteration of the same is described in the practice labs) to
explore and implement on your own.

Reading Summary:

• What matrix addition is and how it is defined in linear algebra

• What matrix subtraction is and how it is defined in linear algebra

• How to perform matrix addition on 2D arrays in C


Practice Lab 2: Matrix Addition

Assignment Brief

PracticeLab2_Question1

Write a program that performs matrix subtraction on two matrices, A and B. Implement the subtraction in two
different ways:

(a) First, take the second matrix, B, and individually multiply all of its elements by (-1), and then add this matrix
to the first matrix, A, to obtain the result of the subtraction.

(b) Write a loop that performs elementwise subtraction by modifying the appropriate line in the given code and
return the result.

We have provided the code for you to input the two matrices and output the result matrices. You are required
to write the subtraction code.

[Hint: You can take inspiration from the matrix addition code discussed with you in the video lectures and in
the reading material.]

PracticeLab2_Question2

We have three 3x3 integer arrays: A, B, and C. They represent chess boards from three different tournaments.
These three arrays hold the accumulated total number of times a chess piece has been captured at a particular
position on the chessboard.

For example, if A[2][3] = 5, it means that in the first tournament, a total of 5 pieces have been captured at the
grid position (2,3), accumulated across all games played in that tournament.

Now, based on this data, you need to compute the average number of times the pieces have been captured at
specific grid positions on the board (averaged) across all three tournaments and store that result (averages for
each grid position) into a new array.

We have provided the code for you to input the three matrices representing the chess board data and the output
which is the result of the average matrix. You are required to write the code for performing the average across
the three matrices and storing it in the result matrix.
Matrix Multiplication
Reading Objective:

In this reading, you will learn a slightly more complex operation on matrices known as matrix multiplication.
Once you familiarize yourselves with how matrix multiplication is defined, you will work your way through
the problem and learn a method for performing the same operation in C programming.

Main Reading Section:

Matrix Multiplication

Matrix multiplication is not as straightforward an operation as matrix addition. We cannot simply multiply the
corresponding elements of two matrices to get a result product matrix. That is not how matrix multiplication is
defined in linear algebra.

To comprehend how matrix multiplication works, it would help to understand how to take a “dot product” of
two vectors. Let us see that first with the help of an illustrative example. Before beginning, it may help to think
of a vector as nothing but a 1D array of elements.

In the above example, we are taking the “dot product” of the two vectors (1,2,3) and (4,5,6), signified by the
dot symbol between the vectors. These are both 1×3 vectors, and they have to be of the same dimension for the
dot product operation to be defined.

The above dot product can be represented in matrix form as shown below:

So, essentially, the dot product of two vectors (or two 1D arrays of the same dimension) is simply the sum of
the products of the corresponding elements of the vectors, as illustrated above.

Now that we understand how to take a dot product between two vectors, let us apply this to understand how
matrix multiplication is defined.

Let us say that our matrices A and B are of dimensions a×b and m×n, respectively. Matrix multiplication is
defined on these two matrices if and only if (precondition) b is equal to m. That is, if and only if the number of
columns in A and the number of rows in B are equal, we can perform matrix multiplication on A and B. In that
case, the resultant matrix will be of dimension a×n.

Now, let us see matrix multiplication in action with the following example:

This looks scary but actually it is a set of four dot products between two vectors. This can be seen more clearly
if we color code the above to show the step-by-step computation:

1.
2.

3.

4.

In this manner, we see how matrix multiplication is nothing but a series of vector dot products performed
between the rows of the first matrix and the columns of the second matrix, each treated as vectors. We also see
that the number of columns of the first matrix are indeed equal to the number of rows of the second matrix, and
hence the matrix multiplication could be performed here. Additionally, the result matrix is of dimension 2×2.

While multiplying two matrices A and B, when we multiply (take the dot product of) the row at index i in A
and the column at index j in B, the result of that multiplication (now a number) is stored at index (i,j) in the
product matrix C.

This is how matrix multiplication is defined in linear algebra.

Let us now see how this looks algorithmically.

Algorithm for Matrix Multiplication (M1 × M2)

Precondition: The number of columns in M1 should be equal to the number of rows in M2.

1. Take two matrices M1[m][n] and M2[n][r] as inputs from the user

2. Initialize an empty matrix M3[m][r] for storing the result of the multiplication

3. Repeat for i=0 to m-1

Repeat for j=0 to r-1

M3[i][j] = 0

Repeat for k=0 to n-1

M3[i][j] += M1[i][k] * M2[k][j]

4. Display matrix M3
C Program for Multiplying Two Matrices

We implement the above algorithm programmatically as follows:

#include <stdio.h>

// declaring the parameters of the matrix dimensions beforehand

#define row1 4

#define col1 3

#define row2 3

#define col2 4

#define row3 4

#define col3 4

void main()

int M1[row1][col1], M2[row2][col2], M3[row3][col3];

int i, j, k;

// Filling the matrices with elements taken as input

printf(“Enter data for Matrix M1\n”);

for(i=0; i<row1; i++)

for(j=0; j<col1; j++)

scanf(“%d”, &M1[i][j]);

printf(“\n”);

printf(“Enter data for Matrix M2\n”);

for(i=0; i<row2; i++)

for(j=0; j<col2; j++)

scanf(“%d”, &M2[i][j]);
}

printf(“\n”);

// Checking for the precondition for matrix multiplication

if (col1 != row2)

printf(“Multiplication is not possible”);

Return;

// Performing the multiplication, dot product of each row and column

for(i=0; i<row1; i++)

for(j=0; j<col2; j++)

M3[i][j] = 0;

// since col1 = row2, we could have also written

// k<row2 in the condition of the loop below

for(k=0; k<col1 ;k++)

M3[i][j] += M1[i][k] * M2[k][j];

printf(“\n”);

// Printing the result of the matrix multiplication

printf(“Result matrix:\n ”);

for(i=0; i<row3; i++)

for(j=0; j<col3; j++)

{
printf(“%d”, M3[i][j]);

printf(“\n”);

return;

The flow of the program can be seen through the comments mentioned in the code. Most importantly, we need
to see how our loops are running and how the dot products themselves are computed. There are three loops
required for performing matrix multiplication. The first loop (with loop variable i) is meant to iterate through
each row in M1. The second loop (with loop variable j) also iterates, but through each column in M2. Therefore,
in a particular iteration of these two nested loops, we have selected a row from M1 and a column from M2.
Now, therefore, we use our third loop (with loop variable k) to then compute the dot product between each row
and column as a running “sum of products” (using the loop), which we accordingly store in the respective
locations in the result matrix M3 (at location [i][j], because i represents the row in M1 we have selected, and j
represents the column in M2 that we have selected). In this manner, we construct our final matrix M3. All these
crucial parts of the code are highlighted above.

Reading Summary:

In this reading, you have learned the following:

• What matrix multiplication is and how it is defined in linear algebra. We understood this by learning the
operation from the first principles

• How to perform matrix multiplication on 2D arrays in C


Practice Lab 3: Matrix Multiplication

Assignment Brief

PracticeLab3_Question1

Suppose that we define a new kind of product called "inverted product" (inv) between two vectors in this way:
(a,b,c) inv (x,y,z) = (a+x) * (b+y) * (c+z).

Here, we are calculating the products of sums, as opposed to sums of products in the normal dot product.

Say we now want to perform a new kind of matrix multiplication whereby the product between the rows and
columns is actually the inverted product as defined above (instead of the dot product as is the case in normal
matrix multiplication).

Write a C program that performs this modified matrix multiplication on two input matrices, A and B. To simplify
your task, we have provided the code that inputs the two matrices and outputs the result. You need to compute
the modified matrix multiplication and store it in the appropriate result matrix.

PracticeLab3_Question2

There is a famous algorithm in graph mining theory known as "PageRank." The purpose of this algorithm is to
find out the most "important" page in a network on the web. This is a very useful algorithm, and the Google
search engine uses an advanced version of the same.

For the purpose of this problem, you need not know the details of the algorithm. You should know that, in the
iterative step of the simplest variant of this algorithm, we perform a matrix multiplication between two matrices,
A and P_i, to obtain P_i1, which then gets used in the next iteration to compute P_i2, and so on.

Here, A is a matrix having an equal number of rows and columns (equal to the number of web pages in the
network), and P is a column matrix having the number of rows equal to the number of web pages in the network.

Considering the matrices A and P_i at a particular iteration i, you need to perform the iterative step of the
PageRank (just one iteration) algorithm to obtain the P_i1 matrix. It is important to note that this step is just one
single matrix multiplication that you need to compute.
n-Dimensional Arrays: A Glimpse
Reading Objective:

In this reading, you will learn how multi-dimensional arrays need not be limited to just two dimensions. You
will also gain insight into how arrays of higher dimensions are described in C.

Main Reading Section:

N-Dimensional Arrays

Before last week (Week 5), our knowledge was limited to single variables and we considered them as zero-
dimensional objects. However, last week, we learned about one-dimensional (1D) arrays: a way to store more
than one object under a single variable name. This week, we have extensively explored two-dimensional (2D)
arrays: a way to store more than one array under the single variable name. This begs the question of whether or
not we can have arrays of any higher dimension.

In C, we can actually do that. A bunch of “zero-dimensional objects” (single variables) forms a 1D array. A
bunch of 1D arrays forms a 2D array. Likewise, a bunch of 2D arrays forms a 3D array. And we can extend the
same (a bunch of 3D arrays forms a 4D array, and so on) as far as we want.

Conceptually, we can visualize arrays up to a dimension of three in the following way (refer to Figure 1):

Figure 1: 1D, 2D, and 3D arrays

It is not possible for us to visualize arrays beyond the third dimension, but they can and do exist.

Declaring such higher dimensional arrays is rather simple in C and it follows the same pattern as that in 2D
arrays. Let us take a look at a few examples to understand this better.

int arr[2][2]; // 2D array of integers

int arr[2][2][3]; // 3D array of integers

int arr[2][2][2][2]; // 4D array of integers

int arr[2][2][2][2][2]; // 5D array of integers

// . . . and so on . . .

The above examples illustrate how we may declare higher dimensional arrays. Their initialization and usage in
code work is quite similar to that of 2D arrays. This segment is meant only to serve as a glimpse into higher
dimensional arrays, and you must explore the use and working of programs using the same on your own.
Reading Summary:

In this reading, you have learned the following:

• What higher dimensional arrays are in C

• How to conceptualize them

• How to declare such arrays in the C programming language


Practice Lab 4: n-Dimensional Arrays: A Glimpse

Assignment Brief

PracticeLab4_Question1

Consider a 3D array (an array of 2D arrays) of integers. This array is populated appropriately with user inputs.
From the 3D array, perform matrix addition on each of the constituent 2D arrays in it, and store the result in a
2D matrix called sum.

We have already provided the code that takes the input as dimensions of the 3D array and also the loop to
populate the 3D array. You only need to compute the sum matrix.

PracticeLab4_Question2

Consider a 3D array of integers and an integer as a key. Search the 3D array to determine whether the key
integer exists.

We have provided the code which takes the inputs for the 3D array and the key. You only need to find whether
the key exists in the 3D array.
STRINGS IN C
Reading Objective:

In this reading, you will learn the fundamentals of strings in C. you will also get introduced to the details of the
derived datatype, how to initialize strings, and the basics of solving problems using them.

Main Reading Section:

Strings : Recall from last week that strings in C are nothing but character arrays that terminate with the special
symbol ‘\0’ denoting a NULL character. These special character arrays are treated as single entities by the C
programming language, and hence they give rise to the derived datatype “strings” (strings are not part of the
primitive datatypes that come with C).

Some example declarations of strings in C are given below:

char name[20];

char address[100];

We declare strings by enclosing a bunch of characters within double quotes, as shown below:

char course[28] = “Introduction to Programming”;

The above string is equivalent to the character array {‘I’, ‘n’, ‘t’, ‘r’, ‘o’, ‘d’, ‘u’, ‘c’, ‘t’, ‘i’, ‘o’, ‘n’, ‘ ’, ‘t’, ‘o’,
‘ ’, ‘P’, ‘r’, ‘o’, ‘g’, ‘r’, ‘a’, ‘m’, ‘m’, ‘i’, ‘n’, ‘g’, ‘\0’}. Note that the terminating \0 is absolutely important for
the array to be treated as a string. This character marks the end of the string when we perform operations on it,
as we shall soon see.

With the above initialization done, we may now print the string in a convenient way that C provides us, which
is different from ordinary arrays:

printf(“%s.”, course);

%s is the special format specifier for strings in C. The above statement would run through the character array
and display (in sequence) all the characters until it reads the NULL character, \0. So, the output would be:
Introduction to Programming.

This is how strings are initialized and printed in C. Recall, once again, that strings are different from character
arrays in how C chooses to treat them. For example, the character array: char str1[4]= “BITS”; does not
implicitly include the NULL character at the end (because there is not enough space), and therefore str1 is not
a string in C. In contrast to that, the array char str2[]= “BITS”; does include the NULL character at the end of
the four characters, and therefore str2 is indeed a string in C.

Reading Strings as an Input from the User

We have seen the use of the %s format specifier in the previous section, where it was used with printf. We can
actually use the same in a scanf.

char text[30]; printf(“Enter a string: ”); scanf(“%s”, text); printf(“You have entered: %s”, text);

This program simply takes an input from the user, stores it in the character array text, and then displays it. Here
is a sample execution of the above code (user’s input is underlined):
Enter a string: BITS You have entered: BITS

This seems to be working fine, but there is a small catch with this way of receiving string input. Consider the
following execution of the same program:

Enter a string: BITS and Coursera You have entered: BITS

This does not give us the desired output. It turns out that scanf with a %s format specifier by default truncates
all strings at any “whitespace character” (things like the spacebar, \n, \t, etc). So, in our input, even if we type
“BITS and Coursera”, scanf will read everything only up to the point it encounters that first spacebar
(highlighted). This means that we cannot read any kind of sentence this way, as most sentences consist of more
than one word separated by spaces. However, strings themselves can consist of the whitespace characters.

Thus, there are alternative ways to read such strings.

One such alternative is to alter our format specifier to read everything up to the end-of-line character. This will
allow all single-line strings. In code, this looks like the following:

char text[30]; printf(“Enter a string: ”); scanf(“%[^\n]s”, text); printf(“You have entered: %s”, text);

Notice the highlighted format specifier here. This tells the program to read any character until a ‘\n’ (newline
or end-of-line, basically our Enter key) is encountered in the stream of input. This program will accept what we
were earlier trying to input. Let us see how the execution looks now:

Enter a string: BITS and Coursera You have entered: BITS and Coursera

But this is still not all-encompassing. What if we want to have a multi-line string? The ‘\n’ character can very
well be a part of a string in C, so we must have a way to read strings to allow for that. The same can be done by
altering the format specifier yet again in this way:

char text[30]; printf(“Enter a string: ”); scanf(“%[^~]s”, text); printf(“You have entered: %s”, text);

This is even more subtle, but very similar to the previous case. This tells the program to accept all kinds of
input, including newline characters, until a ‘~’ character is encountered. This is exactly like we did previously
with the ‘\n’ in place of ‘~’. Our earlier program would terminate input wherever encountered a ‘\n’, but this
new program would terminate input where it comes across a ‘~’. Of course, there is nothing special about ‘~’
character (except the fact that it does not occur very often in sentences and paragraphs), so we could easily have
used any other character to mark the end of our string.

This code executes in the following way:

Enter a string: BITS and Coursera Week 6

You have entered: BITS and Coursera Week 6

Note that there is a newline character between “Coursera” and “Week” in the above input, that is, the Enter key
was pressed there.

There are also other smart ways of modifying the string format specifier. For example, the following program
would only allow lowercase alphabets to be allowed in the string. The input terminates on encountering any
character that is not a lowercase alphabet:

char text[30]; printf(“Enter a string: ”); scanf(“%[a-z]s”, text); printf(“The string is : %s”,text);
The format specifiers in C provide a lot of power to the programmer, but there are also several ways of taking
input from the user without requiring using them. One such way is to make use of the gets function. It functions
similarly to the %[^\n]s format specifier. It reads everything up to the newline character. Following is an
example code using the same:

char str[200]; printf(“Enter a string: ”); gets(str); printf(“The string is: %s”,str);

An example execution of the above is given below:

Enter a string: Introduction to Programming

The string is: Introduction to Programming

Now that we have learned how to read strings and store them, let us go through an example problem to figure
out how to manipulate them to solve problems involving strings.

String Manipulation

Let us suppose that we wish to write a program that reads a string and eliminates the spaces from it and returns
the updated string. For instance, if our input is “Introduction To Programming”, then our program should return
“IntroductionToProgramming”.

Intuitively, we may think of solving this problem by scanning the input string from left to right and storing all
the non-space characters into a different character array and then displaying that as our new string.

int main()

char s[80], ws[80];

int i, j;

printf(“Enter the string: \n”);

gets(s); /* reading text from user */

for(i=0, j=0; s[i] != ‘\0’; i++)

if(s[i] != ‘ ’)

ws[j++] = s[i];

ws[j]=‘\0’;

printf(“The text without blank space is: \n”);

puts(ws); /* printing text on monitor */

return;

}
The above code solves our problem. We may note how for loop is essentially just a variant of linear search. We
first read our string as input from the user, store it into the variable s, and then we run one linear scan through
the string, and each time we find that a character is not a whitespace ‘ ’, we simply store that into our ws
character array. Note the updation of the variable j. j denotes the index up to which we have written in our new
array. This is required because as we skip the spaces from our original string, our indices in both strings do not
match. Our linear scan runs through to the very end of the original string, that is, up to ‘\0’, and that is where
our for loop terminates (check the condition of the for loop). In this way, we obtain a new string that contains
the same characters from the original, in the same sequence, excluding all the whitespaces.

Reading Summary:

• What strings are and how to initialize them

• Different ways of receiving a string input from the user

• Basic string manipulation (whitespace removal)


Practice Lab 5: Strings in C

Assignment Brief

PracticeLab5_Question1

Consider that a numeric string, such as “2022,” is stored in an input variable. You need to convert this numeric
string into an actual number and store the result in any integer variable. We have provided the code that takes
the string as input from the user.

PracticeLab5_Question2

Based on two input strings, you need to determine whether the second string is a substring of the first. A string
is a substring of another if all of its characters appear in the same order consecutively in the second string. For
example, "dimension" and "array" are both substrings of the string "multidimensional array."

We have provided the starter code that handles the input aspect of the problem. The program should take both
inputs as strings from the user. Now, you need to set the variable found to 1 if string2 is a substring of string1,
else reset it to 0.
CHARACTER AND STRING-RELATED FUNCTIONS
Reading Objective:

In this reading, you will learn that there are libraries in C, which support character and string-related tasks. You
will also go through some of these functions and learn their basic purpose and use case.

Main Reading Section:

Character and String-Related Functions in C

C provides us with two libraries for character and string manipulation:

• ctype.h, for character manipulation

• string.h, for string manipulation

These libraries contain functions (built-in) that we may use directly for manipulating our strings and characters.
Let us go through a couple of them briefly.

Functions in “ctype.h”

The functions in ctype.h library take a character as an input and perform some operation in it. We have already
seen and used the toupper(c) and tolower(c) functions from this library before, which convert the input string
to its uppercase and lowercase equivalents, respectively. A few other functions from this library are as follows:

isdigit(c) /* Returns a nonzero if c is a digit*/

islower(c) /* Returns a nonzero if c is a lowercase alphabetic character */

isalpha(c) /* Returns a nonzero if c is an alphabet*/

isspace(c) /* Returns a nonzero for blanks */

isupper(c) /* Returns a nonzero if c is an uppercase alphabetic character */

Functions in “string.h”

The functions in string.h library take one or more strings as argument(s) and perform some operations on them.
A few such functions from this library are listed below:

strcpy(s1,s2) /* copies s2 into s1 */

strcat(s1,s2) /* concatenates s2 to s1, i.e., appends s2 to the end of s1 */

strlen(s) /* returns the length of s */

strcmp(s1,s2) /* returns 0 if s1 and s2 are the same

returns less than 0 if s1 < s2 (lexicographically)

returns greater than 0 if s1 > s2 (lexicographically) */


The usage of these functions is best understood through the help of examples. Let us illustrate how these
functions work.

strcmp(s1,s2)

This function compares the two input strings lexicographically. To understand the output of this function, we
must first understand lexicographical comparisons. An easy way to comprehend lexicographical ordering is to
think of which strings would appear earlier in a dictionary. A lexicographical comparison is when we compare
two arrays element by element. This means that if our first characters are different, then we already have our
answer. However, if they are equal, we move to the second characters to settle the comparison; if they are also
equal, we move to the third, and so on. So, if we are to compare the two strings “abc” and “def”, clearly, the
former should come before the latter (< case). If we are now to compare “abc” with “aba”, the latter should
come before the former (> case). The = case is trivial. This is what lexicographical comparison means. The
function strcmp does exactly the same for us.

strcmp compares every character in the string, character by character, until we find a mismatch (< or > case) or
simultaneously reach the end of both strings (= case). Internally, the ASCII code of each character is used to
perform these element-wise comparisons.

To summarize, strcmp(s1,s2):

• returns 0, if both strings are identical

• returns a value > 0, when the first mismatched character in s1 has a greater ASCII value than the
corresponding character in s2

• returns a value < 0, when the first mismatched character in s1 has a lesser ASCII value than the
corresponding character in s2

Some examples of strcmp’s output:

1. s1 = “xyz”, s2 = “xyz”: returns 0 (the strings are exactly the same).


2. s1 = “ybk”, s2 = “yag”: returns > 0 (the first mismatched character is the second, where s2’s second
character has a lower ASCII value than s1’s second character).
3. s1 = “yck”, s2 = “Yaa”: returns < 0 (the first characters themselves are mismatched, and the uppercase
Y of s2 has a lower ASCII value than the lowercase y of s1).
Consider the following example problems that illustrate the usage of some of the above functions:

Problem 1: Write a function to return the middle character of an input string.

#include <stdio.h> #include <string.h>

char return_middle(char str[])

int m = strlen(str) / 2;

return str[m];

int main(void)
{

printf("%c", return_middle("BITSP"));

This function finds the length of the string and divides that by 2 to find the middle of the string and returns the
character there. The output of the given main program is the character ‘T’, which is the middle character of the
string “BITSP”.

Problem 2: Write a program that tells us whether “Apple” or “Amazon” would come first in a dictionary.

#include <stdio.h> #include <string.h>

int main(void)

char string1[] = "Apple";

char string2[] = "Amazon";

if (strcmp(string1, string2) < 0)

printf("%s would appear first in a dictionary of words.", string1);

else if(strcmp(string1, string2) > 0)

printf("%s would appear first in a dictionary of words.", string2);

else

printf("These are the same words.");

In this program, we have used the strcmp() function, which returns a negative number in case the first input
string to it would appear first in a lexicographical ordering, and likewise would return a positive number in case
the second input string would appear first. Since dictionary order is nothing but lexicographical order, we
employ this function to solve the problem. The if-else construct is used to check the return value of strcmp, and
accordingly display output to the user. Clearly, the output of this program would be “Amazon would appear
first in a dictionary of words.”

Problem 3: Write a program that concatenates the two strings “BITS Pilani” and “and Coursera” and displays
the result.

#include <stdio.h> #include <string.h>

int main(void)

char string1[] = "BITS Pilani";

char string2[] = " and Coursera";

strcat(string1, string2);
printf("%s", string1);

Recall that the strcat() function takes two strings as input and appends the second to the first. Our problem is
easily solved with this function, so we use it to concatenate the two strings and then display their output. The
output of this program would therefore be “BITS Pilani and Coursera”.

Other Functions in string.h Library

There are several other functions in string.h. A few of them are listed in this section with brief explanations and
usage:

You can explore the details and proper use of these functions. The above table serves the purpose of informing
you that such functions exist in C and can be used to solve our problems. Not every single thing needs to be
created from scratch; there are some libraries in C to help us in this regard.

Reading Summary:

In this reading, you have learned the following:

• The functions that the ctype.h and string.h libraries in C provide

• The purpose of some of those functions from the aforementioned libraries

• Basic examples to understand how to use them


Practice Lab 6: String Manipulation and Operations in C

Assignment Brief

PracticeLab6_Question1

Consider the code that accepts two strings as input from the user. These strings represent the first name and the
last name of a student, respectively. Based on the input strings (without spaces), you need to concatenate these
strings to display the student’s full name in block letters.

For instance, if the inputs are "Robert" and "Plant," your output should be "ROBERT PLANT."

PracticeLab6_Question2

Consider an input string, for which your program should determine the number of alphabets, digits, and other
symbols. You need to report those numbers by printing them. We have already provided the code which reads
the string as input. You only need to write the code to find out the required counts.

PracticeLab6_Question3

Consider two sample strings already provided in the program, each of which stores two floating point numbers
(but they are strings). You need to convert these strings to actual floating point numbers and then multiply them
and display the result on the screen.

[Hint: There exists a library function that may help you here.]
String Manipulation and Arrays of Strings
Reading Objective:

In this reading, you will learn more about string manipulation. You will also try to implement two of the library
functions from string.h from first principles, and in doing so, you will gain a better understanding of how strings
can be manipulated in the programming language. In addition, you will also learn about another classic problem
of detecting palindromic strings and how to solve it. Thereafter, you will be introduced to the basics of arrays
of strings and see how to declare and use them to aid your programming.

Main Reading Section:

More on String Manipulation

Problem 1: Read a string from the user character by character and print its length.

int n = 0; char text[100], c; while((c = getchar())!=‘\n’&& n<99) text[n++] = c;

text[n++]=‘\0’; printf(“Length of text : %d”,n);

The above code makes use of the getchar() function to read an individual character and uses a while loop to
implement the logic that, as long as the character received as input is not an endline character \n and we haven’t
exhausted our capacity (99 in the above example, note that we are leaving one space free for appending the \0),
we keep reading. When the user is done inputting (that is, when they input \n or when the capacity is exhausted),
we exit the loop and manually insert a \0 at the next location. Throughout this, we maintain and keep updating
our counter variable n, and simply display it at the end as the length of our string.

Note how the above code essentially implements the strlen() function from scratch, manually.

Problem 2: Read two strings from the user and append/concatenate the second string to the end of the first.

char s1[100],s2[100]; int i = 0,j = 0; printf("Enter first string\n"); scanf("%[^\n]s",s1); getchar(); printf("Enter
second string\n"); scanf("%[^\n]s",s2); while(s1[i++] != '\0'); i--; while(s2[j] != '\0') s1[i++] = s2[j++]; s1[i] =
'\0'; printf("\n Final string is:%s",s1);

This program is a little subtle as this is similar to how the strcat function is implemented internally in the string.h
library. Let us look at each of its aspects. Simply put, we are taking our two strings using the scanf statements
with %[^\n]s modifier as input. However, very crucially, we are also including a getchar statement between the
two scanf()s. This is important because when the user types in the first string, they terminate it with a \n. This
\n character remains in the buffer, and might interfere with our succeeding scanf, unless captured explicitly.

Thereafter, we have a simple while loop without a body (notice the semicolon immediately after the while).
This while loop simply traverses us to the very end of the first string (note the i++ in the index of s1). After this
traversal, we execute a single i--; so that we exclude the last \0 from the first string. The next while loop starts
from the point where \0 is supposed to be in s1 and overwrites into s1’s character array everything from the start
up to the end of s2, excluding the final \0 at the end of s2 (which we append manually after the loop ends).

After this, when we print s1, what we get is s1 with s2 appended to its end (note how we have cleverly removed
the \0 that would have come in between the two had we appended blindly).

Note how the above code is a manual implementation of the strcat() function from scratch.
It must be clarified that there is no special reason to have solved these two problems in this particular manner
from the first principles, apart from getting a better idea of how these libraries are internally implemented. The
strlen and strcat functions exist and ought to be used by the programmer as and when deemed necessary (both
these problems could have been solved directly using the library functions). However, going through the
problem from first principles proves to be a more enlightening learning experience. At the end of the day, we
must acknowledge that these libraries have also been coded by people and going through the problems from
first principles makes us appreciate their availability even more, in addition to strengthening our core concepts.

Problem 3: Write a program to check whether a given string is a palindrome (a palindrome is a string that reads
the same forwards and backwards, that is, the reverse of the string is the same as the original string).

void main()

char str[80];

int left, right, i, len, flag = 1;

printf("Enter a string");

for(i = 0;(str[i] = getchar())!='\n';++i);

len = i-1;

for(left = 0,right = len; left < right; ++left,--right)

if(str[left] != str[right])

flag = 0;

break;

if(flag)

printf("\n String is palindrome");

else

printf("\n String is not a palindrome");

In this program, we first accept our string using a very clever for-loop, which has no body and functions, and is
like the while loop in the problem 2. We, thus, also obtain the length of the array using i. Our second for-loop
is also quite clever. It runs two indices through our array, one from left to right, and the other from right to left.
The loop will terminate if at any point the characters at our respective left and right indices are not matching
and then we reset the flag to 0, thereby declaring that our string is not a palindrome. On the other hand, if our
for-loop runs through to the very end (that is, up to the midpoint of the string; we do not need to check beyond
that if we get this far) without ever mismatching, then our flag remains set, and our string is a palindrome (the
inner if statement never executes because the string always matches on reading from left to right and from right
to left because it is a palindrome).

Arrays of Strings

An array of strings is essentially a 2D array of characters. We have learnt 2D arrays in the previous module. All
those principles still apply here.

Arrays of strings can be declared as shown in the example below:

char names[5][30]; // 5 names, each having max 30 characters char words[100][20]; // 100 words, each having
max 20 characters

We may initialize them in the following way:

nums[5][10] = {“One”, “Two”, “Three”, “Four”, “Five”};

Other valid initializations of the same are shown below:

char nums[][] = {“One”, “Two”, “Three”, “Four”, “Five”}; char nums[][5] = {“One”, “Two”, “Three”, “Four”,
“Five”};

Let us take a look at how one similar array of strings looks like in memory. Consider the array:

char cities[4][12] = { “Chennai”, “Kolkata”, “Mumbai”, “New Delhi” };

We can print the second string of the array, that is, “Kolkata” by the following statement:

printf(“%s”, city[1]);

Also, Fig. 1 illustrates how the above array would look inside the memory.

Figure 1: Arrays of strings in memory

Reading Summary:

• Intricate ways of manipulating strings in C

• How to implement the strlen() and strcat() functions manually

• How to programmatically detect palindromic strings

• Basics of arrays of strings: how and why to use themHow arrays of strings are stored in the memory (similar to 2D
arrays)
Practice Lab 7: Array of Strings in C

Assignment Brief

PracticeLab7_Question1

Consider an array of words (arranged in dictionary alphabetical order) and a keyword. You need to use the
strcmp library function to determine where in the array the input key should be placed to maintain the dictionary
order of the words.

We have already provided the code for you that contains the sample array of strings and takes the keyword input
from the user.

PracticeLab7_Question2

Consider a program that takes ten different strings as input and stores them into an array of words. You need to
determine which of these ten input strings (now stored in the array of words) has the smallest length.

[Hint: You may use a library function here.]

PracticeLab7_Question3

Consider a program that takes ten strings as input and stores them into an array of strings. You need to iterate
over this array and detect which of these strings start with an alphabet and which do not. For those that do, you
must store them in a separate array called alpha, and for those that do not, you must store them in another array
called non-alpha. The program should then display the contents of these two additional arrays.
Solutions to Practice Labs
P ac ce_Lab_1/Q1

Given a D arra and an element ke this program finds if the element is


present in the D arra

include stdio.h

int main(void)
{
int n, m;
printf("Enter the number of rows and columns: ");
scanf(" d d", &n, &m);
int arr[n][m];
printf("Enter the elements of the array: ");

for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
scanf(" d", &arr[i][j]);
}
}

int key;
printf("Enter the element to be searched: ");
scanf(" d", &key);

Linear search on the D arra across rows and columns to find ke


int found 0;
for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
if (arr[i][j] key)
{
found 1;
break;
}
}
}

if (found 1)
{
printf("Element found");
}
else
{
printf("Element not found");
}
}
P ac ce_Lab_1/Q2

Given a D arra this program computes the sum of the products of the
elements in each row in the matrix

include stdio.h

int main(void)
{
int n, m;
printf("Enter the number of rows and columns: ");
scanf(" d d", &n, &m);
int arr[n][m];
printf("Enter the elements of the array: ");

for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
scanf(" d", &arr[i][j]);
}
}

int prod 0;
int term 0;
for (int i 0; i n; i )
{
prod arr[i][0];
for (int j 1; j m; j )
{
prod prod * arr[i][j];
}
term term prod;
prod 1;
}

printf("Term d", term);


}
P ac ce_Lab_2/Q1

This program performs matrix subtraction


include stdio.h

int main(void)
{
int n, m;
printf("Enter the number of rows and columns: ");
scanf(" d d", &n, &m);
int arr1[n][m];
int arr2[n][m];
printf("Enter the elements of the first array: ");

for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
scanf(" d", &arr1[i][j]);
}
}

printf("Enter the elements of the second array: ");

for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
scanf(" d", &arr2[i][j]);
}
}

int arr31[n][m];
for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
arr31[i][j] arr1[i][j] - arr2[i][j];
}
}

for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
arr2[i][j] (-1) * arr2[i][j];
}
}

int arr32[n][m];
for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
arr32[i][j] arr1[i][j] arr2[i][j];
}
}

printf("The result of matrix subtraction (by the first method) is: \n");
for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
printf(" d ", arr31[i][j]);
}
printf("\n");
}

printf("The result of matrix subtraction (by the second method) is: \n");
for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
printf(" d ", arr32[i][j]);
}
printf("\n");

}
}
P ac ce_Lab_2/Q2

This program computes the average number of times pieces have been captured at
a location on a chessboard in three different tournaments
include stdio.h

int main(void)
{
int A[3][3];
int B[3][3];
int C[3][3];
float D[3][3];

printf("Enter the array elements from the data of the first tournament: \n");
for (int i 0; i 3; i )
{
for (int j 0; j 3; j )
{
scanf(" d", &A[i][j]);
}
}

printf("Enter the array elements from the data of the second tournament:
\n");
for (int i 0; i 3; i )
{
for (int j 0; j 3; j )
{
scanf(" d", &B[i][j]);
}
}

printf("Enter the array elements from the data of the third tournament: \n");
for (int i 0; i 3; i )
{
for (int j 0; j 3; j )
{
scanf(" d", &C[i][j]);
}
}

for (int i 0; i 3; i )
{
for (int j 0; j 3; j )
{
D[i][j] (A[i][j] B[i][j] C[i][j]) / 3.0;
}
}

printf("The array with the averaged data is as follows: \n");


for (int i 0; i 3; i )
{
for (int j 0; j 3; j )
{
printf(" f ", D[i][j]);
}
printf("\n");
}
}
P ac ce_Lab_3/Q1

Multipl two matrices

include stdio.h

int main(void)
{
int n, m, p; n x m and m x p matrices
printf("Enter n: ");
scanf(" d", &n);
printf("Enter m: ");
scanf(" d", &m);
printf("Enter p: ");
scanf(" d", &p);

int a[n][m], b[m][p], c[n][p];


printf("Enter elements of the first matrix: ");
for (int i 0; i n; i )
{
for (int j 0; j m; j )
{
scanf(" d", &a[i][j]);
}
}
printf("Enter elements of the second matrix: ");
for (int i 0; i m; i )
{
for (int j 0; j p; j )
{
scanf(" d", &b[i][j]);
}
}

for (int i 0; i n; i )
{
for (int j 0; j p; j )
{
c[i][j] 1;
for (int k 0; k m; k )
{
c[i][j] * a[i][k] b[k][j];
}
}
}

printf("The product of the matrices is: ");


for (int i 0; i n; i )
{
for (int j 0; j p; j )
{
printf(" d ", c[i][j]);
}
printf("\n");
}
}
P ac ce_Lab_3/Q2

Given a square matrix of nxn and a column vector of n values perform matrix
multiplication on them

include stdio.h

int main(void)
{
int A[5][5] {{0,1,0,1,0}, {1,0,1,0,1}, {1,1,0,1,1}, {0,0,1,0,1},
{0,1,1,1,0}};
float P_i[5] {0.75,0.666,0.45,0.33,0.832};
float P_i1[5] {0,0,0,0,0};

for (int i 0; i 5; i )
{
for (int j 0; j 5; j )
{
P_i1[i] A[i][j] * P_i[j];
}
}

for (int i 0; i 5; i )
{
printf(" f ", P_i1[i]);
}
}
P ac ce_Lab_4/Q1

Given a D arra this code runs matrix addition on each of the constituent D
arra s and returns final sum matrix

include stdio.h

int main(void)
{
int x,y,z;
printf("Enter the dimensions of the 3D array: ");
scanf(" d d d", &x, &y, &z);
int arr[x][y][z], sum[x][y];
printf("Enter the elements of the 3D array: ");
for(int i 0; i x; i )
{
for(int j 0; j y; j )
{
for(int k 0; k z; k )
{
scanf(" d", &arr[i][j][k]);
}
}
}

int line 0;
for(int i 0; i x; i )
{
for(int j 0; j y; j )
{
line 0;
for(int k 0; k z; k )
{
line arr[k][i][j];
}
sum[i][j] line;
}
}
printf("The sum of the constituent 2D arrays is: ");
for(int i 0; i x; i )
{
for(int j 0; j y; j )
{
printf(" d ", sum[i][j]);
}
printf("\n");
}
}
P ac ce_Lab_4/Q2

Searches for a ke in a D arra of integers

include stdio.h

int main(void)
{
int x,y,z;
printf("Enter the dimensions of the array: ");
scanf(" d d d", &x, &y, &z);
int arr[x][y][z];
printf("Enter the elements of the array: ");
for(int i 0; i x; i )
{
for(int j 0; j y; j )
{
for(int k 0; k z; k )
{
scanf(" d", &arr[i][j][k]);
}
}
}

int key;
printf("Enter the key to be searched: ");
scanf(" d", &key);

int found
for(int i 0; i x; i )
{
for(int j 0; j y; j )
{
for(int k 0; k z; k )
{
if(arr[i][j][k] key)
{
printf("The key was found");
return 0;
}
}
}
}
printf("The key was not found in the array");
}
P ac ce_Lab_5/Q1

For a numeric string stored in a string variable this code converts that and
stores it into an integer variable
include stdio.h

int main(void)
{
int n;
printf("Enter the length of the string (number of digits in the numeric
string): ");
scanf(" d", &n);
char str[n];
printf("Enter the numeric string: ");
scanf(" s", str);
int num 0;
int i 0;
while(str[i] ! '\0')
{
num num * 10 (str[i] - '0');
i ;
}
printf(" d", num);
}
P ac ce_Lab_5/Q2

Finds if the second string is a substring of the first


include stdio.h

int main(void)
{
int n,m;
printf("Enter the length of the first string: ");
scanf(" d", &n);
printf("Enter the length of the second string: ");
scanf(" d", &m);
if (m n)
{
printf("The second string cannot be a substring of the first string.");
return 0;
}
char str1[n], str2[m];
printf("Enter the first string: ");
scanf(" s", str1);
printf("Enter the second string: ");
scanf(" s", str2);
int found 0;
for(int i 0; i n; i )
{
if(str1[i] str2[0])
{
for(int j 0; j m; j )
{
if(str1[i j] ! str2[j])
{
break;
}
if(j m-1)
{
found 1;
}
}
}
}
if(found 1)
{
printf("The second string is a substring of the first string.");
}
else
{
printf("The second string is not a substring of the first string.");
}
}
P ac ce_Lab_6/Q1

Given a student s first name and last name as strings without spaces as
inputs this code concatenates them and displa s the full name in block letters

include stdio.h
include ctype.h
include string.h

int main(void)
{
char first[50], last[50];
int i;
printf("Enter first name: ");
scanf(" s", first);
printf("Enter last name: ");
scanf(" s", last);
char full[100] "";
strcat(full, first);
strcat(full, " ");
strcat(full, last);
for (i 0; i strlen(full); i ) {
printf(" c", toupper(full[i]));
}
}
P ac ce_Lab_6/Q2

Counts the number of alphabets and digits and other s mbols in an input string
and reports those numbers b printing them

include stdio.h
include ctype.h

int main(void)
{
int n;
printf("Enter length of the string: ");
scanf(" d", &n);
char str[n];
printf("Enter the string: ");
scanf(" s", str);

int alphabets 0, digits 0, others 0;

for (int i 0; i n; i )
{
if (isalpha(str[i]))
{
alphabets ;
}
else if (isdigit(str[i]))
{
digits ;
}
else
{
others ;
}
}

printf("Alphabets: d\n", alphabets);


printf("Digits: d\n", digits);
printf("Others: d\n", others);
return 0;
}
P ac ce_Lab_6/Q3

Given two strings that represent floating point numbers this program performs
multiplication on them b first converting them from strings to floating point
numbers and then multipl ing them The result is then printed

include stdio.h
include stdlib.h

int main(void)
{
char str1[10] "-14.20";
char str2[10] "3.141";
char str3[10];
double num1 atof(str1);
double num2 atof(str2);
double num3 num1 * num2;
printf("Sum: f", num3);
}
P ac ce_Lab_7/Q1

Given an arra of words arranged alphabeticall and a ke word this code finds
out where in that order the ke should be inserted

include stdio.h
include string.h

int main(void)
{
char words[10][20] {"apple", "banana", "cherry", "grape", "kiwi", "lemon",
"orange", "pear", "pineapple", "watermelon"};
char key[20];
char temp[20];
int i, j, k, result;

printf("Enter a word: ");


scanf(" s", key);

Convert ke to lowercase and store it in temp for comparison


for (i 0; i strlen(key); i )
{
if (key[i] 'A' && key[i] 'Z')
{
temp[i] key[i] 32;
}
else
temp[i] key[i];
}

find which index ke should be placed in for arra to be in order


for (i 0; i 10; i )
{
result strcmp(temp, words[i]);
if (result 0)
{
break;
}
}

printf("The word s should be placed at index d in the array.\n", key, i);


}
P ac ce_Lab_7/Q2

From an arra of strings given as an input this code finds out which of
the strings in the arra has the smallest length

include stdio.h
include string.h

int main(void)
{
char words[10][50];
int i, j, min 0;
for (i 0; i 10; i ) {
printf("Enter string d: ", i 1);
scanf(" s", words[i]);
}
for (i 0; i 10; i ) {
if (strlen(words[i]) strlen(words[min])) {
min i;
}
}
printf("The first string with the smallest length is s", words[min]);
}
P ac ce_Lab_7/Q3

Given an arra of strings this code separates the arra into two arra s one
for strings that start with an alphabet and another for those that do not

include stdio.h
include ctype.h
include string.h

int main(void)
{
char strings[10][100];
char alpha[10][100];
char nonalpha[10][100];
printf("Enter 10 strings: ");
for (int i 0; i 10; i )
{
scanf(" s", strings[i]);
}
int alpha_count 0;
int nonalpha_count 0;
for (int i 0; i 10; i )
{
if (isalpha(strings[i][0]))
{
strcpy(alpha[alpha_count], strings[i]);
alpha_count ;
}
else
{
strcpy(nonalpha[nonalpha_count], strings[i]);
nonalpha_count ;
}
}
printf("The strings that start with an alphabet are: ");
for (int i 0; i alpha_count; i )
{
printf(" s ", alpha[i]);
}
printf("\nThe strings that do not start with an alphabet are: ");
for (int i 0; i nonalpha_count; i )
{
printf(" s ", nonalpha[i]);
}
}
Lesson 1: Multi-Dimensional Arrays C and Matrix Operations (Sections 10.12-10.15)

10.12 TWO-DIMENSIONAL (2D) ARRAYS


C supports multi-dimensional arrays to represent tables and complex data structures. Each
element of such an array is accessed with multiple subscripts. A two-dimensional array
needs two subscripts and a three-dimensional array needs three subscripts. Like a single-
dimensional array, a multi-dimensional array occupies a contiguous region of memory. Arrays 317

A two-dimensional (2D) array, arr, takes the form arr[i][j], where the subscript i represents the
number of rows and j represents the number of columns. The following statement declares a 2D
array without initializing it:
short table[3][4];

Because a 2D array is actually a set of rows and columns, the symbolic constants, ROWS and COLUMNS,
are often used to represent its size. Internally though, a 2D array can be viewed as an array of
single-dimensional arrays. This means that table represents a set of three single-dimensional arrays
of four elements each. Alternatively, it can be considered as three rows having four columns in each
row. Figure 10.2 depicts the layout in memory of an array having these dimensions. Knowledge of
this organization will help us understand pointers when the concept is applied to arrays.

Row 0 Row 1 Row 2


1 2 3 4 11 22 33 44 111 222 333 444
a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]

short a[3][4] = {{1, 2, 3, 4}, {11, 22, 33, 44}, {111, 222, 333, 444}};

FIGURE 10.2 Layout in Memory of the 2D array a[3][4]

Note from Figure 10.2 that a total of 12 memory slots (typically using 24 bytes) are allocated for
this array. The first four cells are used by the four columns of row 0, followed by four columns
each of rows 1 and 2. This means that, when memory is scanned sequentially, a column changes
faster than its row. Thus, the array can be completely accessed by using the row and column as key
variables in an outer and inner loop, respectively.

10.12.1 Full Initialization During Declaration


We can employ the usual technique of using curly braces for initialization of a 2D array at the time
of declaration, except that you may also have to use a set of inner braces to group the columns of
each row. Here’s how table is declared and initialized with 15 values:
short table[3][5] = { {0, 1, 2, 3, 4}, Row 0
{10, 11, 12, 13, 14}, Row 1
{20, 21, 22, 23, 24} }; Row 2
The columns of each row should be enclosed by a set of inner curly braces and placed on a separate
line. It then becomes easy to determine the value of an element by visual inspection. For instance,
table[1][2] is 12 and table[2][0] is 20. Observe the comma that follows the closing curly brace
of each row and not the number on its left. For a fully initialized array (like the one above), we can
also do without the inner braces:
short table[3][5] = {0, 1, 2, 3, 4, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24};
318 Computer Fundamentals & C Programming

It could be difficult to relate values to subscripts when the inner braces are dropped, so you should
use them wherever possible—specially for large arrays. When all values are known, C permits
dropping the subscript for the row:
short table[ ][5] = { {0, 1, 2, 3, 4},
{10, 11, 12, 13, 14},
{20, 21, 22, 23, 24} };
The inner braces and subscript for the row can be dropped only if their omission doesn’t lead to
ambiguity. This is not an issue for a fully initialized array like the one above, so we can drop the
braces and the compiler will still be able to correctly determine the number of rows.

10.12.2 Partial Initialization During Declaration


C permits partial initialization of an array. The braces in that case are necessary because without
them there is no way the compiler can figure out how the values are organized among the rows
and columns. Consider the following declarations:
short table1[3][5] = { {0, 1, 2}, {10, 11}, {20} };
short table2[ ] [5] = { {0, 1, 2}, {10, 11}, {20} };
For both arrays, the three elements of the first inner group belong to row 0, the next two belong to
row 1 and the last element belongs to row 2. The remaining elements are initialized to zeroes as
shown below in the row-column view of the data:
0 1 2 0 0 Row 0
10 11 0 0 0 Row 1
20 0 0 0 0 Row 2
Because of the presence of the braces, the compiler correctly computes the number of rows in the
declaration of table2. Omission of the inner braces would confuse the compiler when it looks at
the following data:
short table2[3][5] = { 0, 1, 2, 10, 11, 20 };
The compiler would now assign the first five elements to row 0 and the last element to row 1.
It would also incorrectly compute the number of rows for table2. The row-column view would
then change to this:
0 1 2 10 11 Row 0
20 0 0 0 0 Row 1
0 0 0 0 0 Row 2
The message is loud and clear. Use inner braces whether initialization is partial or complete.

10.12.3 Assignment After Declaration


Like with single-dimensional arrays, elements can also be assigned values after declaration. The
assigned value can be a constant, variable or expression which can include another array element:
table[1][4] = 14;
table[2][7] = x;
table[2][4] = table[1][4] + x;
Arrays 319

You can also use scanf to assign a value to a 2D array element. Don’t forget to use the & prefix:
scanf(“%hd”, &table[2][3]);
scanf can also be used in a nested loop construct to populate an entire 2D array. The statement
scanf(“%hd”, &table[i][j]); does the job but it could be inconvenient to key in data without
knowing which element it is meant for.

Takeaway: C uses the inner curly braces to assign a set of values to a specific row of a 2D
array. If you partially assign a row, then you must use the braces because otherwise the compiler
would simply assign the values to all of the columns of a row before taking up the next row.

10.12.4 Assignment and Printing Using a Nested Loop


You need a nested loop to assign a single value or expression to an entire 2D array. The outer loop
changes the row and the inner loop changes the column. Program 10.11 fully initializes table to
an expression. The same nested loop also prints the contents of the array.

/* print_2d_array.c: Populates and prints a 2D array. */


#define ROWS 3
#define COLUMNS 5
#include <stdio.h>
int main(void)
{
short i, j;
short table[ROWS][COLUMNS];
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLUMNS; j++) {
table[i][j] = i * 10 + j * 2;
printf(“%5hd”, table[i][j]);
}
printf(“\n”);
}
return 0;
}

PROGRAM 10.11: print_2d_array.c


0 2 4 6 8
10 12 14 16 18
20 22 24 26 28

PROGRAM OUTPUT: print_2d_array.c


320 Computer Fundamentals & C Programming

10.13 count_numbers.c: USING A 2D ARRAY AS A COUNTER


Program 10.12 uses a 2D array to implement a frequency counter. The program accepts a set of
integers as user input and sequentially assigns them to the first row of the array arr, but only after
checking that the number doesn’t already exist there. The corresponding column of the next row
maintains a count for the number. For instance, the first number is assigned to arr[0][0] and its
count is saved in arr[1][0].
/* count_numbers.c: Uses a 2D array as a frequency counter.
First row stores number, second row stores frequency. */
#include <stdio.h>
int main(void)
{
short i, imax = 0, tmp;
short arr[2][50] = {{0}}; /* All array elements initialized */
char is_found;
printf(“Key in some integers (q to quit): “);
while (scanf(“%hd”, &tmp) == 1) {
is_found = ‘n’;
for (i = 0; i < imax; i++) { /* Check whether number exists */
if (arr[0][i] == tmp) { /* If number found in array ... */
arr[1][i]++; /* ... then increment its count */
is_found = ‘y’;
break; /* Abandon further checking */
}
}
if (is_found == ‘n’) { /* If number not found in array ... */
arr[0][i] = tmp; /* ... add number to array ... */
arr[1][i] = 1; /* ... and set its count to 1 */
imax++; /* Increase limit of search */
}
}
/* Print array contents in pairs */
for (i = 0; i < imax; i++)
printf(“%5hd = %2hd\n”, arr[0][i], arr[1][i]);
return 0;
}

PROGRAM 10.12: count_numbers.c


Key in some integers (q to quit): 11 33 22 -1 0 33 666 33 22 0 33 q
11 = 1
33 = 4
22 = 2
-1 = 1
0 = 2
666 = 1
PROGRAM OUTPUT: count_numbers.c
Arrays 321

Each number that is input is sequentially looked up in the first row of the array. This is how the
program responds to the result of the search:
If the number is found in that row, then its corresponding column in the second row is
incremented (arr[1][i]++;). The for loop is then terminated with break for the next number
to be keyed in.
If the number doesn’t exist in the first row, it is assigned to the next free element of that row.
The count in the corresponding column of the second row is then set to 1. The variable, imax,
is then incremented for use by the next number to be added.
After all numbers have been input, the expression imax - 1 evaluates to the number of elements
used by the first row of arr. The last for loop uses this value to print the key-value pairs of only
those array elements that have been utilized.
Note that even though the highest number input was 666, only 12 elements of the array were used.
Program 10.10 (which used the number itself as the array index) would have needed an array of
666 elements. That program will still fail here because it can’t handle negative numbers, which
this program does with ease.

10.14 MULTI-DIMENSIONAL ARRAYS


Multi-dimensional arrays in C can go beyond two dimensions. Every increase in dimension increases
the number of subscripts by one, with the subscript on the right changing faster than the one on
the left. A three-dimensional (3D) array can be treated as an array of arrays of arrays. In this case,
only those elements accessed with the right-most subscript—with the other subscripts remaining
unchanged—occupy consecutive memory cells.
While the principles of memory allocation and initialization remain unchanged for multi-dimensional
arrays, visualization of an array becomes difficult when the number of dimensions exceeds three.
A 3D array is declared in this manner:
int max_temp[YEARS][MONTHS][DAYS];

This array can store the maximum daily temperature, grouped by month, for multiple years. Three
loops are needed to initialize the elements:
for (i = 0; i < YEARS; i++)
for (j = 0; j < MONTHS; j++)
for (k = 0; k < DAYS; k++) If all values are 0, simply ...
max_temp[i][j][k] = 0; ... initialize one element to 0
The symbolic constants representing the subscripts, MONTHS and DAYS, would have the values 12 and
31, respectively. The month changes after every 31 elements, while the year changes after 31 ¥ 12
elements. Because all months don’t have 31 days, special logic has to be built into a program to
skip those elements that point to invalid days.
322 Computer Fundamentals & C Programming

10.15 USING ARRAYS AS MATRICES


In mathematics, you would have encountered the matrix as a rectangular array of numbers or
expressions. A matrix is represented by a name, say, A, having the dimensions m ¥ n. Each element
of the matrix is represented by the notation, say, ai,j, where the subscripts i and j cannot exceed m
and n respectively. This is how a 3 ¥ 4 matrix named A is represented in mathematics:
a1,1 a1,2 a1,3 a1,4
A = a2,1 a2,2 a2,3 a2,4
a3,1 a3,2 a3,3 a3,4

A two-dimensional array can easily implement a matrix except that the array index starts from
0 and not 1. Thus, the matrix element a1,1 is the same as the array element a[0][0]. In the general
case, am,n is equivalent to a[m - 1][n - 1]. We’ll now examine three programs that use 2D arrays
to implement the most commonly performed operations on matrices.

10.15.1 matrix_transpose.c: Transposing a Matrix


A matrix is transposed by swapping its rows and columns. Thus, the first row becomes the first
column; the last row becomes the last column. Program 10.13 transposes the matrix represented
by the 2D array, mat1, and saves the output in the array, mat2.

/* matrix_transpose.c: Transposes a matrix;


converts rows to columns and vice versa. */
#include <stdio.h>
#define COLUMNS 3
#define ROWS 3
int main(void)
{
short mat1[ROWS][COLUMNS] = {{1, 2, 3},
{4, 5, 6},
{7, 8, 9}};
short mat2[ROWS][COLUMNS];
short i, j;
for (i = 0; i < ROWS; i++)
for (j = 0; j < COLUMNS; j++)
mat2[i][j] = mat1[j][i];
printf(“Transposed matrix:”);
for (i = 0; i < ROWS; i++) {
putchar(‘\n’);
for (j = 0; j < COLUMNS; j++)
printf (“%4hd”, mat2[i][j]);
}
return 0;
}

PROGRAM 10.13: matrix_transpose.c


Arrays 323

Transposed matrix:
1 4 7
2 5 8
3 6 9

PROGRAM OUTPUT: matrix_transpose.c

10.15.2 matrix_add_subtract.c: Adding and Subtracting Two Matrices


Two matrices can be added or subtracted if both of them have the same number of rows and the same
number of columns. Program 10.14 adds or subtracts two matrices, mat1 and mat2, depending on the
option chosen. Note the conditional expression that uses two printf expressions as the operands of
the ternary operator, ?:. As in Program 10.8, these expressions are used only for their side effects.
/* matrix_add_subtract.c: Adds/subtracts two initialized matrices. */
#include <stdio.h>
#define COLUMNS 4
#define ROWS 3
int main(void)
{
short mat1[ROWS][COLUMNS] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
short mat2[ROWS][COLUMNS] = {{12, 11, 10, 9}, {8, 7, 6, 5}, {4, 3, 2, 1}};
short mat3[ROWS][COLUMNS];
short i, j, choice;
printf(“Enter 1 for addition or 2 for subtraction: “);
scanf(“%hd”, &choice);
for (i = 0; i < ROWS; i++)
for (j = 0; j < COLUMNS; j++)
switch (choice) {
case 1: mat3[i][j] = mat1[i][j] + mat2[i][j];
break;
case 2: mat3[i][j] = mat1[i][j] - mat2[i][j];
break;
default: printf(“Illegal choice\n”);
return 1;
}
(choice == 1) ? printf(“Matrix after addition:”)
: printf(“Matrix after subtraction:”);
for (i = 0; i < ROWS; i++) {
putchar(‘\n’);
for (j = 0; j < COLUMNS; j++)
printf (“%4hd”, mat3[i][j]);
}
return 0;
}
PROGRAM 10.14: matrix_add_subtract.c
324 Computer Fundamentals & C Programming

Enter 1 for addition or 2 for subtraction: 3


Illegal choice
Enter 1 for addition or 2 for subtraction: 1
Matrix after addition:
13 13 13 13
13 13 13 13
13 13 13 13
Enter 1 for addition or 2 for subtraction: 2
Matrix after subtraction:
-11 -9 -7 -5
-3 -1 1 3
5 7 9 11

PROGRAM OUTPUT: matrix_add_subtract.c

10.15.3 matrix_multiply.c: Multiplying Two Matrices


Matrix multiplication is not as straightforward as addition and subtraction. An m ¥ n matrix can
only be multiplied with an n ¥ p matrix, which means that the number of columns of the first matrix
must equal the number of rows of the second matrix. The resultant matrix has the dimensions m ¥ p.
Matrix multiplication is performed by using the dot-product technique. The dot-product of
two sets of numbers containing the same number of elements is the sum of the products of the
corresponding elements. For matrices, the dot-product applies to the row elements of the first matrix
and the column elements of the second matrix (Fig. 10.3).

1 2 6 5 4 12 9 6
3 4 3 2 1 30 23 16
5 6 48 37 26
Matrix 1 Matrix 2 Matrix 3

FIGURE 10.3 Multiplying Two Matrices

The data for the matrices are obtained from the output of Program 10.15. The first element of matrix
3 (12) is evaluated by computing the dot-product of the first row of matrix 1 and the first column
of matrix 2. For the second element (9), the dot-product is applied to the same row of matrix 1 and
the second column of matrix 2. Thus, the first row of matrix 3 is evaluated in the following manner:
1 ¥ 6 + 2 ¥ 3 = 12 (mat3[0][0])
1 ¥ 5 + 2 ¥ 2 = 9 (mat3[0][1])
1 ¥ 4 + 2 ¥ 1 = 6 (mat3[0][2])
The second and third rows of matrix 3 are computed in a similar manner. Program 10.15 performs
this dot-product evaluation where the matrix values are supplied by user input. The program also
performs a compatibility test of the two matrices.
Arrays 325

/* matrix_multiply.c: Multiplies two matrices. */


#include <stdio.h>
int main(void)
{
short mat1[10][10], mat2[10][10];
short mat3[10][10] = {{0}}; /* Initializes all elements to zero */
short rows1, cols1, rows2, cols2, rows3, cols3, i, j, k;
printf(“Enter number of rows and columns of matrix 1: “);
scanf(“%hd %hd”, &rows1, &cols1);
printf(“Enter number of rows and columns of matrix 2: “);
scanf(“%hd %hd”, &rows2, &cols2);
if (cols1 != rows2) {
printf(“Matrices not compatible; multiplication not possible\n”);
return 1;
}
printf(“Resultant matrix has the size %hd X %hd\n”,
rows3 = rows1, cols3 = cols2);
/* Assign and display values for matrix 1 */
printf(“\nMatrix 1: Key in %hd items, %hd items for each row:\n”,
rows1 * cols1, cols1);
for (i = 0; i < rows1; i++)
for (j = 0; j < cols1; j++)
scanf(“%hd”, &mat1[i][j]);
printf(“Entered values for matrix 1: “);
for (i = 0; i < rows1; i++) {
putchar(‘\n’);
for (j = 0; j < cols1; j++)
printf (“%4hd”, mat1[i][j]);
}
/* Assign and display values for matrix 2 */
printf(“\n\nMatrix 2: Key in %hd items, %hd items for each row:\n”,
rows2 * cols2, cols2);
for (i = 0; i < rows2; i++)
for (j = 0; j < cols2; j++)
scanf(“%hd”, &mat2[i][j]);
printf(“Entered values for matrix 2: “);
for (i = 0; i < rows2; i++) {
putchar(‘\n’);
for (j = 0; j < cols2; j++)
printf (“%4hd”, mat2[i][j]);
}
326 Computer Fundamentals & C Programming

/* Multiply the matrices and display values for matrix 3 */


for (i = 0; i < rows3; i++)
for (j = 0; j < cols3; j++)
for (k = 0; k < cols1; k++)
mat3[i][j] += mat1[i][k] * mat2[k][j];
printf(“\nComputed values for matrix 3: “);
for (i = 0; i < rows3; i++) {
putchar(‘\n’);
for (j = 0; j < cols3; j++)
printf (“%4hd”, mat3[i][j]);
}
return 0;
}

PROGRAM 10.15: matrix_multiply.c


Enter number of rows and columns of matrix 1: 3 2
Enter number of rows and columns of matrix 2: 2 3
Resultant matrix has the size 3 X 3
Matrix 1: Key in 6 items, 2 items for each row:
1 2 3 4 5 6
Entered values for matrix 1:
1 2
3 4
5 6
Matrix 2: Key in 6 items, 3 items for each row:
6 5 4 3 2 1
Entered values for matrix 2:
6 5 4
3 2 1
Computed values for matrix 3:
12 9 6
30 23 16
48 37 26

PROGRAM OUTPUT: matrix_multiply.c

10.16 VARIABLE LENGTH ARRAYS (C99)


Finally, let’s briefly discuss one of the most beneficial products to emerge from the C99 standard.
This is the variable length array (VLA) which can be declared after obtaining the size of the array at
runtime. In Program 10.16, the array arr is declared only after its size is obtained from a call to scanf.
The output confirms that size has been rightly set to n. Note that you are able to set the size of an
array at runtime without changing the program at all. The expression sizeof(arr) / sizeof(arr[0])
evaluates to the number of array elements (10.3).
Lesson 2: Strings and String Operations

13 Strings

Chapter 13, Sections 13.1-5, and 13.7-9


WHAT TO LEARN

Interpreting a string as an array and a sequence of double-quoted characters.


Handling a line of text as a string with fgets and fputs.
Reading and writing a string with sscanf and sprintf.
Manipulating strings using their pointers.
Rudimentary implementation of some string-handling functions of the standard library.
How to use the string- and character-oriented functions of the library.
Handling multiple strings using a two-dimensional array.
Sorting a line of characters and an array of strings.
How to conserve memory using an array of pointers to strings.
Tweaking the main function for invoking a program with arguments.

13.1 STRING BASICS


Programming isn’t only about crunching numbers. It also involves extensive string handling. Unlike
Java, C doesn’t support a primary data type for a string. The language approaches strings through
the “backdoor”—by tweaking the char data type. A string belongs to the category of derived data
types and is interpreted as an array of type char terminated by the NUL character. C offers no
keywords or operators for manipulating strings but its standard library supports a limited set of
string-handling functions.
Even though a string is a collection of characters, they are interpreted differently. A character
constant is a single-byte integer whose equivalent symbol is enclosed in single quotes. Thus, ‘A’
and its ASCII value, 65, can be used interchangeably in programs. But a string constant is a set of
one or more characters enclosed in double quotes. Thus, “A” signifies a string. ‘A’ uses one byte
but “A” needs two bytes which include the NUL character. Functions that operate on characters
don’t work with strings and vice versa.
Strings 413

The pointer introduces an additional dimension to a string. The string constant “Micromax”
represents a pointer to the address of its first character, i.e., 'M'. This string can thus be assigned to
a pointer variable of type char *. Since “Micromax” can also be interpreted as an array of characters,
the name of the array storing the string also represents the address of 'M'. A string can be manipulated
with both array and pointer notation even though they are not entirely equivalent.
Because strings don’t have fixed sizes, every string-handling function must know where a string
begins and where it ends. A function knows the beginning of a string from the pointer associated
with it. The end is signified by the NUL character. Functions of the standard library that
create or modify strings make sure that the NUL is always in place. However, when you create
a string-handling function, it’s your job to append the NUL at the end of the string. NUL has the
decimal value 0 and is represented by the escape sequence ‘\0’.
Note: Because a string must contain NUL, even the empty string “” takes up one byte. The length
of the string is zero though.

13.2 DECLARING AND INITIALIZING A STRING


A string can be declared either as an array of type char or as a pointer of type char *. The two
techniques of declaration are not fully equivalent. You must understand how they differ and when
to use one technique in preference to the other.
13.2.1 Using an Array to Declare a String
We have previously declared and initialized an array with a set of values enclosed in curly braces.
The same technique applies to strings as well. The following declaration (and definition) statements
also include initialization with a set of single-quoted characters:
char stg1[20] = {‘F’, ‘a’, ‘c’, ‘e’, ‘b’, ‘o’, ‘o’, ‘k’, ‘\0’};
char stg2[ ] = {‘T’, ‘w’, ‘i’, ‘t’, ‘t’, ‘e’, ‘r’, ‘\0’};
The last element in each case is NUL and has to be explicitly inserted when you initialize a string
like this. The first declaration wastes space because the string uses only nine elements. But the
compiler automatically sets the size of stg2 by counting eight items in the list.
Keying in single-quoted characters for initialization is a tedious job, so C also offers a convenient
alternative. You can use a double-quoted string to assign an array at the time of declaration:
char stg3[ ] = “Whatsapp”; Array has 9 elements and not 8
The NUL is automatically inserted when an array is assigned in this way. So, even though
sizeof stg1 evaluates to 20 (the declared size), sizeof stg2 and sizeof stg3 evaluate to 8 and 9,
respectively.
13.2.2 When an Array is Declared But Not Initialized
It is possible to declare an array—meant for holding a string—without initializing it. In that case,
declare the array in the same way you declare a variable:
char stg4[30]; Also defines stg4
414 Computer Fundamentals & C Programming

Once an array has been declared in this manner, you can’t assign values to it by using either of the
methods shown previously. This means that the following assignments are invalid:
stg4 = {‘X’, ‘o’, ‘l’, ‘o’, ‘\0’}; Wrong!
stg4 = “Xolo”; Wrong!
You must now assign the elements individually, but don’t forget to include the NUL:
stg4[0] = ‘X’; stg4[1] = ‘o’; stg4[2] = ‘l’; stg4[3] = ‘o’; stg4[4] = ‘\0’;

Most of the space allocated to stg4 are not assigned specific values. But make no mistake: this is not
a partially initialized array because values were not assigned at the time of declaration.
Of the four arrays considered, it’s only for stg3 that NUL was automatically inserted by the compiler.
We need to know why the compiler would have done the same for stg1 even if it was initialized
without including NUL.

13.2.3 Using a Pointer to Declare a String


C supports another way of creating a string. Declare a pointer to char and then assign a double-
quoted string to the pointer variable:
char *p; p currently pointing “nowhere”
p = “Redmi Note”; p contains address of ‘R’
This string comprises multiple words which scanf reads with two %s specifiers, but printf
needs a single %s to print. Like with a regular variable, you can combine declaration and initialization
in one statement:
char *p = “Redmi Note”; NUL automatically appended
The previous declarations that used an array allocated memory only for the array. But the preceding
one creates space in memory for two objects—for the pointer p and for the string constant,
“Redmi Note”. It then makes p point to the first character of the string (Figure 13.1).

R e d m i N o t e \0
2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029
Memory Addresses

2017 P char *p = "Redmi Note";

FIGURE 13.1 Memory Allocation For the Pointer and the String Pointed To
Unlike the array stg4, which signifies a constant pointer, p is a pointer constant. That doesn’t mean
p is a constant but that the object it points to is a constant. At any time, the pointer p can be unhooked
from the current string and made to point somewhere else, which could be another string or an array:
Strings 415

p = “Samsung Galaxy”; p now contains address of ‘S’


p = stg4; p points to the string “Xolo”
Note that arrays have fixed addresses which can’t be changed even though their contents can be.
For instance, you can reassign any element, say, stg4[3] to ‘C’. But you can’t change the value
stg4 evaluates to.

13.2.4 When an Array of Characters Is Not a String


An array of characters is not a string when it doesn’t include the ‘\0’ character as the terminator.
Usually, we don’t bother to include the NUL because the compiler does this job for us, but negligence
on our part can prevent the compiler from doing so. Consider the following declarations:
char stg5[5] = {‘f’, ‘l’, ‘o’, ‘a’, ‘t’}; This isn’t a string ...
char stg6[30] = {‘f’, ‘l’, ‘o’, ‘a’, ‘t’}; ... but this one is.
In this scenario, stg5 has no space left to store the NUL character. When printf encounters this
string, it continues printing beyond the ‘t’ until it encounters a NUL somewhere on the way.
This means looking at a region of memory not reserved for it. You may see extra characters printed
at the end of the word float or the program could even crash.
The NUL character has not been explicitly inserted in stg6, so one would be tempted to conclude
that stg6 is not a string. However, stg6 is a partially initialized array, which by definition, has the
uninitialized elements set to NUL. Hence, stg6 is a string.

Takeaway: The compiler automatically inserts NUL when (i) a double-quoted set of characters
is used as the value, or (ii) the array is partially initialized. When assigning an array, there must
be room for the NUL to be included.

13.3 intro2strings.c: DECLARING AND INITIALIZING STRINGS


Program 13.1 demonstrates the techniques of assigning string values to array and pointer variables.
It also shows how sizeof computes the size of an array containing a string. Finally, the program
runs scanf in a loop to repeatedly assign an array. One of the arrays is not a string, and the output
shows the consequences of not using the NUL character when it should have been used.
The program declares and initializes four arrays in four different ways. It also assigns one of the
arrays to a pointer. Three of the arrays (stg1, stg2 and stg4) are valid strings because they are
terminated—explicitly or implicitly—by NUL. However, stg3 is not a valid string. Observe that
sizeof doesn’t return the string length but the number of bytes used by the array in memory.
Printing of stg3 has created problems on this machine. printf failed to stop after printing char, but
intruded into the territory used by stg2 to print double as well. You may not get the same output,
but the output would still be unpredictable because there’s no guarantee that printf will find NUL
at the end of the character sequence.
Finally, scanf runs in a loop to read multiple words and assign each word in turn to stg1. To read
a line as a single string, scanf needs to use the scan set (9.11.1), but the fgets function (13.4.3) also
does the job. We’ll use both techniques in this chapter.
416 Computer Fundamentals & C Programming

/* intro2strings.c: Declares and initializes strings.


Shows consequence of not inserting NUL in stg3. */
#include <stdio.h>
int main(void)
{
char stg1[30] = {‘f’, ‘l’, ‘o’, ‘a’, ‘t’};
char stg2[ ] = {‘d’, ‘o’, ‘u’, ‘b’, ‘l’, ‘e’, ‘\0’};
char stg3[4] = {‘c’, ‘h’, ‘a’, ‘r’};
char stg4[ ] = “long double”;
char *p = stg4;

printf(“Size of stg1 = %d “, sizeof stg1);


printf(“Size of stg2 = %d “, sizeof stg2);
printf(“Size of stg3 = %d “, sizeof stg3);
printf(“Size of stg4 = %d\n”, sizeof stg4);

printf(“%s\n”, stg1); printf(“%s\n”, stg2);


printf(“%s\n”, stg3); printf(“%s\n”, stg4);
printf(“%s\n\n”, p);

printf(“Enter a string ([Ctrl-d] to terminate): “);


while (scanf(“%s”, stg1) == 1)
printf(“Each word: %s\n”, stg1);

return 0;
}

PROGRAM 13.1: intro2strings.c


Size of stg1 = 30 Size of stg2 = 7 Size of stg3 = 4 Size of stg4 = 12
float
double
chardouble printf accesses space of previous array
long double
long double Dereferenced value of pointer p
Enter a string ([Ctrl-d] to terminate): Lenovo K6 Power
Each word: Lenovo
Each word: K6
Each word: Power
[Ctrl-d]

PROGRAM OUTPUT: intro2strings.c

Note: It’s only when accessing a string that both printf and scanf use the same identifier name
(the name of the array). scanf doesn’t use the & prefix for this purpose.
Strings 417

13.4 HANDLING LINES AS STRINGS


We often deal with text files which are organized as a group of lines containing printable characters.
Each line is separated from its adjacent one by the appropriate newline character specific to the
operating system (CR-LF for MSDOS, LF for UNIX). We routinely sort lines, display those that
contain a specific string and also modify them. C supports the following I/O functions that read
and write lines:
gets/puts (Discussed here but unsafe to use)
fgets/fputs
These functions use a buffer (an array) as an argument to store a line of data. The two sets differ
in handling of NUL and the newline character. We’ll first take up the gets/puts duo, understand
the danger involved in using gets and then discuss the safer functions, fgets and fputs. We’ll also
understand how the fgets/sscanf combination is often a better alternative to scanf for formatting
input data.

13.4.1 Using gets/puts: The Unsafe Way


Prototype: char *gets(char *s);
Header file: stdio.h
Return value: (i) s on success, (ii) NULL on error or when EOF occurs before any characters
are read.
Prototype: int puts(const char *s);
Header file: stdio.h
Return value: A non-negative number on success or EOF on error.
Both functions use a single argument, a pointer to char. For gets, this must be an implicit pointer
to a buffer represented by an array of type char. puts simply writes the contents of the buffer to
the terminal:
char stg[80]; Can hold up to 80 characters including NUL
gets(stg); Reads a line from standard input into stg
puts(stg); Writes contents of stg to standard output
gets converts the trailing newline to NUL, while puts adds it back before writing to the terminal.
stg must be large enough to hold an entire line of input even though gets doesn’t check this size.
Thus, if the line is longer than the size of stg, gets will simply overflow this buffer.
Note: The infamous Morris worm, which paralyzed (possibly) one-tenth of the Internet in 1988,
originated from an overflow situation caused by gets in a self-replicating and self-propagating
program.

13.4.2 gets_puts.c: A Program Using gets and puts


Program 13.2 changes the case of text from lower to upper in a line of text read by gets. puts
writes the modified line to the standard output. A subsequent program will use the toupper library
418 Computer Fundamentals & C Programming

/* gets_puts.c: Gets a line from standard input, changes lowercase


to uppercase before writing line to standard output. */
#include <stdio.h>
#define SIZE 80

int main(void)
{
char line[SIZE], diff = ‘a’ - ‘A’;
int i;

printf(“Enter a line of text: \n”);


gets(line); /* Replaces ‘\n’ with ‘\0’ */

for (i = 0; line[i] != ‘\0’; i++)


if (line[i] >= ‘a’ && line[i] <= ‘z’)
line[i] -= diff;

puts(line); /* Replaces ‘\0’ with ‘\n’ */

return 0;
}

PROGRAM 13.2: gets_puts.c


the gets function is dangerous and should not be used.
THE GETS FUNCTION IS DANGEROUS AND SHOULD NOT BE USED.

PROGRAM OUTPUT: gets_puts.c

function for doing the same job. However, the message put out here must be taken seriously: use
fgets instead of gets. After this program, we won’t use gets in this book.

Caution: Never use the gets function! If the data can’t fit in the buffer, gets will write past its end
and change other parts of the program. The fgets function eliminates this possibility because it
takes the size of the buffer as a separate argument. C11 has removed gets from its specification.

13.4.3 Using fgets/fputs: The Safe Way


Prototype: char *fgets(char *s, int size, FILE *stream);
Header file: stdio.h
Return value: (i) s on success, (ii) NULL on error or when EOF occurs before any characters
are read.
Prototype: int fputs(const char *s, FILE *stream);
Header file: stdio.h
Return value: A non-negative number on success, EOF on error.
Strings 419

Unlike gets and puts, fgets and fputs work with any file type, including disk files. The file type is
specified as the last argument for both functions. For I/O operations with the terminal, this argument
is stdin and stdout for fgets and fputs, respectively. fgets overcomes the buffer overflow problem
by using the size of the buffer as the second argument:
char stg[80];
fgets(stg, 80, stdin); Reads up to 79 characters into stg
fputs(stg, stdout);
fgets reads a maximum of size - 1 characters from the standard input stream to the buffer s.
Unlike gets, fgets reads the newline into the buffer and adds ‘\0’ after the last character.
fputs writes the contents of s to the standard output stream, but without the trailing ‘\0’.

13.4.4 fgets_fputs.c: A Program Using fgets and fputs


Program 13.3 modifies and enhances the previous program to reverse the case of characters using
three library functions that need the file ctype.h to be included. The islower function returns true
if its argument is a lowercase letter. The toupper and tolower functions convert the case of a letter.
They either return the converted character or the existing character if conversion is not possible.
This program prepares us for using character-oriented functions for string manipulation tasks.
These functions are listed and discussed in Section 13.10.

/* fgets_fputs.c: Gets a line of input from standard input and reverses


the case before writing line to standard output. */
#include <stdio.h>
#include <ctype.h> /* For islower, tolower and toupper */
#define SIZE 80

int main(void)
{
char line1[SIZE], line2[SIZE];
short i;
printf(“Enter a line of text: “);
fgets(line1, SIZE, stdin); /* Doesn’t remove ‘\n’ */
for (i = 0; line1[i] != ‘\0’; i++)
if (islower(line1[i]))
line2[i] = toupper(line1[i]);
else
line2[i] = tolower(line1[i]);

line2[i] = ‘\0’; /* ‘\0’ was not copied */


fputs(line2, stdout); /* Doesn’t add ‘\n’ */
return 0;
}

PROGRAM 13.3: fgets_fputs.c


420 Computer Fundamentals & C Programming

Enter a line of text: fgets and sscanf form a useful combination.


FGETS AND SSCANF FORM A USEFUL COMBINATION.

Enter a line of text: FPUTS AND SPRINTF ARE OFTEN USED TOGETHER.
fputs and sprintf are often used together.

PROGRAM OUTPUT: fgets_fputs.c

13.5 THE sscanf AND sprintf FUNCTIONS


Sometimes, you would like to save keyboard input in a string before using a scanf-type function
to read from the string. Because the string itself doesn’t change, you can use multiple formatting
techniques on this data. scanf can’t do this job because it changes the buffer in every invocation,
but the sscanf function meets our requirement.
In the same vein, you may like to use printf-like functionality to format a line of data and save
it in a string. This gives you the chance to modify the string before or after printing. The sprintf
function does whatever printf does except that it saves the formatted output in a string. Once that
is done, you can use fputs to print the string.

13.5.1 sscanf: Formatted Input from a String


Consider a situation where you have the option of inputting a date in multiple formats (say,
dd-mm-yy, month_name-dd-yy or dd-month_name-yy). You can’t catch them all by repeatedly
invoking scanf with different format specifiers because each invocation will fetch data from the
buffer and overwrite the previous data. You need to use sscanf which has the following prototype:
int sscanf(char *str, cstring, &var1, &var2, ...);

Unlike scanf which reads standard input, sscanf reads the string str and uses the information
provided in cstring (the control string) to format the data before saving them in variables.
The function is often used in combination with fgets. To validate a date, read the input into
a buffer with fgets first:
fgets(line, SIZE, stdin);

You can now use sscanf to match the input saved in line with the form dd-mm-yyyy:
sscanf(line, “%2d-%2d-%4d”, &day, &month, &year);

If the match doesn’t succeed, you can call sscanf multiple times, with a different format specifier
in each invocation. The following code snippet accepts 12-05-16, May-12-16 and 12-May-16 as valid
date formats:
if (sscanf(line, “%2hd-%2hd-%2hd”, &day, &month, &year) == 3)
date_ok = 1;
else if (sscanf(line, “%3s-%hd-%hd”, month_arr, &day, &year) == 3)
date_ok = 1;
Strings 421

else if (sscanf(line, “%hd-%3s-%hd”, &day, month_arr, &year) == 3)


date_ok = 1;
else
date_ok = 0;
You couldn’t have done this by repeatedly calling scanf because each call would change the input
buffer. Here, line remains unchanged throughout the checking process. After discussing sprintf,
we’ll use both sscanf and sprintf in a program.
Tip: When a data item can occur in multiple formats, use fgets to read the input into a buffer.
You can then use sscanf multiple times and with different format specifiers to match the data in
the buffer.

13.5.2 sprintf: Formatted Output to a String


The dates that were matched with sscanf in the previous section can now be saved (along with
additional information) in a string. This is possible with the sprintf function which uses an
additional argument to specify the string:
int sprintf(char *str, cstring, var1, var2, ...);

sprintf uses the format specifiers in cstring to format the data represented by the variables. It then
writes the formatted data to the string str instead of the display. Depending on whether you want
the date 19/05/53 to be represented as 19-05-1953 or May 19, 1953, you can populate the array,
line, in one of these two ways:
sprintf(line, “dd-mm-yyyy format: %02d-%02d-%04d\n”, day, month, year);
sprintf(line, “month dd, yyyy format: %s %02d, %04d\n”, month_arr, day, year);
sprintf appends NUL to line. You can save this line in a file using fputs but you can also print
it using the same function:
fputs(line, stdout);

sprintf also returns the number of characters written to the string (including ‘\0’). You can use
this return value to validate the length of the string. This feature is utilized in the next program.

13.5.3 validate_pan.c: Using sscanf and sprintf to Validate Data


Program 13.4 validates a code (PAN, the Permanent Account Number) keyed in by the user.
The 10-character PAN code has the form XXXXX9999X, where X represents a letter. The program
runs in a loop which is exited only after the user has keyed in a valid PAN. This program totally
avoids the use of the scanf/printf heavyweights by dividing the work among four functions.
sscanf uses three scan sets to specify the size and type of the three components of the code. The side
effect of sprintf writes the validated input to a string while its return value is used to check whether
the code comprises 10 characters. The entire work is divided among two sets of functions. The
logic is handled by sscanf and sprintf, and I/O operations are carried out using fgets and fputs.
422 Computer Fundamentals & C Programming

/* validate_pan.c: Accepts and validates the user’s Permanent Account Number.


PAN has the form XXXXX9999X (10 characters). */
#include <stdio.h>
#include <string.h>
#define SIZE 30

int main(void)
{
char line1[SIZE], line2[SIZE];
char stg1[6], stg2[5], stg3[2];
short pan_length;

while (fputs(“Enter your PAN identifier: “, stdout)) {


fgets(line1, SIZE, stdin);
if (sscanf(line1, “%5[a-zA-Z]%4[0-9]%[a-zA-Z]”, stg1, stg2, stg3) != 3)
fputs(“Invalid PAN\n”, stdout);
else {
pan_length = sprintf(line2, “%s%s%s”, stg1, stg2, stg3);
if (pan_length != 10)
fputs(“PAN must be 10 characters.\n”, stdout);
else
break;
}
}
fputs(line2, stdout);

return 0;
}

PROGRAM 13.4: validate_pan.c


Enter your PAN identifier: ADOYTT4598C
Invalid PAN
Enter your PAN identifier: AXEGB1097EE
PAN must be 10 characters.
Enter your PAN identifier: AXEGB1097E
AXEGB1097E Valid entry

PROGRAM OUTPUT: validate_pan.c

13.6 USING POINTERS FOR STRING MANIPULATION


String manipulation is an important programming activity, so we must fully understand the
mechanism of navigating a string and accessing each character. Because a string can be treated as an
array and a pointer, the previous discussions (12.8.2) related to the use of pointer and array notation
in pointer arithmetic also apply here. Consider the following strings defined in two different ways:
char stg[ ] = “Nyasaland”;
char *p = “Malawi”;
Strings 423

Because stg is a constant, we can use limited pointer arithmetic (like stg + 1) to access and update
each element, but we cannot use stg++ and stg--. The pointer p, being a variable, knows no such
restrictions. In addition to using expressions like p + 1, we can also use p++ and p-- to change the
current pointer position in the string.
There is one more difference between stg and p. While you can change the contents of stg
(“Nyasaland”), you can’t change the dereferenced value of p (“Malawi”) because p is a pointer
constant. p points to a region of memory that is marked read-only. However, if p is made to point to
stg, then you can change any character in stg using p. Table 13.1 compares the validity of operations
made on the two representations of a string.
TABLE 13.1 Permissible Operations on Pointer that Represents a String
char stg[ ] = “Facebook”; char *p = “Twitter”;
Statement/Expression OK/Wrong Statement/Expression OK/Wrong
stg = “Skype”; Wrong p = “Skype”; OK
stg + 1 OK p + 1 OK
stg++; Wrong p++; OK
stg[0] = ‘C’; OK p[0] = ‘C’; Wrong
stg = p; Wrong p = stg; OK

13.7 COMMON STRING-HANDLING PROGRAMS


We’ll now discuss three programs often encountered by beginning C programmers. Except for the
use of strlen, none of the programs uses a function of the standard library for handling strings or
characters. We’ll later reinvent the wheel by developing our own code for some library functions
before we eventually examine the important string-handling functions of the standard library.
A word about scanf and fgets could be helpful before we proceed. Most programs in this
chapter handle a string input from the keyboard. In these programs, we have used either fgets
or scanf (with the [^\n] scan set) to read a line of input and store it as a NUL-terminated string.
Because fgets also saves the ‘\n’, it may sometimes be necessary to overwrite it with NUL. However,
in all cases, scanf with the scan set works without problems.
13.7.1 string_palindrome.c: Program to Check a Palindrome
Program 13.5 checks a string for a palindrome, which is a string of characters that read the same
whether read forward or backward. The string to be checked is saved by scanf in the array, stg.
The variable j represents the index of the last non-NUL character in stg. The while loop begins
the equality check from the two ends of the string and completely scans the string. Ideally, the scan
could have stopped midway and all elements would still have been compared.

/* string_palindrome.c: Ascertains whether a string is a palindrome */


#include<stdio.h>
#include <string.h>
424 Computer Fundamentals & C Programming

int main(void)
{
short i = 0, j;
char stg[80];

printf(“Enter a string: “);


scanf(“%[^\n]”, stg); /* Only ‘\0’ at end of stg, not ‘\n’ */
j = strlen(stg) - 1; /* strlen is a library function */

while (j) {
if (stg[i++] != stg[j--]) {
printf(“%s is not a palindrome\n”, stg);
return 1;
}
}
printf(“%s is a palindrome\n”, stg);

return 0;
}

PROGRAM 13.5: string_palindrome.c


Enter a string: madam
madam is a palindrome
Enter a string: gentleman
gentleman is not a palindrome

PROGRAM OUTPUT: string_palindrome.c

13.7.2 count_words.c: Counting Number of Words in a String


Program 13.6 counts the number of words in a string, where a word is defined as a group of characters
not containing whitespace (i.e., without spaces, tabs or newlines). The clever use of the variable
flag ensures that multiple contiguous whitespace characters are treated as a single delimiter.

/* count_words.c: Counts number of words separated by any number


of spaces, tabs and newlines. */
#include <stdio.h>
#define SIZE 80

int main(void)
{
char stg[SIZE];
short i = 0, flag = 1, count = 0;

fputs(“Enter a string: “, stdout);


fgets(stg, SIZE, stdin);
Strings 425

while (stg[i] != ‘\0’) {


if (stg[i] == ‘ ‘ || stg[i] == ‘\t’ || stg[i] == ‘\n’)
flag = 1;
else if (flag == 1) { /* If this character is not whitespace ... */
flag = 0; /* ... increment word count when ... */
count++; /* ... beginning of word detected. */
}
i++; /* Take up next character in string */
}
printf(“Number of words = %hd\n”, count);

return 0;
}

PROGRAM 13.6: count_words.c


Enter a string: chad mauritania niger upper_volta mali
Number of words = 5

PROGRAM OUTPUT: count_words.c

13.7.3 sort_characters.c: Sorting Characters in a String


The selection sort algorithm was developed in Section 10.8 for ordering a set of integers. Program 13.7
applies the same algorithm on a set of one-byte characters (which are actually integers). The program
output in both invocations includes all alphabetic letters preceded by spaces which have a lower
ASCII value than the letters. To understand the working of this program, it could be helpful to
recall the central features of the sorting algorithm.
Selection sort is based on scanning an array of n elements in multiple passes. After completion of
each pass, the start element has the lowest number among the elements encountered in that pass.
The start point progressively moves inward until n - 1 passes have been made. In this program, the
outer for loop progressively changes the start point, while the inner loop compares the elements
to set the start element to the lowest value encountered in that pass.

/* sort_characters.c: Sorts characters of a string using selection sort */


#include <stdio.h>
#include <string.h>

int main(void)
{
short i, j, length;
char stg[100], tmp;

printf(“Enter string: “);


scanf(“%[^\n]”, stg);
length = strlen(stg);
426 Computer Fundamentals & C Programming

for (i = 0; i < length - 1; i++) { /* Selection sort logic ... */


for (j = i + 1; j < length; j++) /* ... also used in Program 10.7*/
if (stg[i] > stg[j]) {
tmp = stg[i];
stg[i] = stg[j];
stg[j] = tmp;
}
}
printf(“%s\n”, stg);

return 0;
}

PROGRAM 13.7: sort_characters.c


Enter string: the quick brown fox jumps over the lazy dog
abcdeeefghhijklmnoooopqrrsttuuvwxyz
Enter string: pack my box with five dozen liquor jugs
abcdeefghiiijklmnooopqrstuuvwxyz

PROGRAM OUTPUT: sort_characters.c

13.8 DEVELOPING STRING-HANDLING FUNCTIONS


C handles strings using a small set of functions supported by the standard library. In the following
sections, we’ll develop five string-handling functions from scratch. We’ll integrate four of them
into a main program from where they will be invoked. None of these functions specifies the size
of the string as an additional argument simply because each function checks for NUL. We have
also protected the source string from being modified by using the const qualifier.

13.8.1 my_strlen: Function to Evaluate Length of a String


In two previous programs, we used strlen, the function supported by the standard library for
evaluating the length of a string (without NUL). The following function definition of my_strlen
implements the functionality of strlen. my_strlen returns an integer of type short, displays array
notation in the parameter list but uses pointer notation in the body without causing conflict.
short my_strlen(const char arr[ ])
{
short count = 0;
while (*(arr + count) != ‘\0’)
count++;
return count;
}
Strings 427

13.8.2 reverse_string: Function to Reverse a String


This function, which doesn’t have a peer in the standard library, reverses a string. After reversal,
the ‘\0’ is appended to the string. The function returns a pointer to the destination string, but it
is also available in the first parameter. You can thus invoke the function either directly:
reverse string(s2, s1);
printf(“Reversed string is %s\n”, s2);
or by using it as an expression:
printf(“Reversed string is %s\n”, reverse_string(s2, s1);

Most string-handling functions of the standard library that use two strings as arguments are designed
to return a value in these two ways.
char *reverse_string(char *destination, const char *source)
{
short i = 0, j;
j = my_strlen(source) - 1;

while (j >= 0)
destination[i++] = source[j--];
destination[i] = ‘\0’;
return destination;
}

13.8.3 my_strcpy: Function to Copy a String


The my_strcpy function, which copies a string, emulates the strcpy function of the standard library.
This function also uses two arguments, and like reverse_string, returns the destination string in
two ways.
char *my_strcpy(char *destination, const char *source)
{
short i = 0, j;
j = my_strlen(source) - 1;
while (j-- >= 0) {
destination[i] = source[i];
i++;
}
destination[i] = ‘\0’;
return destination;
}

13.8.4 my_strcat: Function to Concatenate Two Strings


The standard library supports the strcat function for concatenating two strings, i.e. appending
one string to another. The one that we have developed here (my_strcat) does the same work.
428 Computer Fundamentals & C Programming

Because destination initially contains ‘\0’ at the end, source is appended at that point by overwriting
the NUL. A separate NUL is later added at the end of the concatenated string.

char *my_strcat(char *destination, const char *source)


{
short i = 0, j;
j = my_strlen(destination);
for (i = 0; source[i] != ‘\0’; i++)
destination[j++] = source[i];
destination[j] = ‘\0’;
return destination;
}

13.8.5 string_manipulation.c: Invoking All Four Functions from main


Now that we have defined the four functions, we need to declare them in a central program
(Program 13.8). The function definitions just discussed must be appended to the main code segment
to form a complete standalone program. The program takes two strings as user input before invoking
the four functions. Note that each call of fgets must be followed by an assignment operation that
replaces ‘\n’ with NUL.

/* string_manipulation.c: Develops four string-handling functions


from scratch. “my_” functions also available in the standard library. */
#include <stdio.h>
short my_strlen(const char arr[]);
char *reverse_string(char *destination, const char *source);
char *my_strcpy(char *destination, const char *source);
char *my_strcat(char *destination, const char *source);

int main(void)
{
char stg1[80], stg2[80], stg3[80], stg4[80];
printf(“Enter main string: “);
fgets(stg1, 80, stdin);
stg1[my_strlen(stg1) - 1] = ‘\0’; /* Replaces ‘\n’ with ‘\0’ */
printf(“Length of string = %hd\n”, my_strlen(stg1));
reverse_string(stg2, stg1);
printf(“Reversed string: %s\n”, stg2);
printf(“Copied string: %s\n”, my_strcpy(stg3, stg1));
printf(“Enter second string: “);
fgets(stg4, 80, stdin);
stg4[my_strlen(stg4) - 1] = ‘\0’; /* Replaces ‘\n’ with ‘\0’ */
Strings 429

my_strcat(stg4, stg1);
printf(“Concatenated string: %s\n”, stg4);

return 0;
}
... Function definitions to be appended here ...

PROGRAM 13.8: string_manipulation.c


Enter main string: Botswana
Length of string = 8
Reversed string: anawstoB
Copied string: Botswana
Enter second string: Bechuanaland/
Concatenated string: Bechuanaland/Botswana

PROGRAM OUTPUT: string_manipulation.c

13.8.6 substr.c: Program Using a Function to Extract a Substring


Unlike most languages, C doesn’t have a function to return a substring from a larger string. The
substr function developed in Program 13.9 uses three arguments: the main string, index and length
of the substring to be extracted. For valid inputs, the function returns a pointer to the substring which
is declared as a static array inside the function. For invalid index or length, substr returns NULL.

/* substr.c: Uses a function to extract a substring from a string.


Index and length of substring specified as arguments. */
#include <stdio.h>
#include <string.h>

char *substr(char *s, short start, short length);

int main(void)
{
char *months = “JanFebMarAprMayJunJulAugSepOctNovDec”;
char *p;
short index, len;

fputs(“Enter index and length of substring: “, stdout);


scanf(“%hd%hd”, &index, &len);
if ((p = substr(months, index, len)) != NULL)
printf(“Substring is %s\n”, p);
else
fputs(“Illegal arguments\n”, stdout);

return 0;
}
430 Computer Fundamentals & C Programming

char *substr(char *s, short start, short length)


{
static char stg[20];
short i, j = 0;
if (start + length > strlen(s)) /* If substring goes past array end */
return NULL;
else {
for (i = start; i < start + length; i++)
stg[j++] = s[i];
stg[j] = ‘\0’;
return stg;
}
}

PROGRAM 13.9: substr.c


Enter index and length of substring: 0 6
Substring is JanFeb
Enter index and length of substring: 33 3
Substring is Dec
Enter index and length of substring: 33 4
Illegal arguments

PROGRAM OUTPUT: substr.c

13.9 STANDARD STRING-HANDLING FUNCTIONS


Unlike C++ and Java, which have strong string-handling capabilities, C supports a limited number
of string-handling functions. An enlarged list of functions supported by the standard library is
available in Appendix A. Table 13.2 lists a subset of these functions which are discussed in the
following sections. They are presented here with their prototypes and code snippets that highlight
their usage. All of them need the #include <string.h> directive.
13.9.1 The strlen Function
Prototype: size_t strlen(const char *s);
printf(“Enter string: “);
scanf(“%[^\n]”, stg1); If input is Burundi ...
printf(“Length: %hd\n”, strlen(stg1)); ... printf displays 7
The strlen function returns the length of the string without the NUL character. The value returned
is of type size_t, which is an unsigned type guaranteed to return at least 16 bits.
13.9.2 The strcpy Function
Prototype: char *strcpy(char *dest, const char *src);
strcpy(stg2, stg1); Copies stg1 to stg2
printf(“Copied string: %s\n”, stg2); Also prints Burundi
Strings 431

TABLE 13.2 String-Handling (string.h) Functions


Function Signi cance
size_t strlen(const char *s) Returns length of s.
char *strcpy(char *dest, const char *src) Copies src to dest, returns pointer to dest.
char *strcat(char *dest, const char *src) Appends src to dest, returns pointer to dest.
int strcmp(const char *s1, const char *s2) Compares s1 to s2, returns -ve, 0 or +ve integer
depending on whether s1 <, ==, or > than s2.
char *strchr(const char *s, int c) Searches c in s, returns pointer to first occurrence
of c in s or NULL if c not found in s.
char *strrchr(const char *s, int c) As above, but returns pointer to last occurrence
of c in s or NULL if c not found in s.
char *strstr(const char *s1, const char *s2) Searches s2 in s1, returns pointer to s2 or NULL
if s2 not found in s1.
C doesn’t allow a string src to be copied to dest using the assignment dest = src because that would
mean having the same pointer value in both variables. A string is copied with the strcpy function.
All characters of src, including the ‘\0’, are copied to dest, and strcpy returns a pointer to dest.
Note that the destination string is always the first argument for functions that use two strings.

13.9.3 The strcat Function


Prototype: char *strcat(char *dest, const char *src);
printf(“Enter second string: “); Assuming stg1 is Burundi ...
scanf(“%[^\n]”, stg3); ... if user enters Congo ...
strcat(stg3, stg1)); ... stg3 is CongoBurundi
printf(“Concatenated string: %s\n”, stg3); ... after concatenation
You can’t use the + operator to concatenate two strings. The expression dest + src is invalid because
that implies adding two pointers, which is not permitted in C. Strings are concatenated with the
strcat function. The function overwrites the NUL at the end of dest with the string src and then
writes a separate NUL at the end of the concatenated string. strcat returns a pointer to dest.

13.9.4 The strcmp Function


Prototype: int strcmp(const char *s1, const char *s2);
if (strcmp(s1, s2) == 0)
printf(“The two strings are equal\n”);
else if (strcmp(s1, s2) > 0)
printf(“s1 is greater than s2\n”);
else
printf(“s1 is less than s2\n”);
432 Computer Fundamentals & C Programming

It’s not possible to use the comparison operators, ==, > and <, to compare two strings because strings
evaluate to pointers and two pointers can only be compared when they refer to the same array.
The strcmp function compares strings by comparing their individual characters and returns
three values:
0 if both strings are identical.
a positive integer if the first unmatched character of s1 is greater than its counterpart in s2.
a negative integer if the first unmatched character of s1 is less than its counterpart in s2.
For determining the value of a comparison, the ASCII code list is used. Thus, Angola is less than
angola because the ASCII value of ‘A’ is 65 and that for ‘a’ is 97. Similarly, Barbados is greater
than Bahamas because ‘r’ in Barbados (the first unmatched character) is greater than ‘h’ in Bahamas.
This function is extensively used for sort operations on strings.

13.9.5 The strchr and strrchr Functions


Prototype: char *strchr(const char *s, int c);
char stg[ ] = “Lesotho/Basutoland”; char *p;
if ((p = strchr(stg, c)) != NULL) { If c is ‘B’ ....
printf(“%c found at position %d\n”, c, p - stg); ... p - stg is 8 ...
printf(“Remaining string is %s\n”, p); ... p now points
} ... to Basutoland

The strchr function determines whether a character c can be found in a string s. The function
returns a pointer to the first matched character, or NULL if the character is not found. The index of
the found character can be easily computed by subtracting the pointer s from the returned pointer
value. In the preceding example, if c is ‘B’, its index is p - stg, i.e. 8 (first index: 0).
The strrchr function is a variant that uses an identical prototype but returns the last occurrence of
the character. To determine whether a character occurs only once in a string, the returned values
of strchr and strrchr must be equal.

13.9.6 The strstr Function


Prototype: char *strstr(const char *haystack, const char *needle);
char *hstack = “JanFebMarAprMayJunJulAugSepOctNovDec”; char *p;
if ((p = strstr(hstack, ndl)) != NULL) {
printf(“%s found at position %d\n”, ndl, p - hstack);
printf(“%s is month number %d\n”, If ndl is May, the last ...
ndl, (p - hstack) / 3 + 1); ... expression evaluates to 5
}

While strchr searches for a character, the strstr function searches for a substring. The prototype
makes it clear that this function lets you search for needle in haystack. If the search is successful,
strstr returns a pointer to needle that exists in haystack, or NULL otherwise.
Reading: Structures in C
Reading Objective:

In this reading, you will learn about user-defined data types in C. You will also learn how to create your own
data and how to use them in programming.

The Need for Structures in C

Until now, we have seen a lot of built-in data types in C, such as int, float, and char, etc. These data types
can represent integers, real numbers, characters, and strings. We can also create arrays of these data types.

But now, let us consider a diverse group of data items that are somehow related to each other. For example,
let us take a student database, where each student has an associated ID, name, and CGPA. How would we
implement this database in C?

Based on what we have learned so far, we could create three different arrays as follows:

Now, for each student, we have stored their data in three separate arrays. If we have to add or delete a
student’s records, it has to be done separately in all three arrays. Moreover, it would require three
parameters to pass a student’s data into a function. Also, there is no real linkage between the ID and the
name of the student in our code.

Consider another example—say we want to represent a point in a three-dimensional space. This can be
done by specifying the x, y, and z coordinates. If we want to store a bunch of such points, we would have to
create three different arrays again:

Would not it be much better if we were able to represent a Point or a Student as one single entity, just as it
is in real life? Fortunately, C offers this functionality! We can design our own custom data types using the
struct keyword in C.

Structures in C

A structure is a data type that is designed by the user. It is a combination of primitive data types, such as
int, float, char, or bool.

We can define a structure as follows:


Note the semicolon at the end of structure definition! Not adding a semicolon would give you an error.
Structures are usually defined globally, that is, outside of int main()or any other function.

Let us now try to define the structures we discussed in the previous sections:

Now that we have defined the structure using the struct keyword, we can declare structure variables just
like we declared variables of primitive data types. The only difference is that a struct keyword is required
whenever declaring a variable, as follows:

Now, s1 is a Student variable and has associated attributes: ID, name, and CGPA. Similarly, p1 and p2 are
point variables.

Accessing Members of a Structure

We can access members of a struct using the dot operator. For example, it is possible to access the ID of
student s1 as s1.ID.

In general, to access any member, just call structure_variable.member_name

As another example, we can also directly transfer values into the structure by taking input. Consider the
following code snippet:
This takes the student’s ID, name, and CGPA as input and stores them in their respective attributes inside
the student structure.

Initializing a Structure Variable

We can specify all attributes of a structure during initialization itself as comma-separated entries inside
curly braces:

Operations on Structures

Not many operations can be performed on structures as a whole in C. The only possible operators that can
be used with structures are =, dot, ->, and &. Equals (‘=’) is the assignment operator. Assignment works just
as in the case of primitive data types.

For example, s1 = s2 will give all attributes of s1 the same value that they had in s2. Similarly, & retains its
usual significance with structures. However, dot (‘.’) and arrow (‘->’) are operators exclusively meant for
accessing members of a structure.

Note that we cannot use any other operations with structures. We cannot add two structures together, or
find their bitwise XOR, or check equality using ‘==’.

Question: If the ‘==’ operator is not allowed to be used with structures, how can we check if two
structures are equal?

Answer: Compare all attributes individually.


Note: The strcmp() function compares two-character arrays for equality. It returns 0 if both strings are
equal.

Extra Note: typedef

It seems unnecessarily lengthy, typing “struct” before every variable declaration, does not it? Fortunately,
C has a feature that can bypass this! The keyword typedef can create a simple synonym for the words struct
<struct_name>, as follows:

The typedef keyword here basically does this: whenever “stu” is encountered in the code, the compiler will
internally convert it to “struct Student.” This saves time for us as we have to type less code!

We can even combine typedef with the structure definition as follows:

Reading Summary:

In this reading, you have learned the following:

• The need for user-defined data types in C

• How to define structures and create structure variables (usage of the struct keyword)

• How to access members of a structure and other possible operations with structures

• How to use typedef to improve readability and reduce typing effort in coding
Practice Lab 1: Structures in C

Assignment Brief

Practice Lab 1_Question_1

Create a Point structure as taught in the lectures. Then, create two-point objects representing the points (3,
2) and (−1, −5). Print the x-coordinate and y-coordinate of the points to standard output.

Practice Lab 1_Question_2

Create a structure called Employee. It should contain the ID, name, and job of the employees. Read the ID,
first name, and job of three employees as input, and print the details of the employee whose ID is 2.

Testcase1
Input:

Enter the ID of employee: 1

Enter the name of employee: Aditya

Enter the job of employee: Intern

Enter the ID of employee: 3

Enter the name of employee: Rahul

Enter the job of employee: Manager

Enter the ID of employee: 2

Enter the name of employee: Tanveer

Enter the job of employee: Accountant

Output:

The details are:

ID = 2, Name = Tanveer, Job = Accountant

Practice Lab 1_Question_3

Write a program that takes two distances as input. The distances are in the form of X kilometers and Y
meters.

For example, 23 68 means 23 kilometers and 68 meters. Store the distances in a structure. After that, find
the difference between the two distances. Store this difference in another structure(1,000 meters = 1
kilometer). It is guaranteed that the second distance is greater than the first distance.

A structure definition has been provided, complete the missing parts of the code!
Testcase1 Testcase2
Input: Input:

Enter first distance = 5 40 Enter first distance = 1 500

Enter second distance = 10 60 Enter second distance = 2 0

Output: Output:

The difference is = 5 kilometers and 20 meters The difference is = 0 kilometers and 500 meters
Reading: More Structures
Reading Objective:

In this reading, you will learn about nested structures and how to create arrays of structures.

Nested Structures

A structure can even contain other structures as attributes. A structure can have another structure, and the
inner structure can contain more structures as its members, and so on. This can help in modeling more
complex data.

Example:

In this example, we created a struct Student that contains, as its members, two more structs, Address, and
name.

Question: How can we access the last name of a student in the above definition?

Answer:

So, the dot operator can be used multiple times to access inner struct data.

It is also possible to initialize nested structures, for example:


Array of Structures

It is possible to create arrays of structures, just as we could create arrays of int, char, bool, etc. The
declaration is pretty straightforward. For example:

This creates an array of Student, of size 500. Now, let us say we want to access the CGPA of the 26th student
in this array. We can do that using stu_records[25]. CGPA (remember that arrays are 0-indexed, so the 26th
student would be at location 25!).

Structures and Functions

The functionality of structures can be enhanced using functions. Structures can be passed as parameters to
functions (both pass-by-value and pass-by-reference are supported) and can be returned from functions
too. For example, we can create a function to add two complex numbers together or to print the real and
imaginary parts of a complex number.

Reading Summary:

In this reading, you have learned the following:

• Nested structures in C

• How to declare arrays of structures and access member variables while dealing with arrays of
structures

• How to pass structure variables as arguments to functions, and how to return structure variables
from functions
Practice Lab 2: More Structures

Assignment Brief

PracticeLab2_Question1

Write a program that takes N points in the x-y coordinate plane as input, and then print the point with the
maximum distance from the origin. Try to use structures in your program!

Hint: The distance of a point from the origin is equal to sqrt{x^2 + y^2}.

The input should be taken in the following format—the first line will contain only a single integer N as
input. After that, the next N lines will contain two real values each, first x coordinate then y coordinate.

Testcase1
Input:

45

1.5-1.5

05

Output:

Maximum distance = 6.403124

PracticeLab2_Question2

The definition of the Complex structure is given in the file Question2.c, which is used to represent complex
numbers in C. Also, the function signature of the addition function is provided, to add two complex
numbers.

1. Complete the addition function, which should return the sum of the two complex numbers.

2. Create two new functions, subtract and multiply, which return the difference and product of the
two complex numbers respectively.

Note: You should not make any changes inside int main().

Hint: Given two complex numbers (a + ib) and (c + id), their sum is given by (a + c) + i(b + d), their
difference is given by (a − c) + i(b − d), and their product is given by (ac − bd) + i(ad + bc).

For example: If the complex numbers are (3 + 4i) and (7 + 12i), their sum is (10 + 16i), their difference is (−4
−8i), their product is (−27 + 64i).

PracticeLab2_Question3

Write a function that takes two dates as parameters, compares them, and returns which date is older.
1. You should make a structure called Date, and store both the dates in the structure.

2. You should make a function that takes two Dates as input and returns the larger Date.

3. Now, write a program so that it takes N = 10 dates as input, compares all of them, and prints the
oldest date.

Input will be given in the following format—each line will contain a new date in the form “D M Y.”

Testcase1
Input:

12 9 2002

27 1 2003

Output: The oldest date is 12-9-2002

Explanation: The dates correspond to 12th September, 2002 and 27th January, 2003. The older date is
printed.
Reading: Example Cases
Reading Objective:

In this reading, you will learn about two comprehensive examples. The first one is on the point structure,
which is essential for coordinate geometry, and the second one is on a student record/database.

Main Reading Section:

Case Study 1: Structures for Co-Ordinate Geometry

Question: Define a structure for representing two-dimensional (2D) grid points on a plane. Using
the structure, define functions to answer the following questions:

• In which quadrant does a given point lie?

• Does a given point lie on the coordinate axes?

• Do the two given points lie on the same quadrant?

Answer: Let us start by defining the point structure and the function prototypes.

The function FindQuadrant will return 1/2/3/4 based on the quadrant the point lies in, and 0 if it lies on the
axes. Hint: see the figure given below.
We can implement the FindQuadrant function as follows:

Now, we can use this function to implement the SameQuadrants function.

Point to ponder: This function will return true even when both points are on the coordinate axes. How
would you change the function if the result should be false for points on coordinate axes?

Answer:

Question: Add the following functionality to the Point program created above.
• What is the distance between two given points?

• Are the three given points collinear?

Recall that the distance between two points is given by the equation:

We can implement this in code as follows (remember that we need to include math.h header file to use the
sqrt function!):

Also, recall that three points are collinear if the area of the triangle formed by them is 0. The area of the
triangle formed by three points is equal to:

We just need to check if this area is 0. Here is how to do it:

Let us now write an example program to show how the code we just wrote would work. Try to write the
program in your local/online compiler and see if the outputs match.
Output:

Point p2 lies in quadrant = 1

Point p5 lies in quadrant = 3

Points p2 and p3 lie in the same quadrants.

Distance between p3 and p5 is = 21.213203

Collinearity of p1, p2, p3 = 1

Collinearity of p1, p2, p4 = 0

Case Study 2: Student Database

Question: Define a structure for representing an array of students. Each student must have a
name, ID, and fields for marks in two subjects. Using the structure, write functions to:

• Print the name and ID of all students whose average marks are greater than 70.

Answer:
Output:

Abhinav 1 100.000000

Shivam 3 73.000000

Harsh 4 72.500000

Question: Modify the Student structure to store the date of joining for each student. Now, write
the functionality to:

• Print the name and ID of students whose date of joining is 2020 or later.

Hint: Recall how to use nested structures!

Answer: Date is not easy to represent using primitive data types. It is a combination of date, month, and
year. It makes sense to make a new structure for the date. We can now define our structures as follows:
The rest of the program is fairly straightforward after this:

Output:

Abhinav 1 21-1-2021

Manish 2 15-9-2020

Shivam 3 7-12-2020

Reading Summary:

Examples of various real-world applications of structures in C. In the first example, you represented points on a
two-dimensional (2D)cartesian plane as structures and performed various operations of coordinate geometry on
them. In the second example, you created a student database to store various details of students and performed
various queries on the database
Lesson 1: Structures in a C Program

Chapter 14, Sections 14.1–14.4

14 User-Defined Data Types

WHAT TO LEARN

How structures extend the capabilities of arrays.


The two-part sequence of declaring and defining structures.
The restricted operations that can be performed on structures.
How structures differ from arrays when used in functions.
When a structure requires to be accessed by a pointer.
How a union interprets a section of memory in multiple ways.
The use of bit fields for handling data in units of bits rather than bytes.
Enhance the readability of programs using enumerators.

14.1 STRUCTURE BASICS


C offers a basket of data types for handling different levels of complexity in the organization of
data. The primary data types (like int and float) are meant for using simple and small amounts
of data. The array is offered as a derived data type for handling a group of data items of the same
type. Real-life situations often involve the use of heterogeneous data that need a mix of data types
to be handled as a single unit. Structures and unions meet this requirement perfectly.
A group of data items are often logically related to one another. For instance, the attributes of an
employee include the name, address, date of birth, salary, and so forth. Each of these attributes
can be encapsulated as a member or field in a structure which can be manipulated as a single unit.
Each member or field, however, retains its separate identity in the structure. That is not all;
a member representing a date, for instance, can itself be a structure comprising the day, month
and year as its members.
A structure is a data type that is designed by the user. However, unlike the other data types, a structure
variable is based on a template that must be created first. Each member of the structure is accessed by
the notation structure.member, and can be used in practically the same way as a variable. A structure
446 Computer Fundamentals & C Programming

member can be a primary data type, an array or even another structure (or union). To accommodate
multiple structures having the same type, C also supports an array of structures.
The advantage of using a structure is strongly felt when it is used as an argument to a function.
It is more convenient to pass a structure containing all employee details as a single argument
to a function rather than pass its members as separate arguments. A function can also return
a structure, and if copying an entire structure bothers you, you can even pass a pointer to a structure
as a function argument. Structures present the best of both worlds as you’ll see soon.

14.2 DECLARING AND DEFINING A STRUCTURE


Because the components of a structure are determined by the user and not by C, a structure variable
is created in two steps which may or may not be combined:
Declare the structure to create a template which specifies the components of the structure.
Declaration simply makes type information available to the compiler. However, the compiler
doesn’t allocate memory by seeing the declaration.
Define a variable that conforms to the template. The compiler allocates memory for the variable
and creates an instance of the template.
Declaration uses the struct keyword, optionally followed by the name of the structure and a set
of specifications for each member or field. The following generalized syntax declares a structure
named struct_name:
struct struct_name {
data_type_1 member_name_1; First member
data_type_2 member_name_2; Second member
...
data_type_n member_name_n; Last member
};
struct_name represents the structure tag. It is followed by the structure body enclosed in a set of
curly braces terminated with a semicolon. The body contains the names of the members preceded
by their data types. For instance, member_name_1 has the data type data_type_1, which could be
a primary, derived or user-defined type (including another structure). Each member specification
is actually a declaration statement, the reason why it is terminated with a semicolon.
On seeing the declaration, the compiler determines how to organize the structure members in
memory. It’s only when you actually create structure variables (by definition), that memory is
allocated by the compiler for the structure. The syntax of the definition also specifies the keyword
struct, the structure tag (struct_name) and the variable name (struct_var):
struct struct_name struct_var;

Let’s now declare and define a structure comprising three members that contain information of
an audio CD sold in a store. This information is represented by the title, the quantity in stock and
the price. The following declaration creates a structure named music_cd:
User-Defined Data Types 447

struct music_cd { music_cd is the structure tag


char title[30]; First member
short qty;
float price;
}; Declaration ends with ;
The structure tag (here, music_cd) is simply an identifier that is needed to create variables of this
type. The names of the three members (title, qty and price) are preceded by their types and
followed by the ; terminator. Like int and float, music_cd is a data type, but a user-defined one.
You can now define one or more structure variables based on the created template. Just as a variable
definition begins with the data type (int x;), the same is true for the definition of a structure:
struct music_cd disk1; Allocates memory for the members
struct music_cd disk1, disk2; Can define multiple variables
The compiler allocates memory for the variables disk1 and disk2, which have struct music_cd
as their data type. These two words representing the type must precede every definition of this
structure. However, C supports an abbreviation feature called typedef (14.4.2) that can create
a simple synonym for the words struct music_cd.

Takeaway: Declaration of a structure creates a template but doesn’t allocate memory for the
members. Memory is allocated when variables based on this template are created by definition.

Note: The terms declaration and definition are often used interchangeably with structures.
This book, however, follows the convention adopted by Kernighan and Ritchie to create a template
by declaration and an instance (i.e., an actual object) by definition.

14.2.1 Accessing Members of a Structure


The variable disk1 (or disk2) is a real-life object of type struct music_cd. The individual members
are connected to their respective variables with a dot, the member operator. Thus, the members of
disk1 are accessed as disk1.title, disk1.qty and disk1.price. These members can be treated like
any other variable, which means that you can assign values to them in the usual manner:
strcpy(disk1.title, “Mozart”);
disk1.qty = 3;
disk1.price= 10.75;
Note that it is not possible to use disk1.title = “Mozart”; because title is an array which signifies
a constant pointer. You can also assign values to these members using scanf:
scanf(“%s”, disk1.title);
scanf(“%hd”, &disk1.qty);
The same dot notation must be used to print the values of these members. For instance,
printf(“%f”, disk1.price); displays the value of the member named price.
Another structure in the program may also have a member named title, but the name disk1.title
will be unique in the program. The naming conventions used for simple variables apply equally to
structure members. This means that the name can’t begin with a digit but can use the underscore.
448 Computer Fundamentals & C Programming

14.2.2 Combining Declaration, Definition and Initialization


The preceding definitions created the variables disk1 and disk2 without initializing them. It is
possible to combine declaration and definition by adding the variable name after the closing curly
brace (Form 1). It is also possible to simultaneously initialize the variable (Form 2):

struct music_cd { struct music_cd {


char title[30]; char title[30];
short qty; short qty;
float price; float price;
} disk1; } disk2 = {“Beethoven”, 3, 12.5};

Form 1 Form 2

For an initialized structure, the variable name is followed by an = and a list of comma-delimited
values enclosed by curly braces. These initializers obviously must be provided in the right order.
In Form 2, “Beethoven” is assigned to title and 3 to qty.
You can create multiple variables and initialize them at the same time, as shown in the following
snippets which represent the last line of declaration:
} disk1, disk2, disk3;
} disk1 = {“Beethoven”, 3, 12.5}, disk2 = {“Mahler”, 4, 8.75};

Like with arrays, you can partially initialize a structure. If you initialize only title, the remaining
members, qty and price, are automatically assigned zeroes.
By default, uninitialized structures have junk values unless they are global. A structure defined
before main is a global variable, which means that the members are automatically initialized to
zero or NULL.

14.2.3 Declaring without Structure Tag


If declaration and definition are combined, the structure tag can be omitted. This is usually
done when no more structure variables of that type require to be defined later in the program.
The following statement has the tag missing:
struct { No tag specified
char title[30];
short qty;
float price;
} disk4 = {“Borodin”, 5}, disk5 = {“Schubert”};
You can’t subsequently create any more variables of this type. Here, both disk4 and disk5 are
partially initialized, which means that disk4.price, disk5.qty and disk5.price are automatically
initialized to zeroes.

Note: The structure tag is necessary when a structure is declared and defined at two separate places.
User-Defined Data Types 449

14.3 intro2structures.c: AN INTRODUCTORY PROGRAM


Program 14.1 declares a structure named music_cd containing three members. One of them is an
array of type char meant to store a string. In this program, four variables are created and assigned
values using different techniques:
disk1 Declared, defined and initialized simultaneously.
disk2 Defined separately but initialized fully.
disk3 Defined separately and initialized partially; two members are assigned values
by scanf.
disk4 Defined separately but not initialized at all; its members are assigned individually.

/* intro2structures.c: Declares and initializes structures. */


#include <stdio.h>
#include <string.h>

int main(void)
{
struct music_cd { /* Declares structure for music_cd */
char title[27]; /* Member can also be an array */
short qty;
float price;
} disk1 = {“Bach”, 3, 33.96}; /* Defines and initializes ...
... structure variable disk1 */
printf(“The size of disk1 is %d\n”, sizeof disk1);

/* Three more ways of defining structure variables */


struct music_cd disk2 = {“Bruckner”, 5, 6.72};
struct music_cd disk3 = {“Handel”}; /* Partial initialization */
struct music_cd disk4;

/* Assigning values to members */


strcpy(disk4.title, “Sibelius”);
disk4.qty = 7;
disk4.price = 10.5;

printf(“The four titles are %s, %s, %s and %s\n”,


disk1.title, disk2.title, disk3.title, disk4.title);

/* Using scanf */
fputs(“Enter the quantity and price for disk3: “, stdout);
scanf(“%hd%f”, &disk3.qty, &disk3.price);
printf(“%s has %hd pieces left costing %.2f a piece.\n”,
disk3.title, disk3.qty, disk3.price);
return 0;
}

PROGRAM 14.1: intro2structures.c


450 Computer Fundamentals & C Programming

The size of disk1 is 36


The four titles are Bach, Bruckner, Handel and Sibelius
Enter the quantity and price for disk3: 3 7.75
Handel has 3 pieces left costing 7.75 a piece.

PROGRAM OUTPUT: intro2structures.c

Note that sizeof computes the total memory occupied by a structure. This is not necessarily
the sum of the sizes of the members. For reasons of efficiency, the compiler tries to align one or
more members on a word boundary. Thus, on this machine having a 4-byte word, title occupies
28 bytes (7 words) even though it uses 27 of them. qty and price individually occupy an entire
word (4 bytes each), but qty uses two of these bytes. disk1 thus needs 33 bytes (27 + 2 + 4) but it
actually occupies 36 bytes.
Alignment issues related to structures lead to the creation of slack bytes or holes in the allocated
memory segment (Fig. 14.1). If you reduce the size of the array to 26, no space is wasted and
sizeof disk1 evaluates to 32.

title qty price

A B C D E FG H I J K LM N O P Q R S T U V W X YZA - - - 9 9 999 9

Slack bytes

FIGURE 14.1 Memory Layout of Members of music_cd

14.4 IMPORTANT ATTRIBUTES OF STRUCTURES


Because a structure can include any data type as a member (including pointers, unions and other
structures), this diversity leads to restrictions on the operations that can be performed on them.
Be prepared for a surprise or two when you consider the following features of structures:
There are no name conflicts between structure templates, their instantiated variables and members.
It is thus possible to have the same names for all of them as shown in the following:
struct x { Name of template is the same ...
char x[30]; ... as a member and ...
short y;
} x, y; ... a structure variable.
Even though the compiler finds nothing wrong in this declaration and definition, a programmer
would do well to avoid naming structures and members in this manner.
The =, dot, -> and & are the only operators that can be used on structures. Two of them (= and &)
retain their usual significance, while the dot and -> are exclusively meant for use with structures.
User-Defined Data Types 451

A structure can be copied to another structure provided both objects are based on the same
template. The first statement in the following code segment defines a variable disk2 based
on a template declared earlier. The second statement uses the assignment operator to copy all
members of disk2 to disk3:
struct music_cd disk2 = {“mozart”, 20, 9.75};
struct music_cd disk3 = disk2;
This feature is not available with arrays; all array elements have to be copied individually.
No arithmetic, relational or logical operations can be performed on structures even when the operation
logically makes sense. It is thus not possible to add two structures to form a third structure
even if the members are compatible.
It is not possible to compare two structures using a single operator even though such comparison
could be logically meaningful. Each member has to be compared individually as shown by
the following code:
if (strcmp(disk2.title, disk3.title) == 0
&& disk2.qty == disk3.qty
&& disk2.price == disk3.price)
fputs(“disk2 and disk3 are identical structures\n”, stdout);

If a structure contains 20 members, you need to use 20 relational expressions to test for equality.
Unfortunately, C doesn’t support a better option.
When a structure is passed by name as an argument to a function, the entire structure is copied inside
the function. This doesn’t happen with arrays where only a pointer to the first element of
the array is passed. However, copying can be prevented by passing a pointer to a structure as
a function argument.
A member of a structure can contain a reference to another structure of the same type.
This property of self-referential structures is used for creating linked lists.
Barring the last attribute in the list, the other attributes will be examined in this chapter.
Self-referential structures are discussed in Chapter 16.

Takeaway: An array and structure differ in three ways: 1) A structure variable can be assigned
to another structure variable of the same type. 2) The name of a structure doesn’t signify
a pointer. 3) When the name of a structure is passed as an argument to a function, the entire
structure is copied inside the function.

14.4.1 structure_attributes.c: Copying and Comparing Structures


Program 14.2 demonstrates the use of (i) the = operator to copy a four-member structure named
cricketer, (ii) a set of four relational expressions to individually compare the members of two
structure variables, bat1 and bat2. bat1 is declared, defined and initialized simultaneously and is
then copied to bat2 before they are compared.
452 Computer Fundamentals & C Programming

/* structure_attributes.c: Copies a structure and tests whether two


structures are identical. */
#include <stdio.h>
#include <string.h>
int main(void)
{
struct cricketer {
char name[30];
short runs;
short tests;
float average;
} bat1 = {“Don Bradman”, 6996, 52, 99.94};
printf(“bat1 values: %s, %hd, %hd, %.2f\n”,
bat1.name, bat1.runs, bat1.tests, bat1.average);
struct cricketer bat2;
bat2 = bat1; /* Copies bat1 to bat2 */
printf(“%s scored %hd runs in %hd tests at an average of %.2f.\n”,
bat2.name, bat2.runs, bat2.tests, bat2.average);
/* Compares members of two structures */
if (strcmp(bat2.name, bat1.name) == 0 && bat2.runs == bat1.runs
&& bat2.tests == bat1.tests && bat2.average == bat1.average)
fputs(“The two structures are identical\n”, stdout);
else
fputs(“The two structures are not identical\n”, stdout);
return 0;
}

PROGRAM 14.2: structure_attributes.c


bat1 values: Don Bradman, 6996, 52, 99.94
Don Bradman scored 6996 runs in 52 tests at an average of 99.94.
The two structures are identical

PROGRAM OUTPUT: structure_attributes.c

14.4.2 Abbreviating a Data Type: The typedef Feature


The typedef keyword is used to abbreviate the names of data types. Using a data type LL instead of
long long involves less typing. Also, use of meaningful names makes programs easier to understand.
The syntax of typedef is simple; follow typedef with the existing data type and its proposed synonym:
typedef data_type new_name;

You can now use new_name in place of data_type. Some of the declarations we have used previously
can now be abbreviated using typedef:
User-Defined Data Types 453

typedef unsigned int UINT; UINT is synonym for unsigned int


typedef unsigned long ULONG; ULONG is synonym for unsigned long
These typedef statements are usually placed at the beginning of the program, but they must precede
their usage. You can now declare variables of the UINT and ULONG types:
UINT int_var; int_var is of type unsigned int
ULONG long_var; long_var is of type unsigned long
Use of uppercase for synonyms is not mandatory but is recommended because you can then
instantly spot the “typedef ’d” data types. typedef is commonly used for creating synonyms to
names of structures, their pointers and enumerators. The synonym can be formed when creating
the template or after, as shown by the following forms shown side by side:

typedef struct student { struct student {


char name[30]; char name[30];
int dt_birth; int dt_birth;
short roll_no; short roll_no;
short total_marks; short total_marks;
} EXAMINEE; }
EXAMINEE stud1; typedef struct student EXAMINEE;
EXAMINEE stud1, stud2;

Form 1 Form 2

Form 1 combines the creation of the synonym named EXAMINEE with the declaration of student,
which is preceded by the keyword typedef. In this form, the structure tag (student) is optional and
can be dropped. But the second form creates the synonym after the declaration, so the tag is necessary.
EXAMINEE is now a replacement for struct student, so you can create variables of type EXAMINEE.

Note: typedef was not designed simply to replace data types with short names. It was designed to
make programs portable by choosing the right data types without majorly disturbing program
contents. If you need 4-byte integers, you can use typedef int INT32; to create a synonym in a special
file and then create variables of type INT32 in all programs. If the programs are moved to a machine
where int uses 2 bytes, you can simply change the statement to typedef long INT32; without disturbing
the contents of the programs. (The long data type uses a minimum of 4 bytes.)

14.4.3 structure_typedef.c: Simplifying Use of Structures


Program 14.3 demonstrates the use of typedef in abbreviating a primary data type and two
structures. The structure named film is given the synonym MOVIE1 at the time of declaration.
An identical but separate structure named cinema is typedef ’d to MOVIE2 after declaration. Observe
the dual use of typedef with cinema; the data type of year is typedef ’d to USHORT before cinema is
typedef ’d to MOVIE2.
Lesson 2: More Structures
Chapter 14, Sections 14.5–14.8.1.

454 Computer Fundamentals & C Programming

/* structure_typedef.c: Demonstrates convenience of abbreviating


data types with typedef. */
#include <stdio.h>

int main(void)
{
typedef struct film { /* Declaration also creates synonym */
char title[30];
char director[30];
unsigned short year;
} MOVIE1; /* Synonym MOVIE1 created as a data type */

MOVIE1 release1 = {“The Godfather”, “Francis Ford Coppola”, 1972};


printf(“release1 values: %s, %s, %hd\n”,
release1.title, release1.director, release1.year);

typedef unsigned short USHORT; /* Synonym USHORT created here ... */


struct cinema {
char title[30];
char director[30];
USHORT year; /* ... and used here */
};

typedef struct cinema MOVIE2; /* Synonym created after declaration */


MOVIE2 release2 = {“Doctor Zhivago”, “David Lean”, 1965};
printf(“release2 values: %s, %s, %hd\n”,
release2.title, release2.director, release2.year);

return 0;
}

PROGRAM 14.3: structure_typedef.c


release1 values: The Godfather, Francis Ford Coppola, 1972
release2 values: Doctor Zhivago, David Lean, 1965

PROGRAM OUTPUT: structure_typedef.c

Note: Even though film and cinema have identical members, they represent separate templates.
Their variables are thus not compatible for copying operations. It’s not possible to assign release1
(of type film) to release2 (of type cinema) or vice versa.

14.5 NESTED STRUCTURES


A structure member can have any data type—including another structure (but not of the same type
though). The inner structure can contain another structure and so on. C supports nested structures
and the following outer structure named student contains an inner structure named dt_birth as
one of its four members:
User-Defined Data Types 455

struct student {
char name[30]; Member 1
struct { Structure as member
short day;
short month;
short year;
} dt_birth; Member 2
int roll_no; Member 3
short total_marks; Member 4
};
struct student stud1;
Here, the declaration of the inner structure forms part of the declaration of the outer one. Note that
dt_birth is actually a variable to which its members are connected. However, you can’t separately
create a structure variable of this type. This restriction, however, doesn’t apply if the two structures
are declared separately. Define the inner structure before the outer one:
struct dob { dob must be declared before ...
short day;
short month;
short year;
};
struct student {
char name[30];
struct dob dt_birth; ... it is used in student.
int roll_no;
short total_marks;
};
struct student stud1;
struct dob dob1;
Encapsulating the three components of a date into a separate structure has two advantages over
using separate “unstructured” members. First, you can pass this structure to a function as a single
argument without losing the ability of individually accessing its members. Second, because dob
can be used by multiple programs, its declaration could be stored in a separate file. A program that
uses a date field as a three-member structure can simply include this file.

14.5.1 Initializing a Nested Structure


When it comes to initialization, nested structures resemble multi-dimensional arrays. For every
level of nesting, a set of inner braces have to be used for members of the inner structure. This is
how you initialize the structure of the preceding template:
struct stud1 = {“Oskar Barnack”, {1, 11, 1879}, 3275, 555};
The three initializers inside the inner braces represent the values of dob members. The inner braces
are optional but you must use them for two reasons. First, they provide clarity when associating
initializers with their members. Second, they let you partially initialize a structure. For instance,
you can drop the value for month or both month and year while initializing stud1.
456 Computer Fundamentals & C Programming

14.5.2 Accessing Members


A member of an inner structure is connected by a dot to its immediately enclosing structure, which
is connected by another dot to the outer structure. For a structure with a single level of nesting, the
innermost member can be accessed as
outer_structure.inner_structure.member
In the present case, after student has been instantiated to form the variable stud1, month can be
accessed as
stud1.dt_birth.month scanf needs the & prefix

For every increment in the level of nesting, the number of dots increase by one. This dot-delimited
notation is similar to the pathname of a file. On Windows and UNIX systems, the pathname
a/b/c refers to a file named c having b as its parent directory, which in turn has a as its parent.
For a structure member, the name a.b.c can be interpreted in a similar manner: a and b must be
names of structures while c can never be one.
14.5.3 structure_nested.c: Program Using a Nested Structure
Program 14.4 uses a nested structure where the inner structure named dob is incorporated
as a member of the outer structure named student. This data type is typedef ’d to EXAMINEE and
used to create two variables, stud1 and stud2. stud1 is initialized but stud2 is populated by scanf.
Note the use of the flag 0 in the printf format specifier (%02hd). This flag pads a zero to the day
and month members of dt_birth to maintain the 2-digit width.

/* structure_nested.c: Shows how to access each member of a nested structure


using structure1.structure2.member notation. */
#include <stdio.h>
int main(void)
{
struct dob { /* Must be declared before student */
short day;
short month;
short year;
};
typedef struct student {
char name[30];
struct dob dt_birth; /* Member is a nested structure */
int roll_no;
short total_marks;
} EXAMINEE; /* Synonym for struct student */

EXAMINEE stud1 = {“Oskar Barnack”, {1, 11, 1879}, 3275, 555};


printf(“Name: %s, DOB: %02hd/%02hd/%hd, Roll No: %d, Marks: %hd\n”,
stud1.name, stud1.dt_birth.day, stud1.dt_birth.month,
stud1.dt_birth.year, stud1.roll_no, stud1.total_marks);
User-Defined Data Types 457

EXAMINEE stud2;
fputs(“Enter name: “, stdout);
scanf(“%[^\n]”, stud2.name); /* Possible to enter multiple words */

while (getchar() != ‘\n’)


; /* Clears newline from buffer */

fputs(“Enter DOB (dd/mm/yyyy), roll no. and marks: “, stdout);


scanf(“%2hd/%2hd/%4hd %d %hd”, &stud2.dt_birth.day, &stud2.dt_birth.month,
&stud2.dt_birth.year, &stud2.roll_no, &stud2.total_marks);

printf(“Name: %s, DOB: %02hd/%02hd/%hd, Roll No: %d, Marks: %hd\n”,


stud2.name, stud2.dt_birth.day, stud2.dt_birth.month,
stud2.dt_birth.year, stud2.roll_no, stud2.total_marks);
return 0;
}

PROGRAM 14.4: structure_nested.c


Name: Oskar Barnack, DOB: 01/11/1879, Roll No: 3275, Marks: 555
Enter name: Carl Zeiss
Enter DOB (dd/mm/yyyy), roll no. and marks: 11/9/1816 5723 666
Name: Carl Zeiss, DOB: 11/09/1816, Roll No: 5723, Marks: 666

PROGRAM OUTPUT: structure_nested.c

14.6 ARRAYS AS STRUCTURE MEMBERS


An array can also be a member of a structure, which is evident from the way we used one of type char
to represent the name or title in music_cd, cricketer and student. But structures in C support arrays
of any type as shown in the following modified form of the student structure:
struct student {
char name[30];
int roll_no;
short marks_pcb[3]; /* Member is an array of 3 elements */
};
Like nested structures, a variable of this type is initialized using a separate set of braces for the values
related to marks_pcb. You are aware that the inner braces are not mandatory but they provide clarity:
struct student stud1 = {“Oskar Barnack”, 3275, {85, 97, 99}};

We can access the individual elements of marks_pcb as stud1.marks_pcb[0], stud1.marks_pcb[1]


and so forth. These elements are handled as simple variables:
stud1.marks_pcb[0] = 90;
printf(“Physics Marks: %hd\n”, stud1.marks_pcb[0]);
scanf(“%hd”, &stud1.marks_pcb[0]);
458 Computer Fundamentals & C Programming

Could we not have used three short variables here? Yes, we could have, and added another two
variables if we wanted to include five subjects. But would you like to use five variables with five
statements or a single array in a loop to access all the marks?
Note: Besides providing clarity, the inner braces in the initializer segment allow the partial
initialization of array elements and members of nested structures.

14.7 ARRAYS OF STRUCTURES


We used two structure variables, stud1 and stud2, to handle data of two students. This technique
won’t work with 500 students. C supports an array of structures, whose definition may or may not
be combined with the declaration of the structure. The following statement defines an array of
type struct student that can hold 50 sets (or records) of student data:
struct student {
char name[30];
int roll_no;
short marks;
} stud[50]; Memory allocated for 50 elements
Alternatively, you can separate the declaration and definition. You can also use typedef to replace
struct student with STUDENT:
typedef struct student {
char name[30];
int roll_no;
short marks;
} STUDENT;
STUDENT stud[50]; All elements now uninitialized
Because stud is an array, its elements are laid out contiguously in memory. The size of each
array element is equal to the size of the structure in memory after accounting for slack bytes.
Pointer arithmetic can easily be employed here to access each array element, and using a special
notation (->), the members as well (14.9).
This array can be partially or fully initialized by enclosing the initializers for each array element
in a set of curly braces. Each set is separated from its adjacent one by a comma, while the entire set
of initializers are enclosed by outermost braces:
STUDENT stud[50] = {
{“Benjamin Franklin”, 1234, 90},
{“Max Planck”, 4321, 80},
...
{“Albert Einstein”, 9876, 70}
};
Each member of each element of this array is accessed by using a dot to connect the member to its
array element. For instance, the member name is accessed as stud[0].name, stud[1].name and so on.
If you have not initialized the array, then you’ll have to separately assign values in the usual manner:
User-Defined Data Types 459

strcpy(stud[5].name, “Emile Berliner”);


stud[5].roll_no = 3333;
stud[5].marks = 75;
You can use scanf to input values to each member using pointers to these variables (like &stud[i].
marks). A simple for loop using the array index as the key variable prints the entire list:
for (i = 0; i < 50; i++)
printf(“%s %d %hd\n”, stud[i].name, stud[i].roll_no, stud[i].marks);
An array of structures is akin to records with fields. In the early days of commercial data processing,
an employee’s details were held as individual fields in a record. The fields of each record were
processed before the next record was read. The same principle now applies to an array of structures
and the two programs that are taken up next clearly demonstrate this.
Tip: An array of structures can take up a lot of memory, so make sure that you set the size of the
array to a realistic value. On a Linux system, sizeof stud evaluated to 2000 bytes, i.e., 40 bytes
for each element.

14.7.1 array_of_structures.c: Program for Displaying Batting Averages


Program 14.5 features an array of structures of type cricketer. Each element of the array variable,
bat, stores the batting average and number of tests played by an individual. bat is partially initialized
with the values of two players. The third array element is assigned separately while the fourth
element is populated with scanf. The complete list is printed using printf in a loop.
The program also uses the FLUSH_BUFFER macro for ridding the buffer of character remnants that
are left behind every time scanf reads a string. Note the use of the - symbol in the format specifier
of the last printf statement. The - left-justifies the name and country—the way string data should
be printed.

/* array_of_structures.c: Demonstrates techniques of defining, initializing


and printing an array of structures. */
#include <stdio.h>
#include <string.h>
#define FLUSH_BUFFER while (getchar() != ‘\n’);
int main(void)
{
short i, imax;
struct cricketer {
char name[20];
char team[15];
short tests;
float average;
} bat[50] = {
{“Don Bradman”, “Australia”, 52, 99.94},
{“Greame Pollock”, “South Africa”, 23, 60.97}
};
i = 2;
460 Computer Fundamentals & C Programming

strcpy(bat[i].name, “Wally Hammond”);


strcpy(bat[i].team, “England”);
bat[i].tests = 85; bat[i].average = 58.46f;

i++;
fputs(“Enter name: “, stdout);
scanf(“%[^\n]”, bat[i].name);

FLUSH_BUFFER
fputs(“Enter team: “, stdout);
scanf(“%[^\n]”, bat[i].team);

fputs(“Enter tests and average: “, stdout);


scanf(“%hd %f”, &bat[i].tests, &bat[i].average);

imax = i;
for (i = 0; i <= imax; i++)
printf(“%-20s %-15s %3hd %.2f\n”,
bat[i].name, bat[i].team, bat[i].tests, bat[i].average);
return 0;
}

PROGRAM 14.5: array_of_structures.c


Enter name: Sachin Tendulkar
Enter team: India
Enter tests and average: 200 53.79
Don Bradman Australia 52 99.94
Greame Pollock South Africa 23 60.97
Wally Hammond England 85 58.46
Sachin Tendulkar India 200 53.79

PROGRAM OUTPUT: array_of_structures.c

14.7.2 structure_sort.c: Sorting an Array of Structures


Program 14.6 fully initializes an array of five structures. The structures are then sorted in descending
order on the marks field, using the selection sort algorithm that has been discussed previously (10.8).
The program prints the student details before and after sorting.

/* structure_sort.c: Sorts an array of structures on the marks field.


Algorithm used: selection sort */
#include <stdio.h>
#define COLUMNS 5
int main(void)
{
short i, j, imax;
User-Defined Data Types 461

struct student {
char name[30];
int roll_no;
short marks;
} temp, stud[COLUMNS] = {
{“Alexander the Great”, 1234, 666},
{“Napolean Bonaparte”, 4567, 555},
{“Otto von Bismark”, 8910, 999},
{“Maria Teresa”, 2345, 777},
{“Catherine The Great”, 6789, 888}
};

printf(“Before sorting ...\n”);


for (i = 0; i < COLUMNS; i++)
printf(“%-20s %4d %4hd\n”, stud[i].name, stud[i].roll_no, stud[i].marks);

printf(“\nAfter sorting ...\n”);


imax = i;
for (i = 0; i < imax - 1; i++) /* Selection sort algorithm ... */
for (j = i + 1; j < imax; j++) /* ... explained in Section 10.8 */
if (stud[j].marks > stud[i].marks) {
temp = stud[i];
stud[i] = stud[j];
stud[j] = temp;
}

for (i = 0; i < COLUMNS; i++)


printf(“%-20s%5d%5hd\n”, stud[i].name, stud[i].roll_no, stud[i].marks);

return 0;
}

PROGRAM 14.6: structure_sort.c


Before sorting ...
Alexander the Great 1234 666
Napolean Bonaparte 4567 555
Otto von Bismark 8910 999
Maria Teresa 2345 777
Catherine The Great 6789 888

After sorting ...


Otto von Bismark 8910 999
Catherine The Great 6789 888
Maria Teresa 2345 777
Alexander the Great 1234 666
Napolean Bonaparte 4567 555

PROGRAM OUTPUT: structure_sort.c


462 Computer Fundamentals & C Programming

It’s impractical to provide large amounts of data in the program. In real-life, structure data are saved
in a file, with each line (or record) representing the data of members stored in an array element.
Chapter 15 examines the standard functions that read and write files and how they can be used in
handling data of structures.

14.8 STRUCTURES IN FUNCTIONS


The importance of structures can be strongly felt when they are used in functions. Functions use
structures in practically every conceivable way, and this versatility makes structures somewhat
superior to arrays when used in functions. A structure-related data item can take on the following
forms when used as a function argument:
An element of a structure. The function copies this element which is represented in the form
structure.member.
The structure name. The function copies the entire structure. It doesn’t interpret the name of
the structure as a pointer. This is not the case when the name of an array is passed to a function.
A pointer to a structure or a member. This technique is normally used for returning multiple
values or avoiding the overhead of copying an entire structure inside a function.
A function can also return any of these three objects. Structures present the best of both worlds
because a function can be told to either copy the entire structure or simply its pointer. The former
technique is memory-intensive but safe while the latter could be unsafe if not handled with care.
We declare a function before main for its signature to be visible throughout the program. This means
that the declaration of a structure used by a function must precede the declaration of the function,
as shown in the following:
struct date { Structure declared before main ...
short day;
short month;
short year;
};
void display(struct date d); ... and before function declaration
The display function returns nothing but it could have an implementation similar to this:
void display(struct date d) Function definition
{
printf(“Date is %hd/%hd/%hd\n”, d.day, d.month, d.year);
return;
}
After the date template has been instantiated, you can invoke the function inside main using the
structure variable as argument:
struct date today = {29, 2, 2017};
display(today); Displays Date is 29/2/2017
Note that the display function copies the entire structure, today. However, this copy disappears
after the function has returned. Can we use this copy to change the original structure? We’ll soon
learn that we can.
User-Defined Data Types 463

Takeaway: A function using a structure as argument copies the entire structure. If the structure
contains an array, it too will be copied.

Caution: A program may run out of memory when using structures as arguments. A structure
containing numerous members and large arrays can lead to stack overflow and cause the program
to abort.

14.8.1 structure_in_func.c: An Introductory Program


Program 14.7 uses two functions that use a three-member structure named time as an
argument. The display_structure function simply outputs the values of the three members.
The time_to_seconds function returns the time after conversion to seconds. Note that we have
now moved the structure declaration before main and before the function declarations. time is now
a global template even though its variable t is not global.

/* structure_in_func.c: Use a structure as a function argument. */


#include <stdio.h>
struct time { /* Must be declared before main because ... */
short hour; /* ... following function declarations ... */
short min; /* ... use this structure. */
short sec;
};
void display_structure(struct time f_time);
int time_to_seconds(struct time f_time);
int main(void)
{
struct time t;
fputs(“Enter a time in hh:mm:ss format: “, stdout);
scanf(“%hd:%hd:%hd”, &t.hour, &t.min, &t.sec);
display_structure(t);
int value = time_to_seconds(t);
printf(“Value of t in seconds: %d\n”, value);
return 0;
}
void display_structure(struct time f_time)
{
printf(“Hours: %hd, Minutes: %hd, Seconds: %hd\n”,
f_time.hour, f_time.min, f_time.sec);
return;
}
int time_to_seconds(struct time f_time)
{
return f_time.hour * 60 * 60 + f_time.min * 60 + f_time.sec;
}

PROGRAM 14.7: structure_in_func.c


Lesson 3: Example Cases

Chapter 14, Sections 14.8.2–14.8.3.

464 Computer Fundamentals & C Programming

Enter a time in hh:mm:ss format: 9:15:45


Hours: 9, Minutes: 15, Seconds: 45
Value of t in seconds: 33345

PROGRAM OUTPUT: structure_in_func.c

14.8.2 time_difference.c: Using a Function that Returns a Structure


A function can also return a structure which often has the same type that is passed to it. In the
following declaration, the time_diff function accepts two arguments of type struct time and also
returns a value of the same type:
struct time time_diff(struct time t1, struct time t2);

Program 14.8 computes the difference between two times that are input to scanf in the form
hh:mm:ss. The values are saved in two structure variables, t1 and t2, that are typedef ’d to
TIME. A third variable, t3, stores the returned value. Note that the mins operand is shared by the
equal-priority operators, -- and dot, but because of L-R associativity, no parentheses are needed.

/* time_difference.c: Uses a function to compute and return


the difference between two times as a structure. */
#include <stdio.h>

typedef struct time {


short hours;
short mins;
short secs;
} TIME;

TIME time_diff(TIME t1, TIME t2); /* Function returns a structure */

int main(void)
{
TIME t1, t2, t3;

fputs(“Enter start time in hh:mm:ss format: “, stdout);


scanf(“%hd:%hd:%hd”, &t1.hours, &t1.mins, &t1.secs);

fputs(“Enter stop time in hh:mm:ss format: “, stdout);


scanf(“%hd:%hd:%hd”, &t2.hours, &t2.mins, &t2.secs);

t3 = time_diff(t1, t2);
printf(“Difference: %hd hours, %hd minutes, %hd seconds\n”,
t3.hours, t3.mins, t3.secs);
return 0;
}
User-Defined Data Types 465

TIME time_diff(TIME t1, TIME t2)


{
TIME diff; /* TIME visible here because it is ... */
if (t1.secs > t2.secs) { /* ... declared before main */
t2.mins--;
t2.secs += 60;
}
if (t1.mins > t2.mins) {
t2.hours--;
t2.mins += 60;
}
diff.secs = t2.secs - t1.secs;
diff.mins = t2.mins - t1.mins;
diff.hours = t2.hours - t1.hours;
return diff;
}

PROGRAM 14.8: time_difference.c


Enter start time in hh:mm:ss format: 6:45:50
Enter stop time in hh:mm:ss format: 8:35:55
Difference: 1 hours, 50 minutes, 5 seconds

PROGRAM OUTPUT: time_difference.c

Since t3 now has three new values, it can be said that time_diff has “returned” three values.
This property of structures breaks the well-known maxim that a function can return a single
value using the return statement. We’ll use this property to provide an alternative solution to the
swapping problem.
Note: The dot has the same precedence as the increment and decrement operators, but it has
L-R associativity. Thus, in the expression t2.mins--, the dot operates on mins before the -- does,
so we don’t need to use (t2.mins)-- in the program.

14.8.3 swap_success2.c: Swapping Variables Revisited


The programs, swap_failure.c (Program 11.3) and swap_success.c (Program 12.5) taught us an
important lesson: a function can interchange the values of two variables only by using their pointers
as arguments. What if these two variables are members of a structure? Program 14.9 proves that it is
possible to swap them without using pointers. The game-changer in this program is the statement,
s = swap(s);, which merits some discussion.
The swap function here accepts and returns a two-member structure of type two_var. This function
swaps the copies of s.x and s.y in the usual manner but it also returns a copy of the structure.
The swapping operation succeeds here because this returned value is assigned to the same variable
that was passed as argument (s = swap(s);). Using this technique, it is possible for a function to
“return” multiple values without using pointers.
466 Computer Fundamentals & C Programming

/* swap_success2.c: Successfully swaps two variables using a structure


and without using a pointer. */
#include <stdio.h>
struct two_var {
short x;
short y;
} s = { 1, 10};
struct two_var swap(struct two_var);
int main(void)
{
printf(“Before swap: s.x = %hd, s.y = %hd\n”, s.x, s.y);
s = swap(s); /* The game changer */
printf(“After swap: s.x = %hd, s.y = %hd\n”, s.x, s.y);
return 0;
}
struct two_var swap(struct two_var z)
{
short temp;
temp = z.x; z.x = z.y; z.y = temp;
return z;
}

PROGRAM 14.9: swap_success2.c


Before swap: s.x = 1, s.y = 10
After swap: s.x = 10, s.y = 1

PROGRAM OUTPUT: swap_success2.c

Takeaway: The assignment property (=) of structures, because of which one structure variable
can be assigned to another, allows a function to change the original values of structure members
using their copies.

14.9 POINTERS TO STRUCTURES


Pointers represent the ultimate access mechanism of structure members. As with the other data
types, a pointer to a structure must be defined before it is pointed to a structure variable. These steps
are shown in the following statements:
struct rectangle {
short length;
short width;
} rect1 = {10, 20};

struct rectangle *p; Creates pointer p


p = &rect1; p points to rect1
Reading: Pointers in C
Reading Objective:

In this reading, you will get a clear insight into what are pointers in C, how they are used, and their relations
to variables and addresses.

Main Reading Section

Motivation

We have used variables extensively in our code till now and have dealt with datatypes like int, float, char,
double, etc. We also used arrays when we needed to store multiple values of the same type, and structures
to store multiple values of different types. All of these kinds of variables have one thing in common—each
of them is associated with some fixed amount of memory in the CPU. We have seen that the size of each
variable depends on its data type. These sizes are system dependent. For example, a char variable may be
1 byte in size on one system and 2 bytes on another system. Similarly, an int variable may be 2 bytes on one
system and 4 bytes on another system. The size of a variable is determined by the compiler and is fixed for
the lifetime of the program. We can use the sizeof() operator to find the size of a variable. For example,
sizeof(int) returns the size of an int variable in bytes. In C, the & unary operator is used to obtain the memory
address of a variable. For example, &a would give the address of variable a.

Let us consider an example.

int main(void) {

int a = 5;

printf("Size of integer: %lu\n", sizeof(a));

printf("Address of a: %p\n", &a);

return 0;

The above program outputs something like this:

Size of integer: 4

Address of a: 0x7ffdf9dc7964

The first line shows that the size of an int in the current system is 4 bytes. Let us unpack the second line. This
is the memory address of variable a. The %p format specifier is used to print the memory address of a
variable. The memory address is a unique hexadecimal number associated with a variable. However, you
do not need to know the hexadecimal number system to understand this. The number 0x7ffdf9dc7964 when
converted into decimal is 140734656066148. This means that the compiler has allocated 4 bytes of memory
to a variable 'a' of type integer starting at location 140734656066148. Thus, the variable a can be imagined
as a container located in your computer's memory. The container has a fixed size and a fixed location in the
memory. The size of the container is determined by the data type of the variable. The location of the
container is the address.
What is a Pointer?

Pointers are special variables that store the memory address of another variable.

Let us learn how to use them now.

Declaring, Assigning, and Dereferencing Pointers

A typical pointer declaration would look like this:

int *p;

The above line of code says that p is a pointer to an integer (or, in simple words, in pointer variable p, we
are planning to store the address of an integer variable). The * operator following the name of the datatype
(int in this case) is used to declare a pointer to that datatype (in the above declaration, a pointer to an int).

Another example would be:

char *x;

This says that x is a pointer to a character.

Now, a pointer declared as above is currently not pointing to any variable. It is just a variable that can store
the memory address of another variable. To make a pointer point to a variable, we use the & operator. For
example, if we want to make the pointer p point to the variable a, we can write:

int *p;

int a = 5;

p = &a;

We can also assign a pointer to another pointer of the same type.

For example:

int *q;

q = p;

Thus, q is another pointer to an integer that points to the same location that p is currently pointing to (i.e.,
&a).

As we have seen with variables, we can combine the initialization and declaration in one line equivalently
as:

int *p = &a;

Now p is pointing to the variable a. If we try to print the value of p, we get the memory address of a. For
example, if we write:
printf("%p\n", p);

We would get the address of a. This line is equivalent to:

printf("%p\n", &a);

We can use the * operator to access the value of the variable that the pointer is pointing to. This is called
dereferencing a pointer variable. For example, if we write:

printf("%d\n", *p);

We would get the value of a. It is easy to see that this line is equivalent to:

printf("%d\n", a);

This * operator is called the dereference operator. It is used to access the value of the variable that the
pointer is pointing to.

Note that, a pointer is still a variable and does occupy space in the memory. The size of a pointer variable
depends on the size of an address on the system.

Let us consider an example where a is an integer with value = 5 and address = 1000, p is a pointer to a. The
size of an integer on the system is 4 bytes, and the size of a pointer on the system is 8 bytes. We can see that
p takes 8 bytes somewhere else in the memory and contains the value 1000 (address of a).

Pointer Arithmetic

In C, we have the flexibility to perform pointer arithmetic. That means we can perform certain arithmetic
operations on pointers. For example, we can add or subtract an integer from a pointer. The result of such
an operation is a pointer to a different location in the memory.

Let us consider an example: (Assume the size of int is 4 bytes)

int main(void) {

int a = 5;

int *p = &a;

printf("%p\n", p);

p = p + 1;

printf("%p\n", p);

return 0;

}
Here, it is tempting to think that the second print statement would print ((address of a) + 1). However, this
is not the case. The second line of code prints ((address of a) + 4). This is because the size of an integer is 4
bytes.

Let us try to understand the motivation behind this behavior.

Let us say ‘a’ occupies 4 bytes starting from location 1000, and b occupies the following 4 bytes in memory.

If p + 1 was 1001, then dereferencing it would have resulted in accessing 4 bytes starting from 1001, which
contains three bytes of 'a' and one neighboring byte (the first byte of b). This is not what we want. We want
to access the next integer in the memory. Thus, the compiler adds the size of an integer (that is 4) to the
address of a. This is the reason why the second print statement prints ((address of a) + 4).

In general, if we have a pointer of type T, then the result of p + 1 is ((address of p) + sizeof(T)). That is, it points
to the next variable of type T from its current location. Similarly, the result of p - 1 is ((address of p) -
sizeof(T)).

Now, it becomes intuitive to guess what the result of adding an integer to a pointer would be. The result of
p + n is ((address of p) + n * sizeof(T)). Similarly, the result of p - n is ((address of p) - n * sizeof(T)).

We can also use the ++ and -- operators on pointers. For example, if we write:

p++;

We are incrementing the pointer by 1. This is equivalent to:

p = p + 1;

which points p to the next integer in the memory by going 4 bytes ahead.

So, we have seen that we can add or subtract an integer from a pointer, and increment or decrement a
pointer. At this point, one might wonder if we can add 2 pointers. The answer is no. We cannot add
pointers. The reason is that pointers represent addresses, and the sum of two addresses does not make
sense.

However, we can subtract two pointers if they are of the same type. The result of this operation is an integer.
The result is the number of elements between the two pointers. For example, if we have two pointers p and
q of type T, then the result of q - p is the number of elements of type T between p and q. This is equivalent
to (q - p) / sizeof(T). The reason for this is that the result of q - p is the number of bytes between p and q.
Thus, we divide it by the size of the type of the pointer to get the number of elements between p and q.

Reading Summary:

• What is a pointer

• How to declare a pointer

• How to assign a variable’s address to a pointer

• Ways to access the value of a variable using a pointer . Steps to perform pointer arithmetic
Practice Lab 1: Pointers in C

Assignment Brief

Practice Lab 1_Question_1

Write a program to swap two numbers(integers) using pointers. The program should read two integer
numbers from the user and store them in the variables a and b. Then print the numbers before and after
swapping.

The program should use pointers to swap the values of a and b.

The program should print the values of a and b before and after swapping.

Two pointers, p and q, point to the variables a and b, respectively. The swapping code should not use the
variables a and b directly. They must achieve the swapping on a and b by using the references p and q and
any temporary variables like int temp.

Example: If the user enters 10 and 20, the program should print the following output:

Enter two numbers: 10 20

Before swapping: a = 10, b = 20

After swapping: a = 20, b = 10.

Practice Lab 1_Question_2

Two integers a and b have been declared in the program. Complete the code snippet to find the number of
characters that can be stored between the start locations of a and b.

(Hint: int pointers can be cast to char pointers)

Example: If the addresses of a and b are 1000 and 2000 respectively, and the size of a char is 1 byte, the
program should print the following output: "The number of characters between a at 0x3e8 and b at 0x7D0
is 1000". (1000 in hex is 0x3e8 and 2000 in hex is 0x7D0.)

Practice Lab 1_Question_3

We know that an int, a float, and a char take 4 bytes, 4 bytes, and 1 byte each in gcc.

Consider an array of four characters in memory. Now, these four characters are stored in four consecutive
memory locations of 1-byte width. However, the bits (0s and 1s) in these four 1-byte-wide locations can also
be interpreted as integers when an integer pointer to the starting location is dereferenced.

Given a character array of four characters, find the integer value that the four bytes represent. Also, find the
float value.

(Hint: Cast the pointer to the array to the appropriate types and dereference it to get the values.)
Pointers and Arrays
Reading Objective:

In this reading, you will learn about the relationship between arrays and pointers in C. You will also look at
the similarities and differences between arrays and pointers. Finally, you will also see how they can be
combined to form an array of pointers.

Main Reading Section:

Relationship Between Pointers and Arrays

We know that arrays are a contiguous collection of elements of the same data type. That is, the elements of
an array are stored in contiguous memory locations. For example, an array of integers is shown in Table 1.

1. An array of integers located at memory location 2000

You can calculate the memory address of any element by using the formula:

&arr[i] = &arr[0] + i * sizeof(arr[0])

where arr[0] is the memory address of the first element of the array arr[].

One would notice that this is essentially the same as pointer arithmetic. That is, if we have a pointer ptr,
which points to the 0th-index element of the array arr[], then we can access any element equivalently by
using the formula:

*(ptr + i)

Since the name of the array denotes the address of the 0th-index element, we can first initialize ptr as
follows:

ptr = arr;
Then, we can perform all operations that we do on an array arr by using ptr instead of arr.

We have usually seen strings as arrays of characters. For example, the string "Hello World" is stored as an
array of characters:

char str[] = "Hello World";

But we can also store a string as a pointer to the first character of the string:

char *str = "Hello World";

We can access any character of the string by using the formula:

*(str + i)

where i is the index of the character we want to access.

One interesting thing to note is that C arrays when used in expressions are automatically converted to
pointers to the first element of the array. For example, the following two expressions are equivalent:

arr[0]

*(arr + 0)

Moreover, the special syntax for accessing array elements using [ ] is just syntactic sugar for pointer
arithmetic followed by dereferencing. Thus, the following two expressions:

arr[i]

*(arr + i)

are equivalent.

This works both ways. We can use the [ ] notation with pointers as well. For example, we were able to access
the second character of the string "Hello World" by using the expression:

*(str + 1)

But we can also use the [ ] notation:

str[1]

The above two expressions are equivalent. The [] notation is internally converted to the addition of the
integer index to the pointer and then dereferencing the result.

Differences Between Using an Array and Using a Pointer

At this point, it might be tempting to assume that pointers and arrays are identical. But this is not the case.
Let us think back on what each of them means. An array is a contiguous block of data of one type, whereas
a pointer is just a variable that points to another variable of a specific type in memory. However, pointer
arithmetic and array indexing are equivalent. So, pointers can be used to mimic the behavior of arrays
when combined with another concept that we will see next week—Dynamic Memory Allocation.

Revisiting our string “Hello World” example, when it is stored as an array of characters, in the memory, it is
stored as an array of 12 characters. The array is located at the index of its first element.
2. Memory map of a string using an array of characters

H e L l o W o r l d

str
str[1] str[2] str[3] str[4] str[5] str[6] str[7] str[8] str[9] str[10]
str[0]

1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010

On the other hand, when a string is stored as a pointer to a character as char *str = “Hello World”,

we have a pointer variable str at one location in memory, and its value is an address where 11 consecutive
characters are stored. So, the pointer variable would take 8 bytes (assuming the size of the address on the
system is 8 bytes), and the compiler automatically allocates a size of 12 bytes for 12 characters.

3. Memory map of a string using a char pointer

Array of Pointers

An array of pointers is basically an array where each element can hold the address of the same datatype.

int *arr[10];

Here, arr is an array of 10 pointers to integers. We can access the pointers in the array using the [ ] notation:

arr[0]

arr[1]...

arr[9]

Each of these pointers can be assigned the address of an integer variable:

int a = 10;

int b = 20;

arr[0] = &a;
arr[1] = &b;

These pointers can also be assigned arrays of integers:

int arr1[] = {1, 2, 3, 4, 5};

int arr2[] = {6, 7, 8, 9, 10};

arr[0] = arr1;

arr[1] = arr2;

Now, we can access the elements of the arrays using the [] notation:

arr[0][0] – 1

arr[2][3] – 9

Thus, we can simulate a two-dimensional (2-D) array of 25 elements using an array of pointers of length 5
and five 1-D arrays of length 5 each. If we had used a 2D array of the form int arr[5][5], we would have
required a contiguous block of 5 × 5, i.e., 25, integer locations in memory. On the other hand, when we use
pointers, we require five sets of five contiguous locations for the five integer arrays and one set of five
contiguous locations of pointer size for the array of pointers.

Let us look at another example.

We can create an array of pointers to char pointers(strings):

char *arr[5];

Here, arr is an array of five pointers to strings. We can access the pointers in the array using the [ ] notation:

arr[0]

arr[1]...

arr[4]

Each of these pointers can be assigned the address of a string literal:

arr[0] = "Welcome";

arr[1] = "to";

arr[2] = "Introduction";

arr[3] = "to";

arr[4] = "Programming";

The interesting thing here is that if we had implemented the same thing using a 2-D array, we would have
had to use a 2-D array of characters such as

char arr[5][13];
Here, the length of each row needs to be at least the length of the longest string and the NULL('\0') character,
which here is 13 ("Programming\0"). This is not the case with the array of pointers. We can store pointers to
strings of different lengths in the array.

Thus, we can see that being able to use arrays and pointers together provides us with an immense amount
of freedom and flexibility, and we can decide the best implementation approach based on the needs and
constraints of the problem at hand.

Reading Summary:

In this reading, you have learned the following:

• What is the relationship between arrays and pointers


• How to find the internals of the [] notation
• The differences between arrays and pointers
• How to use arrays of pointers
• What are the tradeoffs between using arrays and pointers
Assignment Brief

Practice Lab 2_Question_1

Consider two arrays, one containing the names of the students in a class, and another containing their
marks in an evaluative component.

The name of the students is stored as an array of char * and their marks as an array of integers.

You can assume that all names are unique in the class. The same index in both arrays represents a student-
marks pair. This data is initialized in main().

Don’t modify the main() function.

You have to complete the code for a few functions which perform some operations on this data. These
functions have been declared, but their definitions have been left empty.

1. Complete the function num_pass() that calculates the number of students who passed the
component by scoring more than 35 marks.

2. Complete the function rank() that calculates the rank of a student in the exam.

3. For one of the questions, an alternate solution was correct. You have the list of names of students
who used the alternate solution, and you need to increment their marks by 5. Complete the function
update_marks() as specified in the code file.

Navigating the file:

• The file has been equipped with numerous comments explaining what the different functions do

• The main() has been provided. You need not modify it but you are encouraged to go through it first
to get a sense of what the program is trying to achieve.

• There are four additional functions defined in the file: (The parameters are not mentioned here. They
are explained in detail in the “.c” file)

o int num_pass(): Calculates the number of students who passed the examination, ie, scored >=
35 marks in the exam. You need to complete this function.

o int rank(): Calculates the rank of a particular student in the examination. You need to complete
this function.

o void update_marks(): Increments the marks of the specific students by 5. You need to complete
this function.

o void print_marks(): This is a helper function that prints the marks of all the students. This need
not be modified.
Reading: Pointers and Structures
Reading Objective:

In this reading, you will learn about the use of pointers to structures in C. You will also learn how to declare and use pointers
to struct, the use of the -> operator, and the operator precedence of these related operators in C. Lastly, you will also see
one use case in the form of self-referential structures.

Main Reading Section:

Declaring, Assigning, and Dereferencing Pointers to Structures

We have seen pointers to user-defined data types. We have also seen pointers to arrays. In this reading, we shall look at the
logical extension to the above idea: pointers to structures.

A pointer to a structure is declared in the same way as a pointer to a variable. For example, if the structure is defined as:

typedef struct {

int rollno;

char name[20];

float marks;

} student;

then the pointer to the structure is declared as follows:

student *ptr;

It is also assigned values in a similar fashion as a pointer to a variable:

student s1 = {2, “John”, 45};

ptr = &s1;

We can also assign pointers of the same structure type to each other. For example, if we have another pointer to the same
structure type, we can assign it to the first pointer as follows:

student *ptr2;

ptr2 = ptr;

However, we cannot assign a pointer of one structure type to a pointer of another structure type. For example, if we have
a pointer to a different structure type, we cannot assign it to the first pointer as follows. For example,

struct book {

int bookno;

char title[20];

float price;

};

struct book *ptr3; // Pointer to a different structure type

ptr3 = ptr; // This is not allowed since the two pointers are of different structure types.
Accessing Members of the Struct Using the Pointer

Now, it is evident that we can access the members by dereferencing the pointer and using the dot operator. For example,
here, we can access the roll number of the student by using the following statement:

(*ptr).rollno

Here, it is important to note that the dot operator has higher precedence than the dereference operator. Hence, the
dereferencing operator (*) must be specified in parentheses. The statement *ptr.rollno is not valid. Here, the compiler will
try to access the member rollno of the pointer ptr. This is not possible. Hence, the correct way to write the statement is as
follows:

(*ptr).rollno

C also provides a shorthand to access the members of a struct pointed to by a pointer. The shorthand is to use the arrow
operator (->). The arrow operator is used as follows:

ptr->rollno

The above statement is equivalent to (*ptr).rollno

Precedence of Operators

We can now recollect the order of precedence of operators in C and observe the precedence of these new operators in the
table of operators:

Thus, we can see that the -> and . operators are at the highest precedence level and the dereference operator is at the third
level.

We must keep the precedence order in mind while using these operators. Otherwise, the compiler may throw an error or
may result in unintended behavior.

For example, consider the following statement:

*ptr->rollno

The above statement is equivalent to *(ptr->rollno) owing to the higher precedence of -> over *. This results in an error
because we cannot dereference rollno, which happens to be an integer.

Similarly, the following statement:

*ptr->rollno++
is equivalent to *((ptr->rollno)++).

Hence, we must be careful while using these operators. We must be aware of the precedence order and try to be explicit
with the kind of behavior we want (using parenthesis) to improve the code's readability.

Self-Referential Structures

Self-referential structures contain a pointer member that points to a structure of the same structure type.

Consider the following structure:

typedef struct person {

char name[20];

int age;

} person;

Now, in case we want to store the parent of each person, we can try to add a person member to the struct as follows:

typedef struct person {

char name[20];

int age;

struct person parent;

} person;

However, this creates an issue. The structure person is used within itself. Hence, the compiler will not be able to compile
this code. One simple reason for this will become apparent if we try to calculate the size of the structure person. The size
of the structure person is the sum of the sizes of its members. The size of the member parent is again the size of the structure
person. This recursive computation of the size will lead to the size of the structure person becoming infinite. This is not
possible. Hence, the compiler will throw an error.

To overcome this issue, we can use pointers for nesting the structures. For example, we can define the structure person as
follows:

typedef struct person {

char name[20];

int age;

struct person *parent;

} person;

Now, the size of the structure person is the sum of the sizes of its members. The size of the member parent is the size of a
pointer. Hence, the size of the structure person is now finite. The compiler will be able to compile this code.

This feature of having pointers to structures of the same type as members of a structure is very powerful. It allows us to
create complex data structures such as trees, graphs, linked lists, etc.

Reading Summary:

• How to declare and use pointers to structures. Steps to access members using the ->, *, and . operators

• The precedence of operators . Self-referential structures as a use case


Practice Lab 3 : Pointers and Structures
Assignment Brief

Practice Lab 3_Question_1

Consider that you are managing the records for the 99th precinct of the NYPD. You have been given the
details of the employees of the precinct in the form of an array of pointers to employee structs. Each
employee struct has the following fields:

char name[20]: the name of the employee

int age: the age of the employee

int salary: the salary of the employee

employee *manager: a pointer to the manager of the employee.

If the employee does not have a manager, the pointer is set to NULL. If the employee has a manager,
manager points to the manager's struct.

A main() program has been written that makes use of certain functions. These functions need to be
completed for the proper functioning of the program.

1. Complete the function print_employee() that prints the details of the employee whose struct is
pointed to by a parameter e.

2. Complete the function superiors() that prints the details of all the superiors of the employee and
returns the number of superiors.

3. Complete the function average_salary() that returns the average salary of all the employees in the
array of pointers to employee structs.

• The file has been equipped with numerous comments explaining what the different functions do.

• The main() has been provided. You need not modify it but you are encouraged to go through it first
to get a sense of what the program is trying to achieve.

• There are four additional functions defined in the file: (The parameters are not mentioned here. They
are explained in detail in the “.c” file.)

o print_employee(): This function prints the details of the employee whose struct is pointed to by
e. This function needs to be written by you.

o print_all_employees(): This function prints the details of all the employees whose structs are
pointed to by the elements of e. This function has been written for you. You do not need to
modify it. You would notice that it uses the print_employee() function that you wrote. Thus, you
need to write the print_employee() function before you can use this function.

o superiors(): This function prints( the details of all the superiors of the employee whose struct is
pointed to by e and returns the number of superiors. This function needs to be written by you.

o average_salary(): This function takes in the array of pointers to employee structs and returns
the average salary of all the employees in the array. This function needs to be written by you
Reading: Call by Value vs Call by Reference
Reading Objectives:

In this reading, you will learn to differentiate between call by value and call by reference methods of parameter passing.
You will look at one specific example of call by reference: passing arrays.

Main Reading Section:

Revisiting Parameter Passing in C

We know that functions in C accept parameters, perform some operations on them, and return a value. These functions are
invoked from other functions. There are two major paradigms of calling functions: call by value and call by reference. Let
us discuss them in greater detail.

Revisiting Parameter Passing in C

In C, a function definition has the following syntax:

return_type function_name( parameter list ) { body of the function }

Here, the parameter list consists of a comma-separated list of parameters. Each parameter has the data_type
parameter_name format. These parameters are known as formal parameters. When this function is called, the parameters
passed from the calling function are referred to as actual parameters. The actual parameters are copied to the formal
parameters. The scope of the formal parameters concludes at the end of the functional call, and they are destroyed.

1. Call by Value

In call by value, the actual parameters passed from the calling function are copied into the formal function. The called
function then performs operations on the copied parameters. The changes made to the parameters in the called function
are not reflected in the calling function. When the flow returns to the calling function, the parameters in the called function
are destroyed. The calling function uses its own unmodified parameters. The following example illustrates the call by value.

int sum(int x, int y){

x = x + y;

return x;

int main(){

int a = 4;

int b = 3;

int s = sum(a,b);

Though we modify the formal parameter x to store the sum, the actual parameters a and b remain unmodified.

We use call by value when we don't want to mutate (or modify) our data. We only want to perform some operations on the
data and return the result. For example, suppose we want to find the average of three integers. We don't want to modify
the three integers; we just want to use them to find the average. In this case, we can use call by value.

Each time we call a function with primitive data types or structures in C, we use call by value. This is because the primitive
data types and structures are passed by value.

2. Call by Reference
In call by reference, instead of values, the addresses (or references) of the parameters are passed to the called function.
The called function then performs operations on the parameters using the addresses. The changes made to the parameters
in the called function are reflected in the calling function. In C, call by reference is achieved using pointers. When the flow
returns to the calling function, the scope of the formal parameters in the called function is destroyed. However, the calling
function does have its parameters modified, as the modifications were done using references. Let us look at an example
that illustrates call by reference.

void increment(int *x){

*x = *x + 1;

int main(){

int a = 4;

increment(&a);

Though the function does not return a value, the actual parameter a is modified. The address of a is passed to the function.
The variable x is a formal parameter which is a pointer. The address of a is copied to x. The function then increments the
value at the address stored in x. This increments the value of a. The scope of the formal parameter x ends when the function
returns. However, the value of a is modified.

We use call by reference when we want to mutate our data, modify it, and then use it. For example, we might want to
normalize an array of numbers to hold only values between 0 and 1. We want to modify the array to normalize it. In this
case, we can use call by reference.

Call by reference example: Arrays

In C, arrays are passed by reference. This is because the array name denotes the address of the zeroth-index element of the
array. This is why we can modify the contents of an array in the called function, and the changes are reflected in the calling
function.

For example,

void increment(int *x, int n)// Note this signature is identical to void increment(int x[], int n)

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

x[i] = x[i] + 1;

int main()

int a[5] = {1,2,3,4,5};

increment(a, 5);

}
Here, the array a is passed to the function increment. The function then increments each element of the array. The changes
are reflected in the calling function. The array a is modified.

An important thing to note here is that since arrays are passed by reference, the function sees only a pointer. So we also
need to mention the length of the array explicitly. For example, if we had used the sizeof operator on the array in main(),
we could have calculated the length of the array. However, in the function, the sizeof() operator on the parameter (either
int *x or int x[]) would have returned the size of a pointer. So we need to explicitly pass the length of the array as a parameter
to the function. This is why we have int n as the second parameter in the function signature.

Reading Summary:

In this reading, you have learned the following:

• The mechanism of parameter passing in C

• What call by value and call by reference are, and what are their common use cases

• The changes that occur in memory when a call is invoked in either mode

• The passing of arrays in C using call by reference

Practice Lab 4 : Call by Value vs Call by Reference


Assignment Brief

Practice Lab 4_Question_1

Consider that we need to implement the absolute() functionality in C. The absolute value of an integer is the
number without its sign.

For a positive number, the absolute value is the number itself. For a negative number, the absolute value is
the number multiplied by -1.

For example, the absolute value of 5 is 5, the absolute value of -5 is 5.

We do not want you to use the abs() function in the math.h library.

You have to implement the absolute() function yourself. Try to implement it using

• call by value and

• call by reference.

The call by value version of the function should be called absolute_value() and should return the absolute
value of the input integer. The call by reference version of the function should be called
absolute_reference() and should update the value of the input integer to its absolute value.
absolute_reference() should return void.

For example, if the value of int variable a is -5 and it is passed to absolute_value(), the function should return
5.

If the value of int variable a is -5 and it is passed to absolute_reference(), the function should update the
value of a to 5 and return void.

Practice Lab 4_Question_2


Consider that you have given a paragraph of text. However, the text is not capitalized properly. You have to
capitalize the first letter of each sentence. Also, write a function that returns the number of sentences in the
paragraph.

For example, if the input is: "this week's assignment is to write a program that capitalizes the first letter of
each sentence. the program should also count the number of sentences in the paragraph. the program
applies the concept of call by value and call by reference."

The capitalized output should be: "This week's assignment is to write a program that capitalizes the first
letter of each sentence. The program should also count the number of sentences in the paragraph. The
program applies the concept of call by value and call by reference."

And the number of sentences would be: 3

You can ignore other capitalization rules for this problem. Assume that each sentence will end with a '.', '?'
or a '!' and that the input will always end with a '\0'(null terminator).

[Hint: You can use the isalpha() function to check if a character belongs to the alphabet. Also, you can use
the toupper() function to convert a character to uppercase.]
Passing Structures to Functions and Null Pointers
Reading Objectives:

In this reading, you will first look at passing structures to functions. You will then understand the mechanics of how they
are passed by default and the ways by which you can modify this behavior. Following this, you will look at NULL pointers
in C and understand why and how they are used.

Main Reading Section:

Passing Structures as function arguments

In C, structures are passed similarly to primitive data types like int, char, or float. The only difference is that the structure
name is used instead of the data type name.

For example:

typedef struct rectangle {

int length;

int width;

} rectangle;

int area(rectangle r) {

return r.length * r.width;

Here, area() takes a rectangle as an argument. The function can then access the structure members using the dot operator.
The formal parameter r is a copy of the actual parameter, so any changes made to r will not affect the actual parameter.

Thus, structures are passed by value. This is the same as passing an integer or a float. The following example illustrates
how the above function can be used:

rectangle r = {10, 5};

int a = area(r);

If we want to change the actual parameter, we can use call by reference. This is done by passing a pointer to the structure
instead of the structure itself. To access the value of a structure member using the pointer to the structure, we can use two
ways. One is using the dot operator (.), and the other is using the arrow operator (->). These two ways are illustrated in the
example below.

void double_width(rectangle *r) {

(*r).width *= 2;

void double_width(rectangle *r) {

r->width *= 2;

The function double_width() doubles the width of a rectangle. Here, we want to modify the rectangle being passed in. That
is, we want the actual parameter to be mutated. Hence, we use call by reference. Though the formal parameter is a new
variable, it is an alias(pointer) to the variable in the calling function. Hence, we can modify the actual parameters. The
following example illustrates how to use the above function:

rectangle r = {10, 5};

double_width(&r);

printf("width = %d\n", r.width);

The above snippet will print "width = 10"

Thus, we can see that structures are passed by value, but if we want to change the actual parameter, we can use call by
reference. This is done by passing a pointer to the structure instead of the structure itself.

NULL Pointers

Whenever we declare a pointer, we initialize it with a valid address. If you don't initialize it explicitly, it is initialized to a
random value like any other variable. However, dereferencing it now will lead to undefined behavior. It might also lead to
a segmentation fault if the user tries to access an inaccessible memory location.

To prevent this undefined behavior, we always try to initialize our variables. For pointers, we have a reserved address 0 that
is used to indicate that the pointer is not pointing to anything. This address is called the NULL pointer. It is defined in many
header files, including stdio.h and stdlib.h.

The definition would be a line like this:

#define NULL 0

So, if you want to declare a pointer that is not pointing to anything, you can do it like this:

int *ptr = NULL;

This is a good practice to follow. It is also a good practice to check if a pointer is NULL before dereferencing it. This is
because if you try to dereference a NULL pointer, you will get a segmentation fault. So, you can check if a pointer is NULL
like this:

if(ptr != NULL)

// some operations on the pointer

NULL pointers are also used to indicate when a function fails to return a valid pointer. In such cases, the accepted
convention is to return NULL. Then the calling function can check whether the returned pointer is NULL or not.

Thus, NULL pointers help in avoiding non-deterministic behavior and in error handling.

Reading Summary:

In this reading, you have learned the following:

• The differences between call by value and call by reference


• Pass structures to functions both by value and by reference
• The causes of non-determinism and errors that are possible due to uninitialized pointers
• Use NULL pointers
Practice Lab 5: Passing struct to functions and Null Pointers
Assignment Brief

Practice Lab 5_Question_1

Create a Point structure as taught in the lectures. Then, create two-point objects representing the points (3,
2) and (−1, −5). Print the x-coordinate and y-coordinate of the points to standard output.

Consider that we need to ship packages to customers. Each package has a weight in kilograms. We have
defined a structure called package that has three fields:

- weight: the weight of the package in kilograms

- cost: the cost of shipping the package in dollars

- cost_weight_ratio: the cost per kilogram of shipping the package in dollars that this package represents.

With this information, a program has to be written which:

• Calculates the cost of shipping all packages

• Calculates the cost per kilogram for each package and stores it in the cost_weight_ratio field

• Increase the cost of shipping a package by 10%.

For this, the following need to be done:

• Define a structure called package

• Declare an array of 5 packages

• Write a function that prints a package

• Write a function that calculates the cost per kilogram for each package and stores it in the respective
cost_weight_ratio field

• Write a function that increases the cost of shipping a package by 10% (This function should also call
the function that calculates the cost per kilogram)

• Write a function that calculates the total cost of shipping all packages

• Write a main function that calls the functions you have written

In this, we have defined the package structure for you. In main(), we have declared an array of 5 packages.
You need to write the functions and call them in main(). You have to carefully choose which function should
be called by value and which should be called by reference. Accordingly, you have to write the function
prototypes. You can optionally write a function to print all packages that call theprint_package()function to
avoid code duplication.
Lesson 1: Pointers in a C program

Chapter 12, Sections 12.1–12.6, 12.9.

12 Pointers

WHAT TO LEARN

Accessing memory for fetching and updating the stored value.


Significance of indirection and dereferencing when applied to a pointer.
Using pointers as function arguments to change variables defined outside the function.
Safety features associated with pointers used as function arguments.
Using function parameters to return multiple values.
Relationship between arrays and pointers.
Special features of pointer arithmetic and its limitations.
Significance of the null and void pointers.
Advantage of using an array of pointers for sorting operations.
The implicit and explicit use of a pointer to a pointer.
Use of const to protect a pointer and its pointed-to value from modification.

12.1 MEMORY ACCESS AND THE POINTER


A program evaluates a variable by accessing its memory location and retrieving the value stored
there. Because this process is hidden in our programs, you may be surprised to know that
a program executable doesn’t contain any variable names at all. Instead, it contains their addresses
as components of instructions. Before the advent of 3GL languages, programmers had to explicitly
specify memory locations for data, while taking care that these locations didn’t conflict with one
another. Today, the same job is done by the compiler.
C permits a partial return to those old days by allowing every legal area of memory to be directly
accessed. (We say “legal” because not every memory location is accessible by our programs.)
The language allows us to obtain the memory location of every variable, array or structure used
in a program. We can then use the address of this location to access—and even change—the value
stored there.
374 Computer Fundamentals & C Programming

But why should one want to indirectly access a variable when one can do that directly? The simple
answer is that sometimes direct access is not possible. Recall that a function copies a variable that is
passed to it as an argument, which means that it can’t directly access the original variable. If access
to individual memory locations is permitted instead, then we can do the following:
Use the address to change any variable defined anywhere—even in places where the variable
is not visible but its address is.
Make a function return multiple values by passing multiple addresses as function arguments.
Access or change the elements and members of bulky objects like arrays and structures without
copying them inside a function. All you need is knowledge of the address of the first element
or member of the object.
A pointer, the most powerful and misunderstood feature of C, easily performs all of the preceding
tasks. Pointers are hidden in arrays and strings even though we have cleverly managed to use these
objects—often without adequate understanding of what we were doing. But it’s in this chapter that
we need to examine the pointer in all its manifestations and understand why C can’t do without
them. C without pointers is like a mechanic without the toolkit.

Note: Two values are associated with any variable—the address of its memory location and the value
stored at the location.

12.2 POINTER BASICS


A pointer is a variable with a difference. It contains, not a simple value, but the address of another
variable or object. So, if a pointer p is assigned the address of an int variable x which is set to 5, then
p = &x; p now points to x

where &x evaluates to the memory address of x. The pointer p must have been declared previously as
int *p; Declares pointer variable p
C offers two unary operators, the & and *, for handling pointers. The & is used to obtain the address
of a variable. The * is used for two purposes and one of them is to declare the pointer. When p is
assigned the address of x, p is said to point to x, which means you can use p to access and change x.
If p is visible at some location of a program but x is not, you can still access x at that location by
dereferencing p to obtain the value at the address pointed to, i.e. x. The expression *p (second use
of *) evaluates to x:
printf(“x = %d\n”, *p); Prints x = 5
While *p represents the value of x, it can also be used on the left-hand side of an assignment to
change the value of x:
*p = 10; x is now 10
Pointers 375

The power of a pointer lies in the preceding statement. The assignment *p = 10 is the same as
x = 10. A pointer thus provides access to two values—the address it stores and the value stored at
the address. The type of this pointer is not int, but pointer to int, which we also refer to as int *.
Even though a pointer has an integer value, it supports a limited form of arithmetic. Adding 1 to
a pointer value of 100 yields 101 when the pointed-to variable is of type char, and 104 when the
pointed-to variable is of type int. Using pointer arithmetic, we can navigate memory and update
the contents at any legal memory location. You can change the value stored in the pointer or the
value pointed to:
p++ Adds 1 to value of pointer
(*p)++ Adds 1 to value pointed to
Like arrays, pointers belong to the category of derived data types. Since every variable has an address,
we can define pointers for every data type, including arrays and structures. Furthermore, nothing
in C prevents us from creating a pointer to another pointer.
Even though a pointer is usually a variable, any expression that evaluates to an address in memory
can be used where a pointer value is expected. Arrays and strings can also be treated as pointers
for most purposes. The name of an array evaluates to the address of the first element, while
a double-quoted string evaluates to the address of the first character of the string.
Note: A pointer has a data type that is aligned with the data type of its dereferenced value.
If a pointer p points to a variable of type int, then the type of p is int * , i.e., pointer to int. It would
also be helpful to think of *p as a value having the type int.

Takeaway: A pointer value can originate from three sources: (i) any variable name prefixed
with the & operator, (ii) the name of an array, and (iii) a string enclosed within double quotes.

12.3 intro2pointers.c: A SIMPLE DEMONSTRATION OF POINTERS


Program 12.1 establishes the minimal assertions that have been made so far about pointers. The
uninitialized value of a pointer, p, having the type int *, is first printed in hex with the %p format
specifier. It is then made to point to an int variable x. Observe that both *p and x evaluate to 420.
But when *p is assigned the value 840, the value of x also changes to 840. This shows that the pointer
can be used to change the value pointed to.

12.4 DECLARING, INITIALIZING AND DEREFERENCING A POINTER


A pointer variable is declared (and automatically defined) before use. Declaration specifies the type
and allocates the necessary memory for the pointer. The following statement declares a pointer
variable named p that can store the address of an int variable:
int *p; p can’t be dereferenced now
By convention, the * is prefixed to the variable, but you can place it anywhere between int and
p (say, int * p or int* p). The pointer p, which now contains an unpredictable value, can be used
376 Computer Fundamentals & C Programming

/* intro2pointers.c: A simple example of a pointer in action. */


#include <stdio.h>
int main(void)
{
int x = 420;
int *p; /* p has type int * */
printf(“Value of x = %d\n”, x);
printf(“Uninitialized value of pointer p = %p\n”, p);
p = &x; /* p stores address of x */
printf(“Initialized value of pointer p = %p\n”, p);
printf(“Value at address pointed to by p = %d\n”, *p);
printf(“This is the same value as x: %d\n”, x);
*p = 840; /* Same as x = 840; */
printf(“New value of x = %d\n”, x);
return 0;
}

PROGRAM 12.1: intro2pointers.c


Value of x = 420
Uninitialized value of pointer p = 0xb7f1df60 %p prints in hex
Initialized value of pointer p = 0xbfa1b63c Address of x
Value at address pointed to by p = 420
This is the same value as x: 420
New value of x = 840

PROGRAM OUTPUT: intro2pointers.c

only after it is made to point to a legal memory location. This can be the address of an int variable
(say, x), which is obtained by using the & operator:
int x = 5;
p = &x; p stores the address of x
The & operator evaluates its operand (x) as an address. Expectedly, we can combine the declaration
and assignment in this manner:
int *p = &x; int* p = &x; is also OK
p has the data type int *, or pointer to int, which simply means that the value pointed to has the type
int. Similarly, a float variable is pointed to by a pointer of type float * or pointer to float. However,
the size of the pointer itself has no relation to the size of the variable pointed to. A pointer has
a fixed size and its value is printed with the %p format specifier of printf.
After a pointer has been assigned a valid address with p = &x;, we can use the concept of indirection
to obtain the value of x from p. The expression *p evaluates to x:
printf(“Value of x is %d\n”, *p); Prints the value 5
Pointers 377

The *, used as a unary operator in dereferencing or indirection, operates in the following sequence:
It evaluates its operand (p) to obtain the stored address (&x).
It then fetches the value (x) stored at that address.
Hence, *p is synonymous with x and can be used wherever x is used. Thus, *p changes x when
used in the following way:
*p = 10; Same as x = 10;
As long as p points to x, any change made to x can be seen by p, and vice versa. This is the most
important feature of a pointer. p thus has two values—a direct (&x) and an indirect one (*p). The
dereferenced value of p can also be assigned to another variable or used in an arithmetic expression:
int y;
y = *p; y is also 10
y = *p + 1; Same as y = x + 1;
Just as x is a variable on the left side and a value on the right side, the same is true of *p.
The relationship between a pointer and the variable it points to is depicted in Figure 12.1.

Value: 1234 p = & Value: 420


x
Address: 2345 Address: 1234
Pointer variable p int variable x

FIGURE 12.1 Pointer and the Variable Pointed to

Note: C needs more operators than the computer keyboard can provide, so sometimes the same
symbol has to take on multiple roles. The *, the operator used in multiplication, is also used for
declaring a pointer (int *p;) and for dereferencing it (int y = *p;).

Note: The statement int *p; can be interpreted in two ways:


(i) p is of type int * (pointer to int).
(ii) *p is of type int.
This flexibility explains the two ways the * is used. Because of (i), many programmers prefer to write the
declaration as int * p; instead of int *p;.

12.5 pointers.c: USING TWO POINTERS


Program 12.2 uses two pointers p and q of type pointer to short (short *), and two short variables
x and y. The first part, where both p and q point to x, shows the effect dereferencing p and q has on
the value of x. The second part demonstrates the effect on *q when p is made to point to y.
378 Computer Fundamentals & C Programming

/* pointers.c: Uses two pointers to point to same and different variables. */


#include <stdio.h>
int main(void)
{
short x = 100, y = 300;
short *p = &x;
short *q = p; /* Both p and q point to x */
printf(“When x = 100, *p = %hd, *q = %hd\n”, *p, *q);
*p = 150; /* Same as x = 150; */
printf(“When *p = 150, *q = %hd, x = %hd\n”, *q, x);
*q = 200; /* Same as x = 200; */
printf(“When *q = 200, *p = %hd, x = %hd\n”, *p, x);
p = &y; /* q now points to y */
printf(“\np now points to y, *p = %hd, y = %hd\n”, *p, y);
printf(“But *q is still %hd\n\n”, *q);
return 0;
}

PROGRAM 12.2: pointers.c


When x = 100, *p = 100, *q = 100
When *p = 150, *q = 150, x = 150
When *q = 200, *p = 200, x = 200
p now points to y, *p = 300, y = 300
But *q is still 200

PROGRAM OUTPUT: pointers.c

At the time of declaration, the address of x is directly assigned to p and indirectly to q. The first
three printf statements show how changing *p or *q also changes x, which means that *p, *q and x
are equivalent in all respects. In the second part, the linkage between p and x is broken by making
p point to y. q is now no longer a copy of p because it continues to point to x.
You can now understand why a pointer needs to have a data type. Without it, you can access
the address it contains all right, but you can’t dereference the pointer. The type short * in the
declaration of p and q indicates that two bytes have to picked up from the address stored in each
pointer and these bytes have to be interpreted as a value of type short.

Takeaway: The assignment p = &x sets up a reciprocal relationship between two variables.
x can be changed by modifying *p, and vice versa. This relationship is non-existent in the
assignment x = y; that involves two ordinary variables. Here, subsequently changing one
variable doesn’t change the other.
Pointers 379

12.6 IMPORTANT ATTRIBUTES OF POINTERS


Because of the sheer power of indirection, pointers are remarkably effective in solving complex
problems. For this reason, they are used almost everywhere except in the most trivial applications.
Admittedly, it takes some time for a beginner to get used to these simple but tricky concepts.
However, adoption of the following features and guidelines will stand you in good stead:
The data type of a pointer is different from the type of the variable it points to. If the latter is of
type char, then the type of a correctly defined pointer is char *, i.e., pointer to char. Also, the
data type of a variable or constant can be transformed into the data type of the pointer using
a cast. For instance, the expression (int *) 32657 transforms an int to a pointer to int.
The size of a pointer is generally fixed and doesn’t depend on the size of the object it points
to. A pointer, being an address, represents an integer value that is wide enough to address
every byte of memory on a machine. Exceptions notwithstanding, a pointer has the same size
as the native word length, which often has the size of an int. But the pointer itself is not an int.
A pointer can be assigned the value 0. The address 0 is also represented by the symbolic constant
NULL, and a pointer that has this value is known as a null pointer. C guarantees that the address
NULL can never point to an object used by the program.
An uninitialized or null pointer must not be dereferenced. An uninitialized pointer contains
a garbage value which may point to a memory location that is not accessible by your program.
Your program may either behave unpredictably or terminate abnormally if such a pointer is
dereferenced.
Adding 1 to a pointer variable doesn’t necessarily add 1 to the value stored by the pointer. The result
depends on the data type of the pointer. If a pointer stores the address 1000, then adding 1 to it
results in 1001 for a pointer of type char *, 1004 for int * and 1008 for float *. This addition
belongs to the realm of pointer arithmetic, which forms the basis of the relationship between
pointers and arrays.
A pointer can snap its current link and point to another variable. This has already been established
by the previous program (Program 12.2). For instance, if a pointer p points to the variable x,
and you are done with x, then you can use the same pointer to point to another variable y
(Figure 12.2).

Value: 420
Address: 1234

Value: 1234/1238
Value: 840
Address: 2345
Address: 1238
Pointer variable int variables

FIGURE 12.2 Pointer to Two Different Variables


380 Computer Fundamentals & C Programming

A pointer variable can be assigned to another pointer variable provided they are of the same type
In the following example, both p and q point to x:
int x = 10;
int *p = &x;
int *q = p;
This has also been established in the preceding program (Program 12.2). Dereferencing
p and q yield the same value x (Fig. 12.3). You can also change the value of x by changing
either *p or *q. This is exactly what happens when a pointer is passed as an argument to
a function.

Value: 1234
p =
Address: 2346 &x Value: 420
Pointer variable p Address: 1234
int variable x
&x
q =
Value: 1234
Address: 2350
Pointer variable q

FIGURE 12.3 Two Pointers to the Same Variable

A pointer can point to another pointer. This property is implicitly exploited in a multi-
dimensional array or an array of pointers to strings. The pointer pointed to can, in turn, point
to another pointer, and so on.

Program 12.3 demonstrates three features of pointers. It uses three ordinary variables of type char,
short and int along with three matching pointer variables. The program eventually crashes without
executing the final printf statement.
(A) Here, sizeof is used to compare the size of the pointer to the size of the variable it points to.
Note that the data type of the pointer doesn’t determine its own size (4 bytes on this Linux
system) even though it determines the size of the dereferenced objects (*p_char, *p_short
and *p_int).
(B) When 1 is added to each of the three pointer variables, the resultant values increase not by 1,
but one unit of the data type. It’s only the value of p_char that advances by 1; p_short and
p_int increase their values by 2 and 4, respectively. Pointer arithmetic will be examined in
Section 12.9.
Pointers 381

/* pointer_attributes.c: Highlights (i) difference between size of pointer and


its dereferenced object, (ii) effect of adding 1 to a pointer,
(iii) effect of dereferencing a null pointer. */
#include <stdio.h>
int main(void)
{
int i = 97; char c = ‘A’; short s = 97;
int *p_int = &i; char *p_char = &c; short *p_short = &s;
/* (A) Size of pointer compared to size of dereferenced object */
printf(“Size of: p_char = %d, *p_char = %d\n”,
sizeof p_char, sizeof *p_char);
printf(“Size of: p_short = %d, *p_short = %d\n”,
sizeof p_short, sizeof *p_short);
printf(“Size of: p_int = %d, *p_int = %d\n”,
sizeof p_int, sizeof *p_int);
/* (B) Adding 1 to a pointer */
printf(“p_char = %p, p_char + 1 = %p\n”, p_char, p_char + 1);
printf(“p_short = %p, p_short + 1 = %p\n”, p_short, p_short + 1);
printf(“p_int = %p, p_int + 1 = %p\n”, p_int, p_int + 1);
/* (C) Dereferencing a null pointer - Program will crash here!!!!!!! */
p_short = NULL; /* Pointer snaps its current link */
printf(“Dereferenced value of a null pointer = %hd\n”, *p_short);
return 0;
}

PROGRAM 12.3: pointer_attributes.c


Size of: p_char = 4, *p_char = 1
Size of: p_short = 4, *p_short = 2
Size of: p_int = 4, *p_int = 4
p_char = 0xbfc22833, p_char + 1 = 0xbfc22834 Increases by 1
p_short = 0xbfc22830, p_short + 1 = 0xbfc22832 Increases by 2
p_int = 0xbfc22834, p_int + 1 = 0xbfc22838 Increases by 4
Segmentation fault Linux
pointer_attributes.exe has encountered a problem and needs to close. Visual Studio

PROGRAM OUTPUT: pointer_attributes.c


(C) The pointer variable, p_short, is unhooked from the variable s and assigned the value NULL.
The program terminated prematurely when this pointer was dereferenced, and without
executing the final printf statement. GCC (Linux) and Visual Studio output different messages
(both shown in output), but the meaning is clear: you cannot dereference a null pointer.
Caution: It is a common mistake to dereference a pointer that doesn’t point to an object or an
accessible memory location. For instance, the following sequence may not update the contents
at the address stored in p, but instead may cause the program to crash:
382 Computer Fundamentals & C Programming

int *p, x;
*p = 1; Should have done p = &x; first
An uninitialized pointer points to “somewhere in memory,” which can be an illegal memory location.
Assign the pointer an address of an object before you dereference the pointer.

12.7 POINTERS AND FUNCTIONS


The preceding programs demonstrated the basic features of pointers and highlighted the importance
of using them safely. But they comprised manufactured situations that actually provided no
justification for using pointers at all. It’s only when you use functions that you realize that C needs
pointers for the following reasons:
Functions pass their arguments by value. Because the values of arguments are copied to function
parameters, these copies can’t be used to change the originals.
A function uses the return statement to return a single value. It can’t return multiple values
using this mechanism.
We can overcome both hurdles by using pointers as function arguments. Let’s overcome the first
hurdle first. Program 12.4 passes the address of a variable x to the change_x function which uses
this address to double the existing value of x. The function is invoked twice to return the changed
value of x in two different ways. Observe the declaration of the change_x function; it uses a pointer
to short as an argument.

/* change_x.c: Uses a pointer as an argument to a function


to change a variable defined in main. */
#include <stdio.h>
short change_x(short *fx);
int main(void)
{
short x = 100;
short *px = &x;
/* Return value ... */
change_x(px); /* ... not saved here ... */
printf(“New value of x: %hd, %hd\n”, *px, x);
printf(“New value of x: %hd\n”, change_x(px)); /* ... but captured here */
return 0;
}
short change_x(short *fx)
{
*fx *= 2;
return *fx;
}

PROGRAM 12.4: change_x.c


Pointers 391

The first loop prints the values of the first four elements using pointer notation. After arr[2] and
arr[3] are reassigned, the second for loop displays the array elements using a pointer variable p
with array notation. p is finally made to point to arr[3], the last element. The “array” p must now
be used with a negative index to access the previous elements of arr.

12.9 OPERATIONS ON POINTERS


Apart from dereferencing with the *, there are a number of operations that can be performed on
pointers. All of the operations described in the following sections apply to pointer variables, and some
to pointers representing arrays (constant pointers). We consider here three types of operations—
assignment, arithmetic and comparison, using a pointer p that points to int.

12.9.1 Assignment
A pointer can be assigned the address of a variable (p = &c;) or the name of an array (p = arr;).
It can also be assigned the value of another pointer variable of the same type. For instance, if both
p and q are of type int *, then p can be assigned to q, and vice versa:
int c = 5; int *p, *q;
p = &c;
q = p;
It is possible to assign an integer, suitably cast to the type of the pointer, to a pointer variable using,
say, p = (int *) 128;. Unless this address points to an existing object, it makes no sense to make
this assignment.
However, you can, and often will, assign the value 0 to a pointer. This value has a synonym in the
symbolic constant, NULL. You can assign either 0 or NULL to a pointer of any type without using a cast:
int *p = 0;
int *q = NULL;
char *s = NULL;
After these assignments, p, q and s become null pointers. We’ll discuss the significance of NULL and
the null pointer soon.

12.9.2 Pointer Arithmetic Using + and -


The domain of pointer arithmetic is small and simple. Among the basic arithmetic operators,
only the + and - can be used with pointers. An integer may be added to or subtracted from a pointer.
The resultant increase or decrease occurs in terms of storage units, so p + 2 advances the pointer by
two storage units (12.8.1). For an array, this means skipping two elements. The following operations
on &x and an array element are also permitted:
&x - 2 Address reduced by 2 storage units
&arr[3] + 3 This is &arr[6]
392 Computer Fundamentals & C Programming

Addition of two pointers doesn’t make sense, but subtraction between two pointers is valid
provided both pointers refer to elements of the same array. The result signifies how far apart the
elements are. Thus, the following expression
&arr[6] - &arr[3]

evaluates to 3, the difference between the subscripts of the array. In the general case, &arr[m] - &arr[n]
evaluates to m - n irrespective of the data type of the array.

12.9.3 Pointer Arithmetic Using ++ and --


A pointer variable can also be used with the increment and decrement operators. Because ++ and
-- have the side effect of changing their operand, they can’t be used with the name of the array
which is a constant pointer. The following expressions are not permitted for the array arr:
arr++; Not permitted
arr--; Ditto
But if p points to arr, the expressions p++ or --p are legal. p++ increases the pointer to point to the
next array element and p-- decrements it to point to the previous element. If p points to an int
variable, each ++ or -- operation changes the numerical value of the pointer by 4 bytes.
The expressions p++ and p-- are often combined with the * for dereferencing the pointer. Expressions
like *p++ and ++*p are commonly used in programs to evaluate the value pointed to by p, before
or after the incrementing operation. Using the precedence table (Table 12.2) for these operators
as reference, make sure you are not changing the value pointed to when you mean to change the
value of the pointer. Consider the following expressions involving * and the ++ operator:

int arr[ ] = {10, 20, 25, 35}; int *p = arr; (i.e., *p is 10)
Expression Signi cance Value of *p
*++p Increments p before evaluating *p 20
++*p Increments value of *p 21
*p++ Increments p before evaluating *p 25
(*p)++ Increments value of *p 26

When the expression in the first example is evaluated, p advances from arr[0] to point to arr[1]
before indirection is applied. p advances again in the third example to point to arr[2]. In the
second and fourth examples, it’s the value of *p that is incremented and not the pointer. The last
example needs parentheses to change the default order of evaluation. Keep in mind that the postfix
operators bind more tightly than the prefix ones, which have the same precedence as the * and &.

Tip: If you need to step through an array arr using the ++ and -- operators, simply assign the
array to a pointer variable p (p = arr;), and then use p++ or p--.
Pointers 393

TABLE 12.2 Precedence and Associativity of Pointer and Arithmetic Operators


Operators Description Associativity
++ -- Postfix increment/decrement L-R
++ -- Prefix increment/decrement R-L
* & Dereference/Address R-L
* / % Arithmetic L-R
+ - Arithmetic L-R

12.9.4 Comparing Two Pointers


Finally, two pointers can be compared only if they point to members of the same array. There are
two exceptions though. First, any pointer can be compared to the null pointer (p == NULL) that is
often returned by a function when an error condition is encountered. Also, a pointer of any type
can be compared to a generic or void pointer. This pointer type is discussed in Section 12.17.

12.10 max_min.c: USING scanf AND printf WITH POINTER NOTATION


How does one use pointer notation to populate an array arr withscanf and display each element
with printf? One way is to use the expression (arr + n) to step through the array where the variable
n creates the required offset. We can then dereference the same expression to obtain the value.
The following code segment achieves this task:
short n = 0, arr[20];
while (scanf(“%hd”, arr + n) != EOF)
n++;
for (n--; n >= 0; n--)
printf(“%hd “, *(arr + n)); Prints in reverse order
It would be more convenient, however, to scan the array with the ++ and -- operators. We can’t
obviously use arr++, so let’s use a pointer variable p, make it point to arr, and then use p++ or p--
to access each element. Program 12.8 uses this technique to print the maximum and minimum
number found in an array.
In this program, scanf uses the pointer p as its second argument, while printf displays the value
of *p. The counter n keeps track of the number of items read, and the while loop uses this value to
determine the number of items to be printed in reverse order. The standard technique of determining
the maximum and minimum values is to first initialize max and min to the first or last element of
the array. After every iteration, max and min are compared to *p and reassigned if required.

12.11 NULL AND THE NULL POINTER


Sometimes we need to make a pointer variable point to “nowhere,” i.e., not to an existing object.
For this purpose, C supports a value that is “guaranteed to compare unequal to a pointer to any
object or function.” The expression &x cannot be guaranteed to yield such a value, so C allows the
value 0 or the symbolic constant NULL to be assigned to a pointer:
466 Computer Fundamentals & C Programming

/* swap_success2.c: Successfully swaps two variables using a structure


and without using a pointer. */
#include <stdio.h>
struct two_var {
Lesson 2: Arrays and Structures with Pointers
short x;
Chaptershort
14, Section
y; 14.9.
} s = { 1, 10};
struct two_var swap(struct two_var);
int main(void)
{
printf(“Before swap: s.x = %hd, s.y = %hd\n”, s.x, s.y);
s = swap(s); /* The game changer */
printf(“After swap: s.x = %hd, s.y = %hd\n”, s.x, s.y);
return 0;
}
struct two_var swap(struct two_var z)
{
short temp;
temp = z.x; z.x = z.y; z.y = temp;
return z;
}

PROGRAM 14.9: swap_success2.c


Before swap: s.x = 1, s.y = 10
After swap: s.x = 10, s.y = 1

PROGRAM OUTPUT: swap_success2.c

Takeaway: The assignment property (=) of structures, because of which one structure variable
can be assigned to another, allows a function to change the original values of structure members
using their copies.

14.9 POINTERS TO STRUCTURES


Pointers represent the ultimate access mechanism of structure members. As with the other data
types, a pointer to a structure must be defined before it is pointed to a structure variable. These steps
are shown in the following statements:
struct rectangle {
short length;
short width;
} rect1 = {10, 20};

struct rectangle *p; Creates pointer p


p = &rect1; p points to rect1
User-Defined Data Types 467

The structure members can be accessed in three ways; two of them use a pointer with standard
and special notation. For instance, length can be accessed as
rect1.length The standard access mechanism that doesn’t use a pointer.
(*p).length Pointer dereferenced with * and then connected to the member.
p->length Uses a special operator, ->, that works only with structures.
Pointers are so often used with structures that C supports a special operator, ->, to access a structure
member in a convenient manner. This symbol is formed by combining the hyphen with the > symbol.
Once a structure has been pointed to, you can access and manipulate a pointed-to member in the
following ways:
Using (*p).length Using p->length
(*p).length = 100; p->length = 30;
(*p).length++; p->length++;
scanf(“%hd”, &(*p).length); scanf(“%hd”, &p->length);
Form 1 Form 2
The technique shown in Form 1 encloses the *, the standard dereference symbol, in parentheses
because the dot has a higher precedence than the *. In most cases, we’ll use the -> operator rather
than the dot operator with the *. The -> has the same priority and associativity (L-R) as the dot.
Arithmetic works in the usual manner for pointers to structures. Unlike an array, the name of
a structure doesn’t represent a pointer, so we can’t use expressions like rect1 + 1 or rect1++.
However, if we point a compatible pointer p to an array of structures, p++ advances the pointer to the
next array element, i.e., the next structure. This property will be used in a project that follows soon.

14.9.1 pointer_to_structure.c: Accessing Structure Members


Program 14.10 uses a pointer p to access the members of a three-member structure named employee.
After p is made to point to the structure variable emp, the members of emp are displayed using
a mix of all the three access mechanisms discussed previously. Two of these mechanisms (using
the * and ->) are subsequently used to reset the values.

/* pointer2structure.c: Demonstrates multiple techniques of accessing


structure members using a pointer. */
#include <stdio.h>
#include <string.h>

int main(void)
{
struct employee {
short id;
char name[30];
int pay;
} emp = {1024, “Steve Wozniak”, 10000}, *p;
468 Computer Fundamentals & C Programming

p = &emp;
printf(“Emp-id: %hd, Name: %s, Pay: %d\n”, emp.id, (*p).name, p->pay);
(*p).id = 4201; /* Updates id */
strcpy(p->name, “Steve Jobs”); /* Updates name */
p->pay = 20000; /* Updates pay */
printf(“Emp-id: %hd, Name: %s, Pay: %d\n”, p->id, p->name, p->pay);
return 0;
}

PROGRAM 14.10: pointer2structure.c


Emp-id: 1024, Name: Steve Wozniak, Pay: 10000
Emp-id: 4201, Name: Steve Jobs, Pay: 20000

PROGRAM OUTPUT: pointer2structure.c

14.9.2 update_pay.c: Using a Pointer as a Function Argument


We passed one or more structures as function arguments to swap two structure members
(Program 14.9) and compute the difference between two times (Program 14.8). This pass-by-value
mechanism is safe; it protects the original structure from modification by the function. However,
large structures can consume a lot of memory, so we may need to pass a pointer to a structure
instead of the entire structure.
Program 14.11 uses the update_pay function which accepts a pointer to the structure variable emp
and the revised value of pay as its two arguments. The function parameter, f_emp, is a copy of
&emp, so f_emp->pay represents the value at the memory location of emp.pay. Updating a member’s
value using a pointer represents a memory-efficient technique because the function copies not the
structure (36 bytes) but its pointer (4 bytes).

/* update_pay.c: Uses a pointer to a structure as a function argument


to update the structure. */
#include <stdio.h>
struct employee {
short id;
char name[30];
int pay;
} emp = {1024, “Steve Wozniak”, 10000};
void update_pay(struct employee *f_emp, int f_pay);
int main(void)
{
printf(“Old pay = %d\n”, emp.pay);
update_pay(&emp, 20000);
printf(“New pay = %d\n”, emp.pay);
return 0;
}
User-Defined Data Types 469

void update_pay(struct employee *f_emp, int f_pay)


{
f_emp->pay = f_pay; /* Updates member pay of emp */
return;
}

PROGRAM 14.11: update_pay.c


Old pay = 10000
New pay = 20000

PROGRAM OUTPUT: update_pay.c

14.9.3 time_addition.c: Using Pointers as Function Arguments


Program 14.12 features the time_add function which uses pointers to a structure of type time as its
three arguments. The function adds the times represented by the dereferenced value of the first two
arguments and saves the result in the third argument. The first two arguments have been declared
with the const qualifier to protect them from inadvertent modification by the function.

/* time_addition.c: Uses pointers to a structure as function arguments


to compute the sum of two times. */
#include <stdio.h>

typedef struct time {


short hours;
short mins;
short secs;
} TIME;

void time_add(const TIME *, const TIME *, TIME *);

int main(void)
{
TIME t1, t2, t3;

fputs(“Enter the two times in hh:mm:ss format: “, stdout);


scanf(“%hd:%hd:%hd %hd:%hd:%hd”,
&t1.hours, &t1.mins, &t1.secs, &t2.hours, &t2.mins, &t2.secs);

time_add(&t1, &t2, &t3);


printf(“Sum of the two times: %hd hours, %hd minutes, %hd seconds\n”,
t3.hours, t3.mins, t3.secs);
return 0;
}
470 Computer Fundamentals & C Programming

void time_add(const TIME *t1, const TIME *t2, TIME *t3)


{
short temp, quot;
temp = t1->secs + t2->secs; quot = temp / 60;
t3->secs = temp % 60;
temp = t1->mins + t2->mins + quot; quot = temp / 60;
t3->mins = temp % 60;
t3->hours = t1->hours + t2->hours + quot;
return;
}

PROGRAM 14.12: time_addition.c


Enter the two times in hh:mm:ss format: 6:40:30 8:30:50
Sum of the two times: 15 hours, 11 minutes, 20 seconds
PROGRAM OUTPUT: time_addition.c

14.10 student_management.c: A PROJECT


Let’s complete our study of structures by examining a small project involving a student database.
Program 14.13 (shown here in two sections with ‘a’ and ‘b’ suffixes) uses three functions to add
to, display and query an array of structures of type STUDENT. The array, which can hold data for
up to 50 students, is partially populated by user input, followed by display of the data. Finally,
the program queries the database for a specific roll number. The two program sections must be
concatenated before use.
/* student_management.c: Manages student details using pointers
to structures as function arguments. */
#include <stdio.h>
#define COLUMNS 50
#define FLUSH_BUFFER while (getchar() != ‘\n’) ;

typedef struct {
char name[20];
int roll_no; /* Doesn’t conflict with variable of same name */
short marks[4];
} STUDENT;
void add_student(STUDENT f_stud[ ]);
float display_marks(STUDENT f_stud[ ]);
STUDENT *search_student(STUDENT f_stud[ ], int f_roll_no);

int main(void)
{
int roll_no; float average;
STUDENT stud[COLUMNS] = { {“”, 0, {0, 0, 0}} }; /* Note the braces */
STUDENT *q;
Lesson 3: Call by Reference and Null Pointers
382 Computer Fundamentals & C Programming

int *p, x;
*p = 1; Should have done p = &x; first
Chapter
An 12, Sections
uninitialized 12.7,
pointer points to 12.11
“somewhere in memory,” which can be an illegal memory location.
Assign the pointer an address of an object before you dereference the pointer.

12.7 POINTERS AND FUNCTIONS


The preceding programs demonstrated the basic features of pointers and highlighted the importance
of using them safely. But they comprised manufactured situations that actually provided no
justification for using pointers at all. It’s only when you use functions that you realize that C needs
pointers for the following reasons:
Functions pass their arguments by value. Because the values of arguments are copied to function
parameters, these copies can’t be used to change the originals.
A function uses the return statement to return a single value. It can’t return multiple values
using this mechanism.
We can overcome both hurdles by using pointers as function arguments. Let’s overcome the first
hurdle first. Program 12.4 passes the address of a variable x to the change_x function which uses
this address to double the existing value of x. The function is invoked twice to return the changed
value of x in two different ways. Observe the declaration of the change_x function; it uses a pointer
to short as an argument.

/* change_x.c: Uses a pointer as an argument to a function


to change a variable defined in main. */
#include <stdio.h>
short change_x(short *fx);
int main(void)
{
short x = 100;
short *px = &x;
/* Return value ... */
change_x(px); /* ... not saved here ... */
printf(“New value of x: %hd, %hd\n”, *px, x);
printf(“New value of x: %hd\n”, change_x(px)); /* ... but captured here */
return 0;
}
short change_x(short *fx)
{
*fx *= 2;
return *fx;
}

PROGRAM 12.4: change_x.c


Pointers 383

New value of x: 200, 200


New value of x: 400

PROGRAM OUTPUT: change_x.c


The pass-by-value mechanism (11.5.2) applies here in the usual manner. The pointer px, which
points to x, is copied to the parameter fx. It’s true that fx can’t change px, but that doesn’t concern
us here. We want fx to change x instead. Because both px and fx point to x, changing *fx inside the
function updates x defined in main. In other words, x has been changed through a copy of the pointer.
x is changed yet again in the program, but this time through the return value of the function.
So which route should one adopt? And, do we really need to use px instead of &x? We’ll answer
this question soon. But now that we have discovered a new face for the pass-by-value principle,
we need to exploit the same principle in a function to return multiple values.

12.7.1 swap_success.c: Making the swap Function Work


The concept of passing arguments by value hit a roadblock in the program, swap_failure.c
(Program 11.3), which failed to swap the contents of two variables passed as arguments to the
swap function. That version of the function swapped, not the original arguments, but their copies.
Program 12.5 solves the swapping problem using addresses of variables as arguments.

/* swap_success.c: Swaps contents of two variables using a function that


accepts pointers to these variables as arguments. */
#include <stdio.h>
void swap(short *fx, short *fy);
int main(void)
{
short x = 1, y = 100; /* Need to swap these two values */
short *px = &x, *py = &y;
printf(“In main before swap : x = %3hd, y = %3hd\n”, x, y);
swap(px, py);
printf(“In main after first swap : x = %3hd, y = %3hd\n”, x, y);
/* Don’t really need to use px and py; &x and &y will do. */
swap(&x, &y);
printf(“In main after second swap: x = %3hd, y = %3hd\n”, x, y);
return 0;
}
void swap(short *fx, short *fy) /* fx and fy are pointers */
{
short temp = *fx; /* Saves original value of x */
*fx = *fy; /* Copies y to x in main */
*fy = temp; /* Copies original value of x to y in main */
return;
}
PROGRAM 12.5: swap_success.c
384 Computer Fundamentals & C Programming

In main before swap : x = 1, y = 100


In main after first swap : x = 100, y = 1
In main after second swap: x = 1, y = 100

PROGRAM OUTPUT: swap_success.c


The first invocation of swap uses two pointer variables, px and py, that point to x and y, respectively.
As both px and fx contain the address of x, changing *fx updates x and changing *fy updates y.
The power of pointers shows up in this statement:
*fx = *fy;
The statement says this: Fetch the value from the address stored in fy and assign it to the address stored
in fx. This action effectively assigns the value of y to x outside the function. The swap function
works this time because it uses pointers as function arguments to change the value of variables
declared outside the function. This shows the power of indirection!
The first invocation of swap used two pointer variables, px and py, as arguments. But swap simply
needs two addresses having the proper data type. We thus don’t need to define the pointers,
px and py, at all. The second call to swap passes the addresses of x and y to reverse the effect of the
previous swap operation.
Note: Because the swap function is passed the address (also called reference) of two variables,
there’s a view that C can also use pass-by-reference, or at least, create the “effect” of pass-by-
reference (11.5.2). In reality, it’s the programmer, and not C, who creates this effect by using an address
as a function argument and dereferencing its copy inside the function. Make no mistake: C uses
pass-by-value at all times, including when using pointers.

Takeaway: For a function that uses a regular variable or expression as an argument, there is
a one-way flow of data from the argument to the parameter. But when the argument is
a pointer and the function changes its pointed value, the changed value is transmitted
from the parameter to the argument. This represents a two-way communication using the
argument-parameter route.

12.7.2 Using Pointers to Return Multiple Values


Let’s now closely examine the two functions that we last used. The change_x function (in change_x.c)
returned one value (*fx), but it also made the same value available in the parameter. On the other
hand, the swap function (swap_success.c) returned nothing (Fig. 12.4). But if swap could change two
variables x and y that are defined outside the function, is it not true that, in a sense, swap “returns”
two values? Do we compulsorily need to use return to return values?
There is merit in this argument. Pointer variables used as function arguments act like containers
which the function uses to “deposit” data. change_x uses one container while swap uses two.
But change_x also offers an extra option (the return route) that we need to take note of. Now let’s
consider the following scanf statement taken from Program 11.8:
scanf(“%hd%hd%hd%hd”, &hours1, &mins1, &hours2, &mins2);
Pointers 385

short change_x(short *fx) short x = 1, y = 100;


{ swap(&x, &y);
*fx *= 2;
return *fx;
}

FIGURE 12.4 Segments from change_x.c and swap_success.c

Here, scanf “returns” five values—one through its normal return route (which is not captured here),
and the other four through the pointers used as arguments. In that case, if we design a function
to return three values, should one of them use the return route and the other two use pointers as
arguments? Before we answer that question, we need to take up a program that uses this mechanism
of using multiple pointers as arguments to a function.

Takeaway: When pointers are used as function arguments, the term “return” acquires a wider
meaning. Apart from its usual meaning (the value passed by the return statement), the term
also refers to the values placed in the addresses passed as arguments to the function.

12.7.3 sphere_calc.c: Function “Returning” Multiple Values


Program 12.6 takes the radius of a sphere as input and computes its total surface area and volume.
Both values are “returned” through two pointers used as arguments to a function. This time we
use the pow function of the math library to compute two expressions.
The sphere_area_vol function requires three arguments. The first argument (radius) provides
the input. The other two are addresses of area and vol, two variables defined in main. These addresses
are copied to the parameters f_area and f_vol inside the function. When the function computes
*f_area, it updates the value at the address represented by &area. This is the variable area itself.
Similarly, *f_vol updates vol. In this way, the function returns two values using the pointer route.
Note: The first argument in this program provides for one-way transmission of information. But the
remaining arguments (being pointers) are used for two-way communication.

Would it have made sense to return one of these values, say, area, through the return statement?
The function would then need two, and not three, arguments. Nothing stops us from doing that
but the answer would actually depend on the requirements of the application. Generally, a function
uses the pointer-argument route when at least two values have to be returned. The return statement
is then used to perform error checking. This is what we have done with our sphere_area_vol
function of the preceding program.
Note: The program must be compiled with the -lm option when using the gcc command.
This option directs the linker to obtain the code for pow from the math library. Visual Studio performs
the task of linking automatically.
386 Computer Fundamentals & C Programming

/* sphere_calc.c: Uses a function that returns two values using pointers.


Program needs linking with math library. */
#include <stdio.h>
#include <math.h> /* Required by pow function */
#define PI 3.142
short sphere_area_vol(float f_radius, double *f_area, double *f_vol);
int main(void)
{
float radius;
double area, vol;
printf(“Enter radius of sphere: “);
scanf(“%f”, &radius);
if (sphere_area_vol(radius, &area, &vol)) {
printf(“Radius = %.4f, surface area = %.2f, volume = %.2f\n”,
radius, area, vol);
return 0;
}
else {
printf(“Illegal radius: %.4f\n”, radius);
return 1;
}
}
short sphere_area_vol(float f_radius, double *f_area, double *f_vol)
{
if (f_radius <= 0)
return 0;
*f_area = 4 * PI * pow(f_radius, 2); /* Include math.h for pow */
*f_vol = 4.0 / 3 * PI * pow(f_radius, 3);
return 1;
}

PROGRAM 12.6: sphere_calc.c


Enter radius of sphere: -4
Illegal radius: -4.0000
Enter radius of sphere: 2
Radius = 2.0000, surface area = 50.27, volume = 33.51

PROGRAM OUTPUT: sphere_calc.c

12.7.4 Returning a Pointer


A function can also return a pointer using the return statement. The usual caveat applies;
the returned address can’t point to a data object that is locally declared in the function. A pointer
as a returned value finds wide application when handling arrays and strings, and specially when
dynamically allocating memory. We’ll come across one instance of returning a pointer later in the
chapter and some more in Chapter 16.
Pointers 393

TABLE 12.2 Precedence and Associativity of Pointer and Arithmetic Operators


Operators Description Associativity
++ -- Postfix increment/decrement L-R
++ -- Prefix increment/decrement R-L
* & Dereference/Address R-L
* / % Arithmetic L-R
+ - Arithmetic L-R

12.9.4 Comparing Two Pointers


Finally, two pointers can be compared only if they point to members of the same array. There are
two exceptions though. First, any pointer can be compared to the null pointer (p == NULL) that is
often returned by a function when an error condition is encountered. Also, a pointer of any type
can be compared to a generic or void pointer. This pointer type is discussed in Section 12.17.

12.10 max_min.c: USING scanf AND printf WITH POINTER NOTATION


How does one use pointer notation to populate an array arr withscanf and display each element
with printf? One way is to use the expression (arr + n) to step through the array where the variable
n creates the required offset. We can then dereference the same expression to obtain the value.
The following code segment achieves this task:
short n = 0, arr[20];
while (scanf(“%hd”, arr + n) != EOF)
n++;
for (n--; n >= 0; n--)
printf(“%hd “, *(arr + n)); Prints in reverse order
It would be more convenient, however, to scan the array with the ++ and -- operators. We can’t
obviously use arr++, so let’s use a pointer variable p, make it point to arr, and then use p++ or p--
to access each element. Program 12.8 uses this technique to print the maximum and minimum
number found in an array.
In this program, scanf uses the pointer p as its second argument, while printf displays the value
of *p. The counter n keeps track of the number of items read, and the while loop uses this value to
determine the number of items to be printed in reverse order. The standard technique of determining
the maximum and minimum values is to first initialize max and min to the first or last element of
the array. After every iteration, max and min are compared to *p and reassigned if required.

12.11 NULL AND THE NULL POINTER


Sometimes we need to make a pointer variable point to “nowhere,” i.e., not to an existing object.
For this purpose, C supports a value that is “guaranteed to compare unequal to a pointer to any
object or function.” The expression &x cannot be guaranteed to yield such a value, so C allows the
value 0 or the symbolic constant NULL to be assigned to a pointer:
394 Computer Fundamentals & C Programming

/* max_min.c: Uses scanf and printf with pointer notation.


Also prints maximum and minimum numbers. */
#include <stdio.h>
int main(void)
{
short n = 0, arr[20], max = 0, min = 0;
short *p = arr;
printf(“Key in some integers and press EOF: “);
while (scanf(“%hd”, p) != EOF) {
n++; p++;
} /* p moves beyond last element ... */
p--; n--; max = min = *p; /* ... so has to be brought back */
while (n-- >= 0 ) {
printf(“%hd “, *p); /* Prints in reverse order */
if (*p > max)
max = *p;
else if (*p < min)
min = *p;
p--;
}
printf(“\nMax = %hd, Min = %hd\n”, max, min);
return 0;
}

PROGRAM 12.8: max_min.c


Key in some integers and press EOF: 33 99 55 22 66 44 11 0 -5 77
77 -5 0 11 44 66 22 55 99 33
Max = 99, Min = -5

PROGRAM OUTPUT: max_min.c

int *p = 0;
int *p = NULL;
C guarantees that no object can have the address 0 or NULL. This constant NULL is defined in many
header files including stdio.h. Any pointer which is initialized to 0 or NULL is called a null pointer.
An uninitialized pointer is not a null pointer because it points “somewhere,” and, purely by chance,
could have the address of an existing object.
Why do we need a null pointer? One reason is that many functions that return a pointer also need
to indicate an error condition when it occurs. These functions can then return NULL to indicate
failure. For instance, some of the functions of the standard library (like malloc and strstr) return
NULL if the desired objective isn’t achieved. This is like getchar returning EOF when it encounters
an error.
Pointers 395

By default, C doesn’t initialize pointers to NULL, so you may have to make this check if necessary.
Sometimes, this check is mandatory because dereferencing a null pointer will abort a program.
Note that NULL or 0, when used in a pointer context, doesn’t need a cast:
int *p = NULL; Initialize p to NULL
... After processing ...
if (p != NULL) ... check whether p is still null
printf(“%d\n”, *p);
Do we use 0 or NULL? It really doesn’t matter because the preprocessor converts NULL to 0 anyway.
Using NULL instead of 0 is only a “stylistic convention,” a gentle way of reminding a programmer
that a pointer is being used. However, this is not the case when a pointer is used as argument to
a function that takes a variable number of arguments. A value of 0 could be interpreted as the
integer 0, so for getting a pointer to int, use (int *) 0 instead of 0. Better still would be to use NULL.

Takeaway: The address 0 or NULL can never be used by an existing object. Also, a pointer can
be assigned the value 0 without using a cast. However, a cast is required when passing the
pointer value 0 as an argument to a function that takes a variable number of arguments.

12.12 POINTERS, ARRAYS AND FUNCTIONS REVISITED


Pointers, arrays and functions are closely intertwined. This relationship needs to be understood
fully to guard against possible pitfalls. Chapter 13 features several examples where array names have
been passed as function arguments and array notation used inside the function body. We’ll now
examine the use of pointer notation and understand whether we can have a mix of both notations.

12.12.1 Pointers in Lieu of Array as Function Argument


Before we consider replacing the array name by a pointer in a function argument, we need to
understand the following:
How the array name is interpreted by the function.
Why the size of the array is also passed as an argument.
How the [ ] in the function parameter is interpreted by the compiler.
Program 12.9 features the update_array function which updates each element of an array to twice
its initialized value. When the function is invoked, its first argument, arr, is copied as usual to its
corresponding parameter, f_arr[ ], inside the function. The function treats f_arr[ ] as an array
and initializes all of its “elements.” But is f_arr actually an array? The operations involving the
use of the sizeof operator provide the answer.
When update_array is called, &arr[0] is copied to f_arr. Even though the function signature shows
f_arr[ ], this does not imply that f_arr is an array. (In fact, it is not.) f_arr is merely a copy of the
pointer signified by arr. Because f_arr = arr, f_arr can be treated as an array. Thus, updating f_arr
with pointer notation actually updates arr.
Dynamic Memory Allocation
In this reading, you will learn about dynamically allocating memory and dynamic memory management.
You will also learn about two different memory partitions in C, the stack, and the heap. Then, you will gain
insight into the different functions provided in the C libraries for dynamic memory management. Finally,
you will be introduced to the differences between static and dynamic arrays and how to return arrays from
functions.

Main Reading Section:

Stack and Heap

Let us say we have written a C program P1. You would typically save it as a .c file, say P1.c. This file resides
on your disk. When you compile the program with a compiler such as gcc, the executable file is created and
stored on the disk, say P1.exe. Now, when you try to execute this file, the OS first loads the executable file
into the RAM, and then the processor executes the program sequentially. This is illustrated in Figure 1.

Figure 1. A program executing in memory

Now, the memory allocated to the program on the RAM consists of various sections. These are partitioned
in the form of segments. A C program executing in memory has four segments, i.e., Stack, Heap, Data, and
Text, as illustrated in Figure 2.

Figure 2. Segments of an executing C program


Stack Memory

The stack segment is the place where all the auto (or local) variables in our functions are allocated memory.
Each function call gets allocated a frame and this frame contains all the local variables of that function.
When the function reaches the end of its execution and the flow returns to the calling function, the frame is
popped off the stack. In other words, the frame is destroyed, and the space occupied by it is released to the
Operating System to be used for other purposes.

A stack can be thought of as a stack of files that need to be attended to. Each new function call places a new
file on the stack and the height of the stack grows. After the file is attended to by the concerned individual
(or function executed by the CPU), the file is removed from the stack releasing space on the pile. At this
point, the CPU does not remember the contents of the popped file (variables of the popped function call
frame) anymore. They are lost.

Let us understand this through a concrete example. Here is a program:

When the above program starts to execute, a frame gets allocated in the stack for the main() function. This
is illustrated in Figure 3. This frame contains the local variable b. When the call to f1() is made, a frame gets
allocated for f1(). This frame contains, among other information, the local variable a. After f1() completes
its execution, when the control goes back to main, the frame for f1() is popped off the stack. When main()
ends, the frame for main() is also popped.

Figure 3. Illustration of the stack frames being allocated and popped as the program is executed

Heap Memory

Heap refers to that portion of memory that is dynamically and explicitly allocated by the programmer. Here,
since the compiler does not allocate memory by itself, the responsibility of freeing up the allocated memory
also lies with the user. We shall learn how to allocate this memory in the sections ahead. This memory is
accessible globally to all functions. For example, if you dynamically allocate 10 bytes of memory inside f1()
in the above code, that memory will not be freed up when the call stack for f1() is popped. It will persist
until the end of the program execution unless freed explicitly.

Dynamic Memory Allocation

We saw that when we declare a local variable in a function, it is lost when the stack frame is popped.
However, if needed, it can be returned to the calling statement via the return statement. Now, consider the
case of arrays. If the array is declared in a function, and we try to return it, it would be returned by reference.
Thus, the calling function would receive a pointer to a location that is no longer reserved for the array and
is now actually free. These location contents can be filled by some other data unpredictably and would lead
to unpredictable behavior.

Another issue with arrays is that their size cannot be altered at run-time. So, we tend to oversize arrays to
accommodate our needs. This has two issues. First, it is not memory efficient as we would ideally want to
allocate exactly the amount of memory that we use. Second, it may be difficult to exactly estimate how
much oversizing would be sufficient. These and many more challenges can be solved by being able to
dynamically allocate and deallocate memory. Here, the term dynamic means the allocation is being done
at run-time, as opposed to static allocation where the memory requirements are known at compile-time.

This feature is known as Dynamic Memory Management.

Dynamic Memory Management provides immense flexibility to the programmer to allocate and use memory
as they wish. However, this comes with the caveat that the programmer needs to ensure that unused
allocated memory is also freed on time. C provides many functions to achieve Dynamic Memory
Management such as malloc(), calloc(), realloc(), and free(). We shall now see the working of these
functions in detail. These functions are included in the stdlib.h header file.

malloc()

malloc() is a function that accepts an integer number of bytes, allocates that amount of memory, and
returns a void pointer to the memory block of the required size. This allocated memory is present in the
heap.

The void pointer is a special type of pointer, which is used since the function does not know the type of data
that would be stored in the allocated memory. So, it is kept as void * by default and can be type-cast into
any type of pointer as per the need. This returned value would typically be stored in a local pointer so that
the newly allocated memory is accessible.

For example, consider Figure 4.


Figure 4. Syntax and semantics of malloc()

Here, we have requested malloc to allocate sizeof(int) number of bytes. The returned pointer (of type void
*) is cast into a pointer to an int (int *) and assigned to the int pointer pt.

Note that if malloc is not able to allocate the required amount of memory, it allocates a NULL pointer. Thus,
it is a good practice to always check the pointer returned by malloc() against a NULL pointer to check
whether the allocation was successful.

The allocated memory need not always be exactly the size of the data type that would be stored there. We
can request an integer multiple of it to simulate an array as well.

For example, consider the following program:


By passing the argument 10 * sizeof(int) to malloc, p becomes a pointer pointing to a dynamically
allocated memory block that can hold 10 integers. If the OS could not allocate the requested amount of
memory, it would return a NULL pointer. In that case, the condition checked in the if block is satisfied, an
appropriate message is displayed to the user, and the program terminates. If the request was processed
successfully, p != NULL evaluates to true and p now points to a valid dynamically allocated block. The
locations can be accessed equivalently using pointer arithmetic or the [] notation. It can be treated as an
array. After performing some operations on the block (assigning and printing values), when the block is no
longer needed, it can be freed using the free() function (to be covered next).

A successful run of the above program produces the following output:

Here, since the requested amount of memory is small(10 integers), the OS would be able to fulfill the
requirement almost always. However, if we modify it to request very large amounts of memory, malloc may
fail to fulfill the request.

For example, on modifying the malloc() call to

the output obtained on our system was:

Thus, it is always recommended to check for the possibility of NULL pointers before dereferencing and
using the dynamically allocated memory.

free()

free() is a function that is used to free a dynamically allocated block of memory. It accepts a single argument,
namely an address pointing to a dynamically allocated block (i.e., a block allocated using malloc, calloc(),
or realloc(). If a block is not freed, that memory becomes blocked for the duration of the execution of the
program. Failure to free can result in heap depletion. This increases the chances of malloc() or calloc()
returning NULL signifying unsuccessful allocation. It is a good practice to always free blocks that are no
longer needed.

calloc()

In the above function, we allocated 10 integers by passing the request size as 10*sizeof(int) to malloc. This
calculation can be cumbersome at times. Also, at this point, the block is filled with garbage(random values).
So, a function calloc() is provided that encapsulates the size calculation and zero initialization while
allocating arrays. Its syntax is:

That is, we pass in the number of elements required and the size of a single element to calloc() while
allocating a block corresponding to an array of elements. calloc() allocates the block, explicitly sets the
elements of the allocated block to be all zeroes, and returns the required pointer.

realloc()
The function realloc() is used when we need to resize a block that has been previously allocated. For
example, consider the following code snippet:

Here, ptr was initially allocated a size equivalent to 5 integers. If, at a later point in time, we wish to increase
the size to 10 integers, we can use realloc() as shown above. realloc() accepts a dynamically allocated
address and a size in bytes. It allocates a block of the new size, copies the contents pointed in the old block
to the new block, and then frees the old block.

Thus, we can see that the above functions can be used to allocate and deallocate memory dynamically and
provide tremendous flexibility to the users.

Static and Dynamic Arrays

Now, we can revisit the drawbacks of (static) arrays that we started the section on Dynamic Memory
Allocation with. We now know how to use both static and dynamic arrays. We have also seen the similarities
and differences between them. The following table encapsulates the differences between them:

We talked about the issue of returning arrays earlier. When we want to return static arrays from functions,
we are unable to do so since they are passed back by reference. If the array was declared in the called
function and returned to the calling function, the reference location returned no longer belongs to the array
on the stack. Thus, the returned address is of no use.

So, this issue can be solved in two ways. The first approach is to pass an empty array to the function,
populate it within the function, and return the populated array to the calling function. Consider the
following example:
Here, in main(), an array a is declared. We want to define a function copy() that copies the contents of a
into a new array b. We achieve it by passing a new empty array of the same size (here b) as well to copy().
copy() copies the contents of a to b and does not return anything. Since arrays are passed by reference,
this has the effect of actually copying a into b. Thus, functions can effectively "return" arrays by accepting
the array to be returned as an additional parameter and mutating it. Here, the scope is the calling function
and not the called function. So, the array will not be destroyed at return.

Another approach would be to dynamically allocate the array on the heap in the called function and, thus,
solve the issue of the array being present on the popped-off stack frames. For example, consider the
following program:
Here, the function copy() accepts only the old array a and its length n as parameters. The new array b is
allocated dynamically inside copy() using malloc(). It is populated with the values of a and is returned as a
pointer to main(). In this way, we are able to achieve behavior similar to "returning arrays", by returning
pointers to dynamically allocated blocks.

Thus, we can see that both approaches of passing in empty arrays as well as dynamically allocating the new
array in the called function can be used to achieve the required functionality of returning an array from a
function. We prefer either approach based on the constraints and needs of the problem required to be
solved.

Another issue that we talked about was the inability to resize static arrays. We can see this issue gets
resolved in dynamic arrays. Assume we have allocated an array of 10 int values using malloc() (or calloc()).
Now, assuming that we now require to store 5 more int variables in the array, we can simply call realloc()
on the old array with the new size and achieve the required resizing functionality.

Thus, we now have the ability to resize dynamic arrays, which we did not have with static arrays.
Reading Summary:

In this reading, you have learned the following:

• The concept of stack and heap

• How to dynamically allocate and deallocate memory using malloc, calloc, realloc, and free

• The difference between static and dynamic arrays

• How to return arrays from functions

• How to resize dynamic arrays


Practice Lab 1 - Dynamic Memory Allocation
Assignment Brief

Practice Lab 1_Question_1

Recall calloc() is a function that allocates memory and initializes it to zero. It is defined in stdlib.h. The
function takes two arguments: the number of elements and the size of each element.

Implement the function my_calloc() that mimics the behavior of calloc(). You may use malloc() and free() in
your implementation, but you should not use calloc() or realloc(). The prototype of the function is: void
*my_calloc(int num, int size); It should return a pointer to void. The function should return NULL if the
allocation fails.

[Hint: malloc() returns NULL if the allocation fails].

For example, my_calloc(2,3) would return a block of 6 bytes initialized to all zeroes. my_calloc(8, sizeof(int))
would return a block that can hold 8 int values, initialized to all 0s.

Practice Lab 1_Question_2

Write a program to read n floating point numbers using malloc and find their average. Read the number of
elements from the user. Use scanf() to read the elements. You should allocate memory for the array using
malloc. Now, calculate the average of the elements and print it. Use free() to free the memory allocated for
the array.

Testcase1

Input:

Enter the number of elements: 5

Enter 5 elements: 1.2 2.3 3.4 4.5 5.6

Output:

Average = 3.4.
More Dynamic Memory Allocation
Topic: Dynamically Allocated Structures and 2-D Arrays

Reading Objective:

In this reading, you will learn about dynamically allocated structures and 2-D arrays. These constructs follow logically from
dynamic memory allocation of primitive types and 1-D arrays.

Main Reading Section:

Dynamically Allocated Structures

We have seen that we can allocate variables and arrays of primitive types dynamically using the malloc() function. We can
do the same for structures using exactly the same syntax.

For example, consider the following program:

The above code allocates a single structure variable dynamically. The malloc() function returns a void pointer, which we
cast to a pointer to a structure of type struct student.

Similarly, we can allocate an array of structures dynamically using the following syntax:

In order to free the memory allocated for a dynamically-allocated structure, we use the free() function. For example,

The above code frees the memory allocated for the structure variable s and the array s_arr. It works for both isolated
structures and arrays of structures.

Let us put together the above lines of code and view it as a complete program. We write a program to dynamically allocate
an array of 10 students, take the input for the records of the students from the user, and print the details of the student
with the highest CGPA.
Here, we first define the struct student with all of its data members. Then, we dynamically allocate an array of 10 students
using malloc() and store the allocated block’s address in a pointer variable studArray. We then take in input for the records
of the students in the first while loop. After that, we declare a temporary struct student variable temp to store a copy of
the student with the greatest CGPA. Now, we linearly search for the student with the greatest CGPA in our studArray using
a while loop. Whenever we encounter a student with CGPA greater than the student currently stored in temp, we
update temp to now store a copy of the new student. In this way, at the end of the loop, temp now stores a copy of the
required student. Now, we print the roll no. of the required student using a printf() call. In this way, we can use dynamically
allocated arrays of structures to write a diverse range of real-world programs.

2-D Arrays

We have seen that we can allocate memory for 1-D arrays dynamically using the malloc() function. We can do the same for
2-D arrays using exactly the same syntax.

A 2-D array is a 1-D array of 1-D arrays. So, when using pointers, they are a pointer to a pointer. For example, consider the
following code snippet:
The above code block allocates a 2-D array a of 3 rows. Each row is a pointer to an integer. We can allocate memory for
each row using the following syntax:

The above code block allocates memory for each row of the 2-D array. The first row is pointed to by a[0], the second row is
pointed to by a[1], and so on. The above code allocates memory for 2 integers in each row. Figure 1 illustrates the resultant
structure of the above code snippets.

Figure 1. 2D array

Now, we can also create a jagged array wherein each row is of different lengths. This can be done with the following code
snippet in place of the previous allocation in the loop:

Figure 2. Jagged 2D array

Figure 2 illustrates the jagged array created. Note that this kind of jagged array was not possible using purely static
multidimensional arrays.

In order to free the memory allocated for a dynamically allocated 2-D array, we use the free() function. Again, we first need
to free the memory allocated for each row and then free the memory allocated for the array itself. The following code
illustrates this:
Thus, we are now able to use dynamic memory allocation with a variety of data types and constructs, allowing us immense
flexibility in writing complex programs. The arrays thus created can be used, accessed, modified, etc., in the same way as
we covered for static arrays and static arrays of structures. The syntax is exactly the same. The only difference is in their
creation and the memory segment in which they are stored.

Reading Summary:

In this reading, you have learned the following:

• The concept of dynamically allocated structures

• The concept of 2-D arrays and jagged arrays

• How to allocate and free memory for dynamically allocated structures and 2-D arrays
Practice Lab 2 - More Dynamic Memory AllocationAssignment Brief
Practice Lab 2_Question_1

Imagine you are running a pizza shop. Now, pizza is a struct that contains

- base - the base of the pizza. It is an int that represents the index of the base in the global array of bases. For
example, if the base is "Thin", then the index is 0.

- topping - an array of toppings of length NUM_TOPPINGS. Each element is 0 or 1 depending on whether the
topping is present or not. For example, if the toppings are "Tomatoes" and "Olives", then the array is [0, 1,
0, 0, 1]

Every time you receive a new order, you need to instantiate a struct order that contains: -

• order_id: the order number

• customer_name: the name of the customer

• customer_phone: the phone number of the customer

• pizza: a reference to the pizza struct that the customer ordered

• Price: the price of the pizza

Price of a pizza is calculated as follows: -

• The base price is the price of the base

• The topping price is the sum of the prices of all the toppings

• The total price is the sum of the base price and the topping price

You need functions to -

• Create a new order: // allocate memory for a new order, instantiate its members, return pointer to order.

order *new_order(int order_id, char *customer_name, char *customer_phone, pizza *pizzas,int num_pizzas);

• Create a new pizza: // allocate memory for a new pizza, instantiate its members, return pointer to pizza.

pizza *new_pizza(int base, int *toppings);

• Calculate the price of a pizza: int calculate_price(pizza *p);

• Print the details of an order: void print_order(order *o);

• Print the details of a pizza: void print_pizza(pizza *p);

• Complete order: // This function should free the memory allocated to the order

void complete_order(order *o);

• Destroy pizza: // This function should free the memory allocated to the pizza

void destroy_pizza(pizza *p);


Lesson 1: Dynamic Memory Allocation

Dynamic Memory Allocation


16 and Linked Lists

Chapter 16, Sections 16.1–16.5.


WHAT TO LEARN

Concepts of dynamic memory allocation and the returned generic pointer.


Allocating memory with the malloc and calloc functions.
Freeing the allocated block with the free function.
Resizing an allocated memory block with realloc.
How negligence leads to memory leaks and dangling pointers.
How 2D arrays can be simulated on-the-fly while a program is running.
Attributes of a linked list and how they overcome the limitations of an array.
Manipulating a linked list with user-defined functions.
Features of the abstract data types—stacks, queues and trees.

16.1 MEMORY ALLOCATION BASICS


Up until now, memory for variables, arrays and structures have been allocated at the time of their
definition. For variables and arrays, definition is synonymous with declaration. The amount of
memory thus allocated is fixed at compile time and can’t be modified at runtime. This inflexibility
becomes a serious limitation when working with arrays. In our quest to ensure that arrays are
large enough to accommodate our needs, we often tend to oversize them. Because of the wastage
resulting from this static allocation of memory, our programs are often not as efficient as they could
otherwise have been.
The solution to this problem lies in dynamically allocating memory at runtime, i.e., as and when the
program needs it. Access to the allocated block is provided by a generic pointer that is automatically
aligned to reflect the correct type of data pointed to. Because a pointer to a contiguous memory
block can also be treated as an array, we can easily manipulate this array. In the course of program
execution, if this memory chunk is found to be under- or over-sized, it can be resized using a special
function (realloc) of the standard library.
Dynamic Memory Allocation and Linked Lists 521

This flexibility comes at a price—the responsibility to free up the allocated memory when it is no
longer required. So far, memory deallocation has not concerned us because the system has a well-
defined mechanism for deallocating memory for variables defined inside and outside a function.
A function on return frees memory used by its local variables, while global and static variables are
automatically deallocated on program termination. For memory allocated dynamically, we must do
this job ourselves. If we fail to behave responsibly, there can be serious runtime problems resulting
from memory leaks.
When memory is allocated at runtime, its size is usually determined either by user input or an
expression evaluated by the running program. This feature is exploited by a special data structure
called a linked list. Unlike an array, a linked list cannot be defined with a predetermined size—
either at compile time or runtime. When an item is added to the list, memory is created for that
item at that moment and a link (in the form of a pointer) is established between this new item and
its immediate predecessor in the list.
In this chapter, we’ll first examine the library functions that dynamically allocate memory and one
function that frees it. Using these functions, we’ll develop the techniques needed to create, modify
and query a linked list that can be expanded and contracted at will.

16.2 THE FUNCTIONS FOR DYNAMIC MEMORY ALLOCATION


When a program needs memory to be allocated, it makes a request to the operating system.
If the allocated memory is later found to be inadequate or excessive, the program can also ask for
a reallocation with a revised size. The C library supports a set of four functions for dynamically
allocating and deallocating memory. The file stdlib.h must be included for the following functions
to work properly:
malloc This function uses a single argument to specify the memory requirement in bytes.
calloc For allocating memory for arrays, calloc is more convenient to use than malloc.
calloc uses two arguments—the number of array elements and the size of each
element.
realloc This is the function to use for resizing a memory block allocated by a prior call to
malloc or calloc.
free Memory allocated by any of the previous functions is deallocated by this function.
How does one access the allocated or reallocated memory? Unlike variables, arrays and structures,
a dynamically allocated memory block has no name. For providing access to this memory block,
all of these functions (barring free) return a generic pointer that points to the base address of
the block. All read-write operations on the block are subsequently carried out using this pointer.
The free function also uses this pointer to deallocate memory.
Because memory allocation can sometimes fail (for reasons that are discussed soon), the three
allocation functions must always be tested for NULL on invocation (even though we won’t always
practice what we have just preached).
522 Computer Fundamentals & C Programming

16.2.1 The Generic Pointer


The pointer returned by the memory allocation functions is generic because a memory block can
be used to store any data type—fundamental, derived or user-defined. Since C89, a void pointer
can be assigned to a pointer of any type with or without using an explicit cast. For instance, the
following statements using the malloc function assign the base address of a block of 12 bytes
to a pointer variable p of type int *:
int *p = (int *) malloc(12); Not recommended
int *p = malloc(12); Recommended
Many programmers and textbooks use the former form, which made sense in the pre-C89 days
when the explicit cast was necessary. However, C89, which introduced the void pointer, dispenses
with the need to use a cast. We’ll, therefore, use malloc, calloc and realloc without the cast (second
form) to avoid cluttering the code, which also makes it easier to maintain.

16.2.2 Error Handling


All of these functions can fail if memory can’t be allocated (or reallocated). Even though a computer
may have a lot of “free” memory, it is not entirely available for dynamic memory allocation.
While memory is needed by the code and data of a program, the latter needs additional memory
while it is running. This additional memory is of two types—the stack and heap. Figure 16.1
replicates Figure 11.3 which shows the memory layout of these segments in a typical UNIX system;
other systems are broadly similar.
The stack is consumed by parameters and local variables used by a function. The stack grows
continuously as one function calls another, and shrinks when the functions return. Stack overflow
commonly occurs when too many recursive function calls are made without encountering the
terminating condition (11.3).

Stack

Heap

Data

Program Code

FIGURE 16.1 Organization of the Stack and Heap in Memory


Lesson 2: More Dynamic Memory Allocation

Chapter 16, Sections 16.3–16.9


Dynamic Memory Allocation and Linked Lists 523

The heap is used for dynamically allocating memory. It grows at the expense of the stack and vice
versa. A block of memory that is freed using the free function is returned to the heap. Failure to
free memory that is no longer required can result in severe heap depletion that may make further
allocation impossible. The memory allocation functions return NULL if they fail to find the
specified amount of memory as a contiguous chunk in the heap. Every time we use these functions,
we must thus check for NULL.
Note: Dynamic memory allocation creates a chunk of contiguous memory. A pointer to this chunk
can be treated as an array except that the latter is a constant pointer unlike a regular pointer which
can be made to point to this block.

16.3 malloc: SPECIFYING MEMORY REQUIREMENT IN BYTES


Prototype: void *malloc(size_t size);
Header File: stdlib.h
Return Type: A generic pointer to the allocated memory on success, NULL on failure.

We begin our discussions on the individual functions with malloc which is most commonly used
for dynamic memory allocation. malloc accepts the size of the requested memory in bytes as an
argument and returns a generic pointer to the base address of the allocated block. As shown in the
following, this generic pointer is suitably aligned to a pointer variable of a specific data type before
the memory chunk can be accessed:
int *p;
p = malloc(4); No cast needed
malloc allocates a chunk of four bytes whose pointer is implicitly converted to int * on assignment.
The allocated space can now be used to store a four-byte int, but the portable and correct technique
would be to use sizeof(int) as the argument to malloc:
p = malloc(sizeof(int));

malloc doesn’t initialize the allocated block, so each byte has a random value. (This is not the case
with calloc.) However, we can dereference the pointer p to assign a value to this block:
*p = 5; Assigns value to unnamed memory segment
Observe that the allocated block can’t be treated as a variable even though it can be manipulated by
its pointer. You must not lose this pointer because it represents the only mechanism that provides
access to the block.. Further, this pointer is also used by the free function to deallocate the block:
free(p); Deallocates memory allocated by malloc
The pointer returned by malloc is also used by the realloc function for resizing the allocated block,
if considered necessary. Both realloc and free are examined soon.
524 Computer Fundamentals & C Programming

16.3.1 malloc.c: An Introductory Program


Before discussing the other features related to malloc, let’s consider Program 16.1 which invokes
malloc three times to return pointers of three different data types. The program doesn’t use the
free function to deallocate the blocks because it is not required here. The annotations make further
discussions on this program unnecessary.

/* malloc.c: Uses malloc without error checking. */

#include <stdio.h>
#include <stdlib.h> /* Needed by malloc, calloc, realloc and free */
int main(void)
{
short *p1;
int *p2;
float *p3;
p1 = malloc(2); /* Non-portable; not recommended */
p2 = malloc(sizeof(int)); /* The right way */
p3 = malloc(sizeof(float)); /* Ditto */
*p1 = 256; *p3 = 123.456;
printf(“*p1 = %hd\n”, *p1);
printf(“*p2 = %d\n”, *p2); /* Prints uninitialized value */
printf(“*p3 = %f\n”, *p3);
exit(0); /* Frees all allocated blocks */
}

PROGRAM 16.1: malloc.c


*p1 = 256
*p2 = 0 Uninitialized value; may not be 0
*p3 = 123.456001

PROGRAM OUTPUT: malloc.c

16.3.2 Error-Checking in malloc


The preceding program assumed that malloc always succeeds in allocating memory, which may
not be the case. The function fails when there is not enough contiguous memory available for
allocation in the heap. We must, therefore, check the pointer returned by malloc for NULL before
using it. If malloc fails, it may not make sense to continue with program execution:
long *p = malloc(sizeof(long));
if (p == NULL) {
fputs(“Memory allocation failed\n”, stderr);
exit(1);
}
Dynamic Memory Allocation and Linked Lists 525

When the total amount of dynamic memory required by a program exceeds the amount available
on the heap, a program must recycle memory. At any instant, adequate memory must be available
for the next allocation. For small programs that eventually terminate, this is not an issue as the
allocated memory is automatically returned to the heap on program termination. However, memory
recycling is essential for server programs that run continuously without ever terminating.
16.3.3 Using malloc to Store an Array
Because array elements are stored contiguously in memory and malloc creates a contiguous block,
it is often used to create space for an array. Thus, the following statement creates a memory block
for storing 10 short values:
short *p;
p = malloc(10 * sizeof(short));
Since the name of an array represents a pointer to the first array element, p can be treated as an
array for navigating and performing pointer arithmetic. We can assign values to the “elements” of
this dynamically created “array” using either pointer or array notation:
Pointer Notation Array Notation
*p = 5; p[0] = 5;
*(p + 1) = 10; p[1] = 10;
*(p + 2) = 15; p[2] = 15;

Even though the calloc function has been specifically designed for allocating memory for arrays
and structures, simple arrays are easily handled with malloc. As the next program demonstrates,
the size of the array can be determined at runtime.
Takeaway: The malloc function makes available a contiguous block of memory that
can be used to store any data type including arrays and structures but without automatic
initialization.

16.3.4 malloc_array.c: An Array-Handling Program Using malloc


Program 16.2 uses malloc to create space for an array, where the number of elements is determined by
user input. Unlike Program 16.1, this one validates the return value (p) of malloc. Because p is used
to browse the array and finally free the allocated block, it is necessary to keep its value unchanged.
For this reason, p is copied to q, which is changed in a loop to assign values to the array elements.
/* malloc_array.c: Uses memory allocated by malloc as an array after
saving the original pointer returned. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p, *q;
short size, i;
526 Computer Fundamentals & C Programming

fputs(“Enter number of array elements: “, stderr);


scanf(“%hd”, &size);
p = malloc(size * sizeof(int));
if (p == NULL) {
fprintf(stderr, “Memory allocation failed.\n”);
exit(1);
}
q = p; /* Saves p for later use */
/* Assigning values to the “array” q ... */
for (i = 0; i < size; i++)
*q++ = i * 10;
/* ... and printing them using p */
for (i = 0; i < size; i++)
printf(“p[%hd] = %6d, Address = %p\n”, i, p[i], (p + i));
free(p); /* Deallocates block, but not necessary here */
exit(0);
}
PROGRAM 16.2: malloc_array.c
Enter number of array elements: 5
p[0] = 0, Address = 0x804b008
p[1] = 10, Address = 0x804b00c 4 bytes more than previous address
p[2] = 20, Address = 0x804b010 ... Ditto ...
p[3] = 30, Address = 0x804b014 ... Ditto ...
p[4] = 40, Address = 0x804b018 ... Ditto ...
PROGRAM OUTPUT: malloc_array.c

Takeaway: malloc doesn’t determine the data type of the dynamically created block. The type
is determined by the user when declaring the pointer to this block.

Caution: It is a common programming error to use the pointer variable assigned by malloc in
a subsequent call to the same function without freeing the previous block.

16.4 free: FREEING MEMORY ALLOCATED BY malloc


Memory allocated dynamically must be freed when it is no longer required. This will happen
automatically when the program terminates, but we often can’t afford to wait for that to happen.
Memory could be in short supply, the reason why we may need to explicitly free it with the free
function.
free takes the pointer returned by malloc, calloc and realloc as its only argument and frees the
block associated with the pointer. The function uses the following prototype:
void free(void *ptr);
Dynamic Memory Allocation and Linked Lists 527

free returns nothing and ptr must have been previously returned by any of the memory allocation
functions. Thus, the memory allocated by
int *p = malloc(sizeof(int) * 10);

is returned to the heap by a call to free, preferably followed by setting the pointer to NULL:
free(p); Pointer lost
p = NULL; Can’t access the freed memory
free doesn’t need to know the size of the block because it is known to the OS which is ultimately
responsible for memory management. Because of its subsequent assignment to NULL, the original
value of the pointer is now lost, so the freed block can no longer be accessed directly.
For programs performing simple tasks that consume insignificant amounts of memory, we need not
use free because the allocated memory will be automatically deallocated on program termination.
But when heap memory is scarce, a program that calls malloc 100 times (possibly in a loop) must
also call free 100 times. This ensures that, at any instant of time, adequate memory is available
on the heap for allocation.

Takeaway: The pointer returned by malloc is used for three purposes: (i) for performing
read/write operations on the allocated block, (ii) for resizing the block with realloc,
(iii) for deallocating the block with free.

Note: free can’t release named memory blocks used by variables, arrays and structures.
The argument used by free must point to the beginning of a block previously allocated dynamically.

16.5 MEMORY MISMANAGEMENT


Dynamic memory allocation is associated with a couple of problems that we have not encountered
previously because all memory management was hitherto handled automatically by the program.
We are now in a different world where we need to address the following issues:
Even though the memory block is freed, it is still accessible because the pointer to it is not lost
(dangling pointer).
The memory block is not freed at all. The problem becomes acute when malloc is repeatedly
used in a loop (memory leak).
Failure to handle a dangling pointer or a memory leak may not (unluckily) affect some of your
programs. If your program misbehaves soon, consider yourself lucky because you can rectify the
mistake before finalizing the code. Let’s now examine these two issues.

16.5.1 The Dangling Pointer


Freeing a memory block with free(p) doesn’t necessarily make it inaccessible because p continues
to point to the block. p is now a dangling pointer. As a safety measure, it should be set to NULL
immediately after invoking free:
528 Computer Fundamentals & C Programming

free(p); p becomes a dangling pointer


p = NULL; p is now a null pointer
Because this memory—partially or totally—may have already been allocated, a subsequent
attempt to access it using p could produce erroneous results or even cause the program to crash.
Setting p to NULL solves the problem easily.

16.5.2 Memory Leaks


A different problem arises when p is made to point to another block without freeing the existing one.
The previous block can’t then be freed (except by program termination) because its pointer
is lost. A block without its pointer creates a memory leak which can turn serious when malloc is
repeatedly used in a loop.
What happens when malloc is used in a function where the pointer to the block is declared
as a local variable? Repeated invocation of the function will progressively deplete the heap unless
arrangements are made to free the blocks outside the function. Figure 16.2 shows the improved
version of the my_strcpy function (13.8.3) shown with the main section.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *my_strcpy2(const char *src);
int main(void){
char stg[80], *p;
printf(“Enter string: “);
scanf(“%[^\n]”, stg);
p = my_strcpy2(stg);
printf(“Copied string: %s\n”, p);
free(p); /* Frees memory created in the function */
return 0;
}

char *my_strcpy2(const char *src)


{
char *dest;
short length;
length = strlen(src) + 1; /* One extra slot for NUL character */
dest = malloc(length);
strcpy(dest, src);
return dest; /* Pointer to block now available in main */
}

FIGURE 16.2 Freeing Memory Created in a Function


Dynamic Memory Allocation and Linked Lists 529

Unlike my_strcpy, the my_strcpy2 function uses a single argument—a pointer to the source string.
The function uses malloc to create a correctly-sized memory block by determining the length of
the string first. The copied string is then written to this block and its pointer is returned by the
function. Note that termination of the function doesn’t free the block which is eventually freed in
main. A memory leak is thus prevented because the pointer to the block could be transmitted to the caller.

Takeaway: A memory block created inside a function remains in existence after the function
terminates. If this block has a local variable pointing to it, this pointer variable must be returned
by the function so it can be used in the caller to free the block.

16.6 malloc_2Darray.c: SIMULATING A 2D ARRAY


How does one use malloc to create space for a 2D array? To briefly recall, a 2D array is an array
where each element represents a separate array (10.12). Also, for the array arr[i][j], the non-existent
“element”, arr[i], represents a pointer to the ith row, which contains j columns. Figure 16.3 depicts
the simulation of a 2D array where each element of the array of pointers points to a separate array
(shown on the right). The left-most pointer must, therefore, be defined as a pointer to a pointer.

** 1 2 3 4 5
*

11 22 33 44 55
*

Pointer to Array of Pointers 111 222 333 444 555


*

55 66 77 88 99
*

555 666 777 888 999


*

Array of Pointers Each row allocated by malloc

FIGURE 16.3 Simulating a 2D Array with an Array of Pointers


Program 16.3 uses malloc in two stages for creating space that can be accessed with 2D array
notation: (i) for storing pointers to the rows, (ii) for storing the columns of each row.
The program populates the array with user input, displays them and finally frees the allocated
spaces. Observe that the block used by the array of pointers is freed after the blocks used by the
columns are freed inside a loop.

/* malloc_2Darray.c: Uses malloc to (i) create an array of pointers,


(ii) an array of short integers for each pointer.
Also populates and prints array. */
#include <stdio.h>
#include <stdlib.h>
530 Computer Fundamentals & C Programming

int main(void)
{
short rows, columns, i, j;
short **p; /* Signifies a 2D array p[ ][ ] */

fputs(“Number of rows and columns: “, stderr);


scanf(“%hd %hd”, &rows, &columns);

/* Allocate memory for array of pointers */


p = malloc(sizeof(short) * rows);
if (p == NULL) {
fputs(“Cannot allocate memory for row pointers: “, stderr);
exit(1);
}

/* Allocate memory for columns of each row */


for (i = 0; i < rows; i++) {
if ((p[i] = malloc(sizeof(short) * columns)) == NULL) {
fputs(“Cannot allocate memory for columns: “, stderr);
exit(1);
}
/* Populate the array ... */
fprintf(stderr, “Key in %hd integers for row %hd: “, columns, i);
for (j= 0; j < columns; j++)
scanf(“%hd”, &p[i][j]);
}
/* ... and print the array */
for (i = 0; i < rows; i++) {
for (j= 0; j < columns; j++)
printf(“%5hd “, p[i][j]);
printf(“\n”);
free(p[i]); /* Frees each pointer allocated for columns */
}
free(p); /* Frees pointer for rows */
exit(0);
}

PROGRAM 16.3: malloc_2darray.c


Number of rows and columns: 3 5
Key in 5 integers for row 0: 1 2 3 4 5
Key in 5 integers for row 1: 11 22 33 44 55
Key in 5 integers for row 2: 111 222 333 444 555
1 2 3 4 5
11 22 33 44 55
111 222 333 444 555

PROGRAM OUTPUT: malloc_2darray.c


Dynamic Memory Allocation and Linked Lists 531

16.7 malloc_strings.c: STORING MULTIPLE STRINGS


The standard technique of reading a string from the keyboard is to use scanf with a char array.
For reading a set of strings, however, we can use a 2D char array (13.12). This technique wastes
space if the strings are of unequal size but we didn’t have a better option then—or so it seemed.
But we do have a better option now, one that uses malloc.
A set of strings can be saved in an array of char pointers (13.13), but this technique doesn’t work
with strings input from the keyboard. You can’t use scanf with a char pointer that doesn’t signify
or point to an array. Program 16.4 achieves the best of both worlds by using malloc to dynamically
allocate memory both for the strings and the array of pointers. No space is wasted for storing the
strings which are handled in the following manner:
Each string is temporarily read into an array (temp) of a fixed size.
The length of the string is computed with strlen.
A block of memory is dynamically created with malloc for the string.
The string is finally copied from temp to the block.
The program first uses malloc to create the array of pointers named p_names after knowing the
number of strings that it has to hold. In the first for loop, each string is input, its size ascertained
and space for it created with malloc. In the second for loop, the string is printed and its storage
deallocated. Finally, the space used by the array of pointers is freed.

/* malloc_strings.c: Program to create correctly-sized memory blocks for


storing multiple strings. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FLUSH_BUFFER while (getchar() != ‘\n’) ;

int main(void) {
int i, length, num;
char **p_names; /* Array of pointers for multiple strings */
char temp[80];
fputs(“Number of strings to enter: “, stderr);
scanf(“%d”,&num);
p_names = malloc(num * sizeof(char *)); /* (A) Allocates memory for ... */
/* ... array of char pointers */
for (i = 0; i < num; i++) {
FLUSH_BUFFER
fputs(“Name: “, stderr);
scanf(“%[^\n]”, temp);
length = strlen(temp);

/* Now allocate memory for every string read */


p_names[i] = malloc(length + 1); /* (B) Extra byte for NUL */
532 Computer Fundamentals & C Programming

if (p_names[i] == NULL) {
fputs(“Memory allocation failed\n”, stderr);
exit(1);
}
strcpy(p_names[i], temp); /* Copies string to allocated memory */
}
for (i = 0; i < num; i++) {
printf(“%s\n”, p_names[i]); /* Prints each string */
free(p_names[i]); /* Frees memory allocated in (B) */
}
free(p_names); /* Frees memory allocated in (A) */
exit(0);
}

PROGRAM 16.4: malloc_strings.c


Number of strings to enter: 5
Name: Michelangelo Buonarroti
Name: Leonardo da Vinci
Name: Vincent van Gogh
Name: Rembrandt
Name: Pablo Picasso
Michelangelo Buonarroti
Leonardo da Vinci
Vincent van Gogh
Rembrandt
Pablo Picasso
PROGRAM OUTPUT: malloc_strings.c
Using scanf with the scan set (9.11.1) in a loop, you can key in multi-word strings. The %s specifier
won’t work here even though it works without problems in printf. The program is conceptually
similar to Program 13.11, except that we are now working with a ragged array (containing an
unequal number of columns).

16.8 calloc: ALLOCATING MEMORY FOR ARRAYS AND STRUCTURES


Prototype: void *calloc(size_t num, size_t size);
Header File: stdlib.h
Return Type: A generic pointer to the allocated memory on success, NULL on failure.

Even though malloc can create space for storing arrays and structures, C supports a separate
function—calloc—for dynamically allocating memory for arrays of any type. Unlike malloc,
calloc needs two arguments:
The number of array elements (num).
The size in bytes of each element (size).
Dynamic Memory Allocation and Linked Lists 533

calloc creates a block of num * size bytes and returns a generic pointer to the beginning of the
block. Unlike malloc, calloc initializes the entire block with zeroes, sparing a programmer the
trouble of using a loop for initialization.
Both of the following allocation statements create a block of memory that can hold an int array
of 10 elements:
int *p;
p = malloc(10 * sizeof(int));
p = calloc(10, sizeof(int)); Pointer to block created by malloc lost!
In either case, the size of the block is typically 40 bytes, but all bytes of the block created by calloc
are initialized to zeroes. If initialization is not required, then you may use either of the two functions,
even though the syntax of calloc is more intuitive when used for storing arrays.

Takeaway: Both malloc and calloc can create space for arrays except that (i) the arguments
of calloc are array-oriented, (ii) calloc initializes the array elements to zero.

Program 16.5 uses calloc to create space for an array of structures containing the performance
parameters of sportsmen. The size of this array is determined at runtime by user input. After the
data is keyed in, the details of the sportsmen are displayed.

/* calloc.c: Allocates memory for an array of structures.


Size of array is determined at runtime. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
short i = 0, num;
struct cricketer {
char name[30];
short runs;
float average;
} *p; /* Defines pointer to a structure */
fputs(“Number of structures? “, stderr);
scanf(“%hd”, &num);
p = calloc(num, sizeof(struct cricketer));
if (p == NULL) {
fputs(“Cannot allocate memory, quitting ...\n”, stderr);
exit(1);
}
while (i < num) {
fprintf(stderr, “Enter runs, average, name for element %hd: “, i);
scanf(“%hd %f %[^\n]”, &p[i].runs, &p[i].average, p[i].name);
i++;
}
534 Computer Fundamentals & C Programming

fputs(“\nPrinting each structure ...\n”, stderr);


for (i = 0; i < num; i++)
printf(“%20s %6hd %5.2f\n”, p[i].name, p[i].runs, p[i].average);
free(p);
exit(0);
}

PROGRAM 16.5: calloc.c


Number of structures? 4
Enter runs, average, name for element 0: 6996 99.94 Don Bradman
Enter runs, average, name for element 1: 7249 58.45 Walter Hammond
Enter runs, average, name for element 2: 8032 57.78 Garfield Sobers
Enter runs, average, name for element 3: 15921 53.78 Sachin Tendulkar

Printing each structure ...


Don Bradman 6996 99.94
Walter Hammond 7249 58.45
Garfield Sobers 8032 57.78
Sachin Tendulkar 15921 53.78

PROGRAM OUTPUT: calloc.c

Note: Using malloc in tandem with the memset function, you can simulate the effect of calloc.
memset uses three arguments: (i) a pointer to the block, (ii) the initial value, (iii) the number of bytes
to be initialized. The functions have to be used in the following manner to initialize an array of 10 integers
to zero:
p = malloc(sizeof(int) * 10);
memset(p, 0, sizeof(int) * 10); Must include <string.h>
memset assigns the same value to every byte of the block; you can’t use it to set, say, each int array
“element” to 1. That would imply setting every fourth byte to 1. Thus, zero is the only value we can use here.

16.9 realloc: CHANGING SIZE OF ALLOCATED MEMORY BLOCK


Prototype: void *realloc(void *ptr, size_t size);
Header File: stdlib.h
Return Type: A generic pointer to the reallocated memory on success, NULL on failure.

In spite of allocating memory at runtime, it is still possible for the allocation to be either excessive
or inadequate. You would then need to resize the existing memory block with the realloc function.
This function takes the old pointer (ptr) returned by a previous invocation of malloc or calloc as
the first argument and the desired size (size) as the other argument.
realloc returns a pointer to the beginning of the reallocated block, or NULL on error. The following
sequence that also uses malloc enlarges an existing block of 20 bytes to 100:
Dynamic Memory Allocation and Linked Lists 535

int *p, *q;


p = malloc(20); Allocates 20 bytes
...
q = realloc(p, 100) Reallocates 100 bytes
If the requested size (100) is greater than the existing one (20), one of the following things will happen:
realloc will try to enlarge the existing block to 100 bytes of contiguous memory and return
the existing pointer (p = q) if successful.
Otherwise, realloc will attempt to create a new block elsewhere and move the existing data
to the new block. It will return a pointer (q) to this newly created block, free the old block and
set p to NULL. C guarantees that there will be no loss or alteration of data during migration.
If neither action succeeds, realloc will return NULL and leave the existing data intact.
In case q is different from p, you may still want to reassign q to p and continue to use the original
pointer:
p = q;
In case you have overestimated the memory requirements, you can use realloc to shrink the existing
block. The previous reallocation can thus be followed with this call to realloc:
r = realloc(q, 80) Shrinks size to 80 bytes
Program 16.6 uses scanf to accept any number of integers from the keyboard and save them
in a memory block. The space for the first integer is created with malloc. This space is subsequently
extended with realloc to store the remaining integers. The program progressively shows the number
of bytes allocated along with the pointer to each of these blocks. Even though the pointer has not
changed in this sample run, it need not necessarily be so, specially if the block becomes large.

/* realloc.c: Uses realloc to increase the size of allocated memory


every time another integer is read by scanf. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p, temp;
short i = 0, num, newsize;
fputs(“Key in some integers, press [Enter] and [Ctrl-d]: “, stderr);
while (scanf(“%d”, &temp) == 1) {
i++;
if (i == 1) {
/* Allocate memory for 1st element of array */
if ((p = malloc(sizeof(int))) == NULL) {
fputs(“Cannot allocate memory, quitting ...\n”, stderr);
exit(1);
}
printf(“Allocated memory = %2d bytes, Pointer = %p\n”, sizeof(int), p);
*p = temp; /* First integer allocated to memory block */
}
536 Computer Fundamentals & C Programming

else if (i > 1) { /* For subsequent input ... */


newsize = sizeof(int) * i;
p = realloc(p, newsize); /* ... memory reallocated */
if (p == NULL) {
fputs(“Cannot reallocate memory, quitting ...\n”, stderr);
exit(1);
}
*(p + i - 1) = temp; /* Populates reallocated block */
printf(“Reallocated memory = %2d bytes, Pointer = %p\n”, newsize, p);
}
} /* Matching brace for while */

num = i;
if (i > 0) {
fputs(“Printing array elements ...\n”, stderr);
for (i = 0; i < num; i++)
printf(“%d “, p[i]);
}
exit(0);
}

PROGRAM 16.6: realloc.c


Key in some integers, press [Enter] and [Ctrl-d]: 11 222 33 444 55 666
Allocated memory = 4 bytes, Pointer = 0x804b008
Reallocated memory = 8 bytes, Pointer = 0x804b008
Reallocated memory = 12 bytes, Pointer = 0x804b008
Reallocated memory = 16 bytes, Pointer = 0x804b008
Reallocated memory = 20 bytes, Pointer = 0x804b008
Reallocated memory = 24 bytes, Pointer = 0x804b008
[Ctrl-d]
Printing array elements ...
11 222 33 444 55 666

PROGRAM OUTPUT: realloc.c


This program provides the foundation for the remaining discussions in this chapter. The memory
for each input integer is provided “just-in-time,” i.e., when it is needed. Thus, to input n integers,
you need to invoke malloc once and realloc n - 1 times. The program is inefficient because of
multiple allocations, but the “just-in-time” technique illustrates how memory allocation is made
for a linked list which we will now discuss.

16.10 THE LINKED LIST


Program data are often held in one or more lists. A list is simply a sequence of data items held
together by a common access mechanism. An array is a linear list of data items where each item
is identified by a unique array subscript. However, an array has some disadvantages that you are
well aware of:
Files in C
Topic: File Handling in C

Reading Objective:

In this section, you will learn about file handling in C. You will also gain insight into the need for files and how to open, close,
and modify files in C.

File Handling Basics

Until now, we interacted with programs in which data was either provided in the program itself or taken as input from the
user, and output appeared on the terminal. However, this is not convenient for huge amounts of data. Real-world
applications can interact with millions of entries of data. The most convenient way to handle such large scales of data is to
read/write data from/to files. In this section, we will learn how to handle I/O using files in C.

Opening a File

A file has to be opened before data can be read from or written to it. Files can be opened using the fopen function. The
fopen function returns a file pointer, which is a pointer to a built-in structure called FILE. Moreover, a file can be opened
in different modes, such as Read, Write, and Append. The syntax for opening a file is:

Let us take a look at the different modes available:

Mode Abbreviation

Read r

Write w

Append (Writing to the end of an existing file) a

Both reading and writing r+ OR w+

Reading file but only appending permitted a+

Here is an example of opening a file called foo.txt in read-only mode (that is, to take input):

Some important points:

• If the file we are opening is in the same directory that the program is written in, then just writing the file name with
the extension is enough; for example, “abc.txt” or “data.in”.

• If the file is in a different directory, the entire path must be specified in the file name; for example,
“\home\oreo\test\file.txt”.

• Let us say our program is in a directory XYZ, and the input data is inside another directory in XYZ itself, called PQR.
Then it is enough just to specify the relative path. For example, “PQR/abc.txt” will search for the directory PQR
inside the current directory of the program and then search for abc.txt inside it.

• If a file is opened in “w” or “a” mode, a new file with the given name will be created if it does not exist.
Closing a File

When a program is done interacting with a file, it should be closed. A file can be closed simply by calling fclose:

Note that it is not required to close a file. However, it is a good practice to do so for various reasons:

• Operating systems have a limit on the number of files that a program can open.

• Multiple programs might be trying to access the same file, and they cannot do so until the current program closes
the file stream.

Reading and Writing to a File

Various functions are included in the standard header file stdio.h for I/O with files. Let us discuss each of them:

• fgetc:

fgetc fetches the next character that has not yet been read from the file.

Syntax: fgetc(FILE *stream);

It could be used as follows:

If there are no more characters to be read from the file, fgetc returns EOF (End Of File). EOF is an integer defined in stdio.h,
having a value of -1. So, if we wanted to read all characters from a file, it could be done inside a while loop, as follows:

This loop will automatically terminate on EOF.

• fputc:

It outputs a character to the current file.

Syntax: fputc(int c, FILE *stream);

• fgets:

Syntax: fgets(char *s, int size, FILE *stream);

This is a line-oriented function. fgets reads at most size - 1 characters from the file stream and saves the characters in s.
After that, ‘\0’, the string-terminating character, is automatically appended to s. Note that if a newline is encountered
before size - 1 characters, then fgets automatically stops reading. So, fgets will read size - 1 characters or one entire line,
whichever is smaller. Example usage:

• fputs:

Syntax: fputs(const char* s, FILE *stream);


The fputs function writes the string of characters in s to the file stream.

• fscanf and fprintf:

These function exactly like scanf and printf, except we need to specify the file stream as the first argument.

Syntax: fscanf(FILE *stream, ….) (the rest is the same as normal scanf/printf).

For example:

Example Program:

Go through the following program to get an idea of how you can use various file I/O functions:

Question: What would be the output of this program? What is stored in the file file.txt after the program is executed?

Answer: Output - A 1234

In file.txt - A1234 abcd

Special Files

In the previous example, you saw the stdout being used as an argument to fprintf. It turns out that three special files are
available in every C program:

• stdin, the standard input file

• stdout, the standard output file

• stderr, the standard error file

By default, stdin is associated with the keyboard. Any program that reads input from a user is taking input from stdin.
Similarly, stdout is associated with the terminal. When a program prints an output using printf() to the user, it is considered
output to the special file, stdout. stderr is used by programmers mainly for debugging purposes. The default output of
stderr is also in the terminal, the same as stdout.

So, fscanf() and fprintf() can achieve all of the functionality of scanf() and printf(), simply by using the special files, stdin
and stdout.

Example 1

Let us go through the code given below for the first example:
This program reads data from the file “data1.txt” and stores it in the character array “toRead”. Then, the program tries to
find the first occurrence of a substring “How” in the data that is read. To do this, the program uses the strstr function,
which comes in the header file <string.h>. The first occurrence is stored in “p” and prints it.

Example 2

Let us go through the second example. The program is given below:

In this example, we first open a file in the read-only mode and read strings from it, line by line. Using the while loop, we
keep reading strings until we reach the end of the file. So, at the end of the while loop, the character array “toRead” will
contain only the data contained in the last line of the file. Now, let us try to write this last element of data to another file.
We open another file pointer with the file name “output_file.txt”, this time in write-only mode. Then, we can output the
data contained in “toRead” to this file.

.
Reading Summary: The need for file handling in C How to open and close files using FILE keyword as well as fopen() and

. .
fclose() functions Different modes available for file handling (such as r/w/a/r+) Different methods to read/write to files (such as
fgets(), fputs(), fprintf(), fscanf(), etc.)
Practice Lab 1 - File Handling in C
Assignment Brief

Practice Lab 1_Question_1

Write a program that reads all the data from the file dataR.txt, and adds it to the end of the file dataW.txt.
Note that the existing contents of "dataW.txt", must not be overwritten!

Complete the code in Question1.c. In the same directory as Question1, you will find two text files dataR.txt
as well as dataW.txt.

Practice Lab 1_Question_2

The attached file data.in contains a list of 200 integers, each on a new line. Write a program that reads the
contents of this file. After that, make two new files maxElement.txt and minElement.txt, and write the
maximum and minimum elements of data.txt in these files, respectively.
A Comprehensive Case
Reading Objective:

In this reading, you will consider a comprehensive example that covers many of the topics you have learned
so far in C.

Main Reading Section:

Question: We wish to read and maintain a list of grocery items in our program. Each grocery item
has its details, such as name, price, and quantity. The functions that need to be created are as
follows:

• A function to read the details of a list of items from the user and store them in an array of
structures that are dynamically allocated.

• A function that prints the list of items read from the user.

• A function that finds an item of our choice from the above list.

• A function that finds out an item that has the maximum price.

Moreover, we need to create a main function that calls the above functions.

Answer: Let us start by defining the item structure:

The item structure should contain all the necessary information asked for in the problem statement. We will
create an integer field ID, a character string for Name, a floating-point (real value) for Price, and an integer
for Quantity.

Now, we can make a function to read the input from the user:
The function takes the number of grocery items in the list as an argument, dynamically allocates an array of
structures of that size, takes the required input from the user, and returns the dynamically allocated array.

The function to print items from the grocery list would be as follows:

This function just iterates over the dynamically created array of structures and prints the stored data. Next,
we’ll make a function to find an item in the array for a given quantity. If no item is found, we return a dummy
item with ID = -1.
In the above function, we’ll make a variable “index”, which will eventually contain the index of the item with
the required quantity. To find the index, we go through every single element of the array in a for-loop and
check if the current item has quantity equal to the required quantity. If it does, then just return the item.
Otherwise, if we go through the entire loop without finding the right item, we’ll return a dummy (empty)
item with ID = -1.

Next, we make the function to find the item with the maximum price:

The above function maintains two values: maxIndex and maxPrice. maxPrice will store the maximum price
of items encountered so far, and maxIndex will store the index associated with the item with the maximum
price. Initially, we assign maxPrice and maxIndex to -1. Then, in a loop, we will traverse over all the items
stored in the array and store the price and the index of the item with the maximum price seen so far in our
traversal. At the end, maxPrice and maxIndex would contain the price and the index (respectively) of the
item with the maximum price in the array.

Finally, we can write the driver code for all these functions inside main():
This code will be able to utilize the functionality of every function we have made so far. First, we’ll ask for
the size of the grocery list. Next, we’ll take the grocery list as input, and print the list. After that, we can ask
the user for a required item quantity and print the item with that quantity. We then find the max price item
and print it. All these functionalities are achieved by calling their respective functions.

This is a classic example of writing modular programs where each function has a specific functionality given.

Let us look at the sample code execution of the above code:

Enter the number of unique grocery items in the store - 5

Enter the details for item 1:

Name: Potato

Price: 30.56

Quantity: 20

Enter the details for item 2:

Name: Rice
Price: 13.45

Quantity: 6

Enter the details for item 3:

Name: Apple

Price: 22.45

Quantity: 100

Enter the details for item 4:

Name: Oil

Price: 101.34

Quantity: 12

Enter the details for item 5:

Name: Watermelon

Price: 88.65

Quantity: 22

Item ID: 1, Name: Potato, Price: 30.559999, Quantity: 20

Item ID: 2, Name: Rice, Price: 13.450000, Quantity: 6

Item ID: 3, Name: Apple, Price: 22.450001, Quantity: 100

Item ID: 4, Name: Oil, Price: 101.339996, Quantity: 12

Item ID: 5, Name: Watermelon, Price: 88.650002, Quantity: 22

Enter the quantity of the item you wish to find - 22

The item found with quantity 22 is:

ID: 5, Name = Watermelon, Price = 88.650002

The item with maximum price is:

Item ID: 4, Name: Oil, Price: 101.339996, Quantity: 12

Reading Summary:

• A comprehensive example that used many of the topics covered in this course

• How to use dynamic memory allocation from the heap, structures, and functions to simulate a full-
fledged example, showing the uses of programming in the real world

Practice Lab 2: Comprehensive Case


Assignment Brief

Practice Lab 2_Question_1

In the attached file Question1.c,a skeleton program is given to implement a student database. Complete
the following functions in the program:

1. readStudentDatabase - Read the data for student database as input from the user. The data for each
item would be provided as follows: ID, name, section, name of subject 1, marks of subject 1, name of
subject 2, marks of subject 2, name of subject 3, marks of subject 3.

2. printStudentDatabase - Print the data for entire student database.

3. findMaxMarks - Given a student as parameter, return the subject in which the student has scored
maximum marks.

4. findSectionTopper - Given a particular section as parameter, find the student who has the highest
total marks in all 3 subjects.

Note: You must not make any changes to int main(). From int main(), your functions will be called one-by-
one to test their correctness.
File Handling in C and Comprehensive Cas

Chapter 15, Sections 15.1–15.9

15 File Handling

WHAT TO LEARN

Significance of the file pointer and buffer in file-I/O operations.


Opening a file in read, write and append modes (fopen).
Handling I/O errors (perror) and detecting EOF (feof).
How functions use the file position indicator in I/O operations.
Handling data in units of characters (fgetc/fputc) and lines (fgets/fputs).
Formatting data with fscanf and fprintf.
Using command-line arguments to specify filenames.
Read/write operations on arrays and structures (fread/fwrite).
Random file access using the file position indicator (fseek/ftell).
Deleting and renaming files (remove and rename).

15.1 A PROGRAMMER’S VIEW OF THE FILE


So far, our programs used data that was either provided in the program itself or input from
the keyboard. The output always appeared on the terminal. Unsaved input and output are totally
unsuitable for handling voluminous data that we often encounter in real life. You can’t obviously
key in the details of every employee for preparing the payroll, and you certainly can’t do it
every month. The most convenient mechanism for handling bulk data is to store them in files and
use programs to read from and write to these files.
A file is a named container of information that is stored in multiple blocks in a file system. This file
system typically resides in the hard disk but a CD-ROM, DVD-ROM or flash drive also support
a file system. All files are administered by the operating system which allocates and deallocates
disk blocks as and when needed. The OS also controls the transfer of data between programs and
the files they access. Thus, a file-oriented program has to make a call to the OS for a service that
it can’t handle on its own.
File Handling 485

When we invoked a.out < foo1 > foo2 from the shell (9.6), the OS opened the two files, gathered
their scattered blocks and connected their data to the a.out program. This mechanism has some
drawbacks. First, a program can use more than two files. Second, read/write operations don’t
always start from the beginning of the file. Finally, the same file could be both read and written.
These situations can only be handled by the I/O functions offered by the standard library.
A file comprises a set of byte-sized characters, where each character can have any value between
0 and 255. If we needed to have only a character view of a file, only one read and one write function
would have been adequate for read/write operations. However, programs need to know whether
a group of characters represents a string or a number, so multiple functions are needed for making
this distinction. Partly for this reason, we often classify files as binary or text, even though this
distinction has never been fully convincing.
Note: The file-I/O functions have no knowledge of the organization of a file’s data on disk. Neither
are they aware of the mechanism employed by the OS to transfer data between the program and
a file.

15.2 FILE-HANDLING BASICS


A file has to be opened before data can be read from or written to it. Typically, data is read from
a file and then manipulated by program logic. The output is often written to another file. Because
different functions can read the same file, a common buffer and a file position indicator are maintained
in memory for a function to know how much of the file has already been read. C also supports
functions that handle issues like EOF and file-related errors.
Only the fopen function needs to know the name and location of a file. fopen returns information on
the opened file in the form of a file pointer which points to a structure of type FILE. The other functions
use this file pointer to access the information stored in the structure. This information includes the
file position indicator and the location of the buffer that are associated with each opened file.
The buffer is used as temporary storage by the I/O-bound functions like fgetc and fputc.
Even though fgetc reads one character at a time, it would be inefficient to access the disk every
time a character is read. The OS reduces this overhead by reading a block of data into the buffer
from where fgetc fetches a character at a time. A similar buffer is set up when a file is opened for
writing. This buffer is written to disk when it is full or when specifically instructed to do so.
Even though files simply contain characters, it is often necessary to group these characters into
words, lines, arrays and structures. The fgets function fetches a line while fread reads a structure
or even an array of them in one call. Text files contain only printable characters while binary files
may contain any character. It is sometimes necessary for a function to know the type of file it is
dealing with, so the type has to be specified as text or binary when the file is opened.
C was born in a UNIX environment which uses the LF character (ASCII value: 10) as newline.
However, MSDOS/Windows uses CR-LF (two characters) as newline, while Mac uses only CR.
For this reason, some of the library functions have to carry out the necessary replacement when
newline is read or written on these systems.
486 Computer Fundamentals & C Programming

Note: All file-handling functions discussed in this chapter use the file stdio.h, which must be
included in every program containing one of these functions.

15.3 OPENING AND CLOSING FILES


File handling begins with the opening of a file and ends with its closing. A file can be opened in
several modes, and we must know the exact significance of each mode. Opening a file in the wrong
mode can lead to errors—or even disaster. File closing is not mandatory for most situations, even
though it is a good idea to explicitly close a file when you no longer need it.
When a file is opened, the OS associates a stream with the file. Because C treats all devices as files
and device characteristics vary, it is logical that read/write operations are performed on the stream
rather than to the device directly. The OS connects one end of the stream to the physical file and
the other end to the program. This means that any read/write operation on the stream actually
accesses the file on disk. The stream is disconnected when the file is closed.

15.3.1 fopen: Opening a File


Prototype: FILE *fopen(pathname, mode);
Return value: A pointer to FILE, or NULL on error.
The fopen function needs two strings as arguments—the pathname of the file and its mode
of opening. The pathname can be an absolute one (like “/data/foo.txt”) or a simple one
(“foo.txt”). The mode is usually “r”, “w” or “a” for simple read, write and append operations, but
we’ll also learn to use the + suffix for read/write operations. The following code shows the simplest
way of using fopen:
FILE *fp; Defines file pointer
fp = fopen(“foo.txt”, “r”); Only reading permitted
If the call is successful, fopen returns a pointer to a structure typedef ’d to FILE, so the pointer needs
to be defined first. The file is opened for reading (“r”); you can’t write to this file. The pointer
variable, fp, assigned by fopen now acts as a file handle which will be used subsequently by all
functions that access the file. Once a file has been opened, its pathname is not required any longer.

15.3.2 File Opening Modes


We opened the file foo.txt for reading, but fopen supports a number of options to represent the
mode. These options enable you to write, append and also update a file (Table 15.1). For the time
being, you need to keep in mind the following modes:
r Reading a file.
w Writing a file.
a Appending to the end of an existing file.
fopen used with “r” as the mode expects the file to exist, but when used with “w”, it overwrites an
existing file or creates one if the file doesn’t exist. When used with “a”, fopen also creates a file if
File Handling 487

it doesn’t find one. fopen will fail if the file doesn’t have the necessary permissions (read for “r”
and write for “w” and “a”).
Database applications often need both read and write access for the same file, in which case, you
must consider using the “r+”, “w+” and “a+” modes. However, using “w+” will overwrite a file, so
to preserve the contents of a file, use either “r+” or “a+”, depending on whether you need write
access for the entire file or only at its end.
All of the modes that we have discussed apply to text files which are organized as lines terminated
by the newline character used by the operating system. We’ll postpone discussions on the “b” suffix
to Section 15.13 which deals with binary files.

Takeaway: The filename is used only by fopen, but other functions use the FILE pointer
returned by fopen.

Tip: The “w” mode always overwrites an existing file, so if you need to use an existing file for read/
write access, use r+ as the mode. If you use a+, you can read the entire file all right, but you can’t
change the existing contents.

TABLE 15.1 File Modes Used by fopen


Mode File Opened for
r Reading only. Error if file doesn’t exist.
r+ Both reading and writing. Error if file doesn’t exist.
w Writing only. If file exists, then it is overwritten, else it is created.
w+ Both reading and writing. If file exists, then it is overwritten, else it is created.
a Appending only. Creates file if it doesn’t exist.
a+ Reading entire file but only appending permitted. Creates file if it doesn’t exist.
b (prefix) Same as their non-prefixed counterparts, except that binary mode is used.

15.3.3 The Filename


The pathname shown in the prototype can either be a simple filename like “foo.txt”, or a complete
pathname like “/project2/data/foo.txt”. The former is used when the file exists in the same
directory from where the program is run. But that is often not the case, in which case the absolute
pathname must be used.
The preceding pathname is used by UNIX systems where C first made its appearance, but MSDOS/
Windows systems use the \ for delimiting the components of the pathname. Because C uses the \ for
escape sequences and Windows also uses a drive name, you would have to use a pathname like
“C:\\project2\\data\\foo.txt” for fopen to locate a file on Windows. So, C takes a hit here as
far as portability goes.
Instead of hard-coding the filename in the program, we often need to take the name from user
input using code similar to the following:
488 Computer Fundamentals & C Programming

FILE *fp1;
char flname1[40]; For storing the filename
fputs(“Enter name of file for reading: “, stderr);
scanf(“%s”, flname1);
fp1 = fopen(flname1, “r”); File must exist
Another frequently used technique is to accept the input filename from the keyboard and then use
a suffix, say, “.out” , for the output filename. The strcpy and strcat functions easily achieve this task:
strcpy(flname2, flname1);
strcat(flname2, “.out”);
fp2 = fopen(flname2, “w”); Existing file overwritten
Thus, if the input filename (flname1) is foo, the output filename (flname2) is set to foo.out. Filenames
are also provided by command-line arguments, and in this chapter, you’ll encounter some programs
that employ this third technique.

Takeaway: In a program, a filename can be (i) hard-coded in the program, (ii) obtained from
user input, (iii) derived from another filename, (iv) provided as a command-line argument.

15.3.4 Error Handling


The preceding examples used fopen to assign a file pointer variable without performing any error-
checking. But an attempt to open a file can fail for a number of reasons including the following:
The file may not exist.
The file not be readable when the “r” mode is used.
The file may not be writable when the “w” or “a” modes are used.
The number of open files has reached the maximum permitted value.
There’s no point in continuing with program execution if a key file can’t be opened. fopen must
always be used with error-checking code:
fp = fopen(“C:\\data\\foo.txt”, “r”);
if (fp == NULL) {
fputs(“Error opening file for reading.\n”, stderr);
exit(1);
}
As we have previously done with getchar and scanf, the assignment to the file pointer and checking
for NULL can be combined:
if ((fp = fopen(“C:\\data\\foo.txt”, “r”)) == NULL)
fputs(“Error opening file for reading.\n”, stderr);
How do you quit a program if fopen is used inside a function? The return statement won’t help
because it will transfer control to the caller (often main), so we need to use the exit function which
terminates a program instantly after closing all open files. This function, which uses stdlib.h, is
used in all programs featured in this chapter.
File Handling 489

15.3.5 fclose: Closing a File


Prototype: int fclose(FILE *fp);
Return value: 0 on success, EOF on failure.

Operating systems have a limit on the number of files that can be opened by a program. Files must,
therefore, be closed with the fclose function once they are done with. fclose uses the file pointer
as a single argument:
fclose(fp); fp previously declared as FILE *fp;

Closing a file frees the file pointer and associated buffers. Before a file is closed, any data remaining
in the output buffer is automatically written (flushed) to disk. In most cases, using fclose is not
mandatory because files are automatically closed when the program terminates. However, it’s good
programming practice to “fclose” all files when you no longer need them.
Using fclose followed by an immediate fopen of the same file is a convenient way of moving the
file position indicator to the beginning of the file. This repositioning is better achieved with the
rewind function. However, if you have opened a file using “r” as the mode, and you now want to
write it using “r+”, you must close and reopen the file.

15.3.6 fopen_fclose.c: An Introductory Program


Program 15.1 opens and closes the file foo twice—first for writing, and later for reading. A string
input from the keyboard is written to foo and then read back for display using fgets and fputs.
You had used the same functions for performing I/O with the terminal (13.4.3). This program
demonstrates that stdin and stdout are streams that are connected to different devices. fgets and
fputs are revisited in Section 15.6.

/* fopen_fclose.c: Uses fgets and fputs for performing I/O with both
terminal and file. */
#include <stdio.h>
#include <stdlib.h> /* For exit */

int main(void)
{
FILE *fp; /* Declares file pointer */
char buf1[80], buf2[80];

fputs(“Enter a line of text:\n”, stdout);


fgets(buf1, 79, stdin); /* Space left for ‘\0’ */

fp = fopen(“foo”, “w”); /* Creates and opens file for writing */


fputs(buf1, fp); /* Writes input string to foo */
fclose(fp);
490 Computer Fundamentals & C Programming

fp = fopen(“foo”, “r”); /* Opens foo for reading */


fgets(buf2, 79, fp); /* Reads a line from foo */
fputs(buf2, stdout); /* Writes line to display */
fclose(fp);

exit(0);
}
PROGRAM 15.1: fopen_fclose.c
Enter a line of text:
Characters, words, lines and structures
Characters, words, lines and structures Output by fputs
PROGRAM OUTPUT: fopen_fclose.c

15.4 THE FILE POINTER AND FILE BUFFER


Opening a file with fopen has two important consequences—creation of a file pointer and file buffer.
The file pointer (like fp in previous programs) is a variable that points to a structure typedef ’d to
FILE. This structure, which is instantiated when a file is opened, contains the following information
related to the opened file:
The mode of opening.
The location of the file buffer.
The file position indicator.
Whether EOF or any errors have been encountered on reading or writing.
The file position indicator, also called file offset pointer, has an important role to play in input/output
operations. For instance, a read operation updates this field in the FILE structure with the position
of the buffer up to which all data have been read. The next read operation (by the same or another
function) looks up this field to determine the position from where to resume reading. The same
logic applies to write operations.
File opening also sets up a buffer which acts as a go-between between the program and the file.
When a file is read with, say, fgetc, the data is first sent from disk to the buffer from where it is
sent to the program. So, even if fgetc reads one character from the buffer in each call, the buffer
itself is populated in one disk-read operation. This speeds up the eventual data transfer rate from
disk to the program.
A similar buffer is set up when a file is opened for writing. In this case, all write operations are
made to the buffer, which itself is flushed (i.e., written) to disk when full. A file used for read/write
access thus needs two buffers. Figure 15.1 replicates Figure 9.1 which demonstrated the role of
these buffers in terminal I/O).

Takeaway: All read/write operations on files actually take place through their buffers. A buffer
is connected to a disk file by the file pointer which is generated when a file is opened
with fopen.
File Handling 491

Input Input Output Output


Program
Device Buffer Buffer Device

FIGURE 15.1 The File Buffers

Note: The standard streams, stdin, stdout and stderr, which can be used as arguments to file-
handling functions like fgets and fputs, are actually pointers of type FILE. However, every program
always finds these files open even though it can explicitly close them.

15.5 THE FILE READ/WRITE FUNCTIONS


The standard library offers a number of functions for performing read/write operations on files.
All of these functions use the file pointer as argument and can thus work with any file including
stdin, stdout and stderr. In this chapter, we’ll discuss the following functions:
Character-oriented functions (fgetc and fputc)
Line-oriented functions (fgets and fputs)
Formatted functions (fscanf and fprintf)
Array- and structure-oriented functions (fread and fwrite)
The functions belonging to the first two categories have been used with the standard streams in
previous chapters. The functions of the third category (fscanf and fprintf) behave exactly like
their terminal-based counterparts (scanf and printf). The last category needs detailed examination
because fread and fwrite work with binary files.
All of these functions use the file stdio.h. They also read or write a buffer (15.2) that is created
when the file is opened with fopen. Calls to these functions can be mixed with one another without
causing conflict. Each call moves the file position indicator in the buffer so the next call resumes
from where the previous call left off. This assertion needs vindication by a suitable program.

15.5.1 mixing_functions.c: Manipulating the File Offset Pointer


Program 15.2 uses fputs to write a string stored in the char array, buf, to the file foo. The contents
of foo are then retrieved using three separate functions (fgetc, fscanf and fgets) and written to
the standard output. Each write is done by the function corresponding to its read counterpart.
The program uses the rewind function to set the file offset pointer to the beginning of the buffer.
This function will be examined in Section 15.14.2.

/* mixing_functions.c: Uses 3 sets of I/O functions to demonstrate the


significance of the file indication indicator. */
#include <stdio.h>
#include <stdlib.h> /* For exit */
492 Computer Fundamentals & C Programming

int main(void)
{
FILE *fp;
int c, x;
char buf[80] = “A1234 abcd”;
char stg[80];

fp = fopen(“foo”, “w+”); /* File will also be read */


fputs(buf, fp); /* Writes buf to foo */

rewind(fp); /* At position 0 in foo */


c = fgetc(fp);
fputc(c, stdout); /* Prints “A” */

fscanf(fp, “%d”, &x);


fprintf(stdout, “%d”, x); /* Prints “1234” */

fgets(stg, 79, fp);


fputs(stg, stdout); /* Prints “ abcd” */

exit(0);
}

PROGRAM 15.2: mixing_functions.c


A1234 abcd

PROGRAM OUTPUT: mixing_functions.c

15.5.2 The fgetc and fputc Functions Revisited


Prototype: int fgetc(FILE *stream);
Return value: The character read on success, EOF on error or end-of-file.
Prototype: int fputc(int c, FILE *stream);
Return value: The character written on success, EOF on error.
The fgetc and fputc functions have been used in Section 9.7.1 to interact with the terminal using
stdin and stdout, respectively, as the file pointers. fgetc accepts the file pointer stream as a single
argument. It fetches and returns a character from the buffer associated with stream. This is how
the function is typically used:
int c;
c = fgetc(fp); fp returned by a prior fopen
Here, fgetc returns a character from the buffer associated with fp. To read an entire file, fgetc
must be used in a loop which terminates when fgetc detects EOF:
while ((c = fgetc(fp)) != EOF) EOF flag set in FILE structure
File Handling 493

The fputc function uses an additional argument representing the numeric value of the character
written to stream. The following statement writes the character c to the file associated with fp2:
fputc(c, fp2);

Even though fputc returns the character written, we normally use the function only for its side effect.
However, error-checking in fputc is made by comparing the return value with its first argument.
Note: The macros getc and putc are equivalent to fgetc and fputc, respectively, and use the same
syntax. But getchar and putchar can be used only with stdin and stdout, respectively. In fact,
getchar() is equivalent to getc(stdin) and putchar(c) is equivalent to putc(c, stdout).

15.5.3 file_copy.c: A File Copying Program


Program 15.3 copies a file whose name is input by the user. The output filename is generated by
appending the “.out” extension to the input filename. After successful validation of file opening,
the file foo is copied to foo.out by using fgetc and fputc in a loop. Each character fetched by fgetc
from the file associated with the file pointer fp1 is written by fputc to the file associated with fp2.

/* file_copy.c: Copies and displays file contents using fgetc and fputc.
Also uses the exit function instead of return. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void)
{
FILE *fp1, *fp2;
char flname1[80], flname2[80];
int c;

fputs(“Enter filename to be copied: “, stderr);


scanf(“%[^\n]”, flname1);
strcpy(flname2, flname1);
strcat(flname2, “.out”);

if ((fp1 = fopen(flname1, “r”)) == NULL) {


fputs(“Error opening file for reading.\n”, stderr);
exit(2); /* Returns 2 to the operating system */
}
if ((fp2 = fopen(flname2, “w”)) == NULL) {
fputs(“Error opening file for writing.\n”, stderr);
exit(3); /* Returns 3 to the operating system */
}

while ((c = fgetc(fp1)) != EOF)


fputc(c, fp2);
494 Computer Fundamentals & C Programming

fclose(fp1);
fclose(fp2);

exit(0);
}

PROGRAM 15.3: file_copy.c


Enter filename to be copied: foo foo copied to foo.out

PROGRAM OUTPUT: file_copy.c


Note: It makes sense to send diagnostic messages to the stderr stream rather than standard output.
This has been done in the previous program. Systems like UNIX and MSDOS support redirection
of the standard output stream using > and >>, and these diagnostic messages must not be included
with the useful output.

15.5.4 file_append.c: A File Appending Program


Program 15.4 appends the file bar to foo after obtaining both filenames from the keyboard. The file
appended to must be opened in the “a” mode. Validation of file opening is combined (using ||),
so an error will only indicate that one of the files created a problem, but not which one. We also
have a feel of the perror function which specifies the exact cause of failure. The error occurred in
the first invocation, and perror tells us that one of the files doesn’t exist. This function is discussed
in Section 15.10.

/* file_append.c: Appends contents of one file to another.


Introduces the perror function to specify the error. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp1, *fp2;
char flname1[80], flname2[80];
int c;
fputs(“Enter source and destination filenames: “, stderr);
scanf(“%s %s”, flname1, flname2);
fp1 = fopen(flname1, “r”);
fp2 = fopen(flname2, “a”); /* File to be appended to */
if (fp1 == NULL || fp2 == NULL) {
fputs(“Error in opening at least one file.\n”, stderr);
perror(“”); /* Specifies exact cause of failure */
exit(2);
}
File Handling 495

while ((c = fgetc(fp1)) != EOF)


if (fputc(c, fp2) == EOF) {
fputs(“Error in writing file.\n”, stderr);
exit(3);
}
fclose(fp1); fclose(fp2);
exit(0);
}

PROGRAM 15.4: file_append.c


Enter source and destination filenames: bar foo
Error in opening at least one file.
No such file or directory Message from perror
Enter source and destination filenames: foo bar

PROGRAM OUTPUT: file_append.c

15.6 THE fgets AND fputs FUNCTIONS REVISITED

Prototype: char *fgets(char *s, int size, FILE *stream);


Return value: s on success, NULL on error or when EOF is encountered before reading.
Prototype: int fputs(const char *s, FILE *stream);
Return value: A non-negative number on success, EOF on error.

The line-oriented fgets and fputs functions. which were examined in Section 13.4.3, also work with
disk files. fgets reads at most size - 1 characters from stream, saves the characters in the buffer s and
appends ‘\0’ to the buffer. If a newline is encountered before the buffer size limit is encountered,
fgets stops reading and stores the newline in the buffer. This is how the function is typically used:
char buf[80];
fgets(buf, 80, fp) != NULL); fp returned by fopen
Because of the presence of the terminating ‘\0’, buf is a string. If the string is shorter than the size
of buf, the latter will also contain the newline character. This character has to be removed from
buf for string handling operations to be possible.
The fputs function writes the buffer s to the file associated with stream. The following statement
writes the contents of buf to the file associated with fp:
fputs(buf, fp);

fputs strips off the NUL character but doesn’t add a newline character to the output. This appears
reasonable because fputs often outputs a line fetched by fgets which generally contains a newline.
496 Computer Fundamentals & C Programming

Note: Unlike fputs, the puts function appends a trailing newline to the output. puts is normally
not used with fgets because it creates double-spaced lines and it works only with stdout.

15.6.1 fgets_fputs2.c: Using fgets and fputs with a Disk File


Program 15.5 uses fgets to fetch a few lines from keyboard input. fputs writes these lines to
the file foo. After rewind resets the file position indicator for foo to the beginning, fgets reads
this file. The rewind function, which often eliminates the need to close and reopen a file, is discussed
in Section 15.14.2.

/* fgets_fputs2.c: Saves and reads back user input to and from a file. */
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 80
int main(void)
{
FILE *fp;
char buf[BUFSIZE];
if ((fp = fopen(“foo”, “w+”)) == NULL) { /* File will also be read */
perror(“”);
exit(2);
}

fputs(“Enter a few records, [Ctrl-d] to exit\n”, stderr);


while (fgets(buf, BUFSIZE, stdin) != NULL)
fputs(buf, fp);
rewind(fp); /* Positions indicator at beginning */
fputs(“\nReading from foo...\n”, stderr);
while (fgets(buf, BUFSIZE, fp) != NULL)
fputs(buf, stdout);
exit(0);
}

PROGRAM 15.5: fgets_fputs2.c


Enter a few records, [Ctrl-d] to exit
fgets must be used in place of gets.
fputs should be used as the matching counterpart of fgets.

Reading from foo...


fgets must be used in place of gets.
fputs should be used as the matching counterpart of fgets.

PROGRAM OUTPUT: fgets_fputs2.c


File Handling 497

15.6.2 save_student_data.c: Writing Data Stored in Array to a File


Program 15.6 uses fputs in a loop to save five strings to a file. The pointers to the strings are
originally stored in an array of pointers, and each string comprises three colon-delimited fields.
After the file is “rewound,” each line is fetched by fgets, parsed into three separate fields by sscanf,
and then finally displayed in formatted form by printf. It would be helpful to recall the power of
the fgets-sscanf combination in splitting a string into individual fields (13.5.1).

/* save_student_data.c: (i) Saves records stored in an array of pointers.


(ii) Splits each record into fields when reading file. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 80

int main(void)
{
FILE *fp;
short i, size, roll_no, marks;
char buf[BUFSIZE], name[20];

char *student[ ] = { “Juhi Agarwal:11:97”, “Paridhi Jalan:22:80”,


“Shiksha Daga:33:92”, “Paul Oomen:44:100”,
“Abhijith:55:96” }; /* Each record has 3 fields */

size = sizeof student / sizeof (char *); /* Number of records */

if ((fp = fopen(“foo”, “w+”)) == NULL) {


perror(“”);
exit(2);
}

for (i = 0; i < size; i++) {


fputs(student[i], fp); /* fputs doesn’t insert \n by default */
fputs(“\n”, fp);
}

rewind(fp);

while (fgets(buf, BUFSIZE, fp) != NULL) {


sscanf(buf, “%[^:]:%hd:%hd”, name, &roll_no, &marks);
printf(“%-15s %2hd %3hd\n”, name, roll_no, marks);
}
exit(0);
}

PROGRAM 15.6: save_student_data.c


498 Computer Fundamentals & C Programming

Juhi Agarwal 11 97
Paridhi Jalan 22 80
Shiksha Daga 33 92
Paul Oomen 44 100
Abhijith 55 96

PROGRAM OUTPUT: save_student_data.c

15.7 fscanf AND fprintf: THE FORMATTED FUNCTIONS

Prototype: int fscanf(FILE * stream, cstring, &var1, &var2, ...);


Return value: Number of items matched, EOF on error or if end of input encountered before
conversion of first item.
Prototype: int fprintf(FILE * stream, cstring, var1, var2, ...);
Return value: Number of characters printed on success, a negative value on error.

The fscanf and fprintf functions behave exactly like their terminal-oriented counterparts, scanf
and printf, except that both use the file pointer as an additional argument. This is how fscanf
reads a record containing three whitespace-delimited fields from a file and fprintf writes them
along with a computed field to another file:
fscanf(fp1, “%s%hd%hd”, name, &marks1, &marks1);
fprintf(fp2, “%s|%hd|%hd|%hd”, name, marks1, marks2, marks1 + marks2);
Unlike the other I/O functions, the file pointer for both functions is the first, and not the last,
argument. The functions had to be designed that way because they accept a variable number of
arguments. The next argument is a control string (cstring) containing one or more format specifiers.
This string is followed by a list of pointers for fscanf, and variables for fprintf.
Note that the preceding fprintf function writes “|”-delimited fields. Using a delimiter dispenses
with the need to have lines of equal size, which helps conserve disk space. As you’ll soon learn,
the presence of a delimiter also makes it convenient to extract the fields from a line (or record).
It’s obvious that the delimiter must not be present in the data.

15.8 fscanf_fprintf.c: WRITING AND READING LINES CONTAINING FIELDS


Program 15.7 accepts a student’s name and marks in two subjects from the keyboard and appends
them to a file. This sequence is performed in a do-while loop. This loop terminates when the user
presses [Enter] when prompted for the name. After rewinding, fscanf reads the file and fprintf
displays the formatted contents of the entire file (including data saved in previous invocations of
the program). This is possible only when fopen is used with the “a+” mode. Observe that all I/O
operations have been carried out using only fscanf and fprintf.
File Handling 499

/* fscanf_fprintf.c: Uses (i) fprintf to append user input to a file,


(ii) fscanf to read the entire file. */
#include <stdio.h>
#include <stdlib.h>
#define FLUSH_BUFFER while (getchar() != ‘\n’) ;
int main(void)
{
FILE *fp;
char name[20];
short marks1, marks2, total;
if ((fp = fopen(“foo”, “a+”)) == NULL) { /* File will also be read */
perror(“”);
exit(2);
}
do {
fprintf(stderr, “Student name: “);
if (fscanf(stdin, “%[^\n]”, name) != 1)
break; /* Quits loop when only [Enter] is pressed */
fprintf(stderr, “Marks in 2 subjects: “);
fscanf(stdin, “%hd%hd”, &marks1, &marks2);
FLUSH_BUFFER
fprintf(fp, “%s:%hd:%hd:%hd\n”, /* Writes a line to file */
name, marks1, marks2, marks1 + marks2);
} while (1);
rewind(fp);
while (fscanf(fp, “%[^:]:%hd:%hd:%hd”, /* Reads each line */
name, &marks1, &marks2, &total) == 4)
fprintf(stdout, “%-20s %3hd %3hd %3hd”, name, marks1, marks2, total);
exit(0);
}

PROGRAM 15.7: fscanf_fprintf.c


Student name: Larry Ellison
Marks in 2 subjects: 65 75
Student name: Jeff Bezos
Marks in 2 subjects: 75 85
Student name: Elon Musk
Marks in 2 subjects: 90 95
Student name: [Enter]
Larry Ellison 65 75 140
Jeff Bezos 75 85 160
Elon Musk 90 95 185

PROGRAM OUTPUT: fscanf_fprintf.c


500 Computer Fundamentals & C Programming

Have a look at the file foo and you’ll find that the marks are represented as text characters that
look like numbers. That’s how the data were actually keyed in, but they were converted by
fscanf to binary. fprintf reconverted the binary numbers back to text before writing them to foo.
When heavy number crunching is involved, numeric data must be stored and retrieved in binary
form. This what the fread and fwrite functions are meant to be used for.
The previous program worked fine simply because each record of the file foo contained four fields.
When a database is created from multiple and diverse sources, one often encounters lines having
a wrong field count. If foo contained a line with three fields, the program would have simply gone
haywire because of the usual buffer problems associated with the functions of the “scanf ” family.
We’ll improve this program using fgets and sscanf, and eventually provide a near-perfect solution
with fread and fwrite.

15.9 FILENAMES FROM COMMAND-LINE ARGUMENTS


In real life, we don’t hard-code filenames in programs. Neither do we ask the user to key it in
response to a prompt. If a program has to pause for taking user input, then the user needs to be
present when the program is running. It would then be impossible to automate or schedule programs.
The solution lies in using command-line arguments with file-handling programs.
When copying a file using the MSDOS COPY or UNIX cp command, we provide the two filenames
as command-line arguments to the program (like COPY foo foo.bak). To briefly recall (13.16), for
a program to be invoked with arguments, its main section must look like this:
int main(int argc, char *argv[ ])
The first argument (argc) is set to the number of arguments which includes the program name.
The second argument represents an array of pointers of type char *. A program to copy a file needs
two filenames as arguments and is invoked in the following manner:
AOUT foo1 foo2
When you run the AOUT (or a.out) program from the MSDOS (or UNIX) shell, the program loader
invokes main with its arguments set in the following manner:
argc = 3
argv[0] = AOUT.EXE or a.out The name of the program
argv[1] = "foo1" The pointer to "foo1"
argv[2] = "foo2" The pointer to "foo2"
The reason why argv[0] stores the program name has been explained in Section 13.16, and is taken
advantage of by UNIX programmers.
15.9.1 file_copy2.c: File Copying Using Command-Line Arguments
Program 15.8 improves a previous one (Program 15.3) by using command-line arguments to copy
a file. The program first checks whether the right number of arguments has been entered. It then
checks the access rights of the two files, which are represented as argv[1] and argv[2]. All error
messages are written to the standard error stream using fprintf. Note that argv[0] represents the
name of the program, which has been used here to display the first error message.
File Handling 501

/* file_copy2.c: Copies a file using command-line arguments. */


#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[ ])
{
FILE *fp1, *fp2;
int c;

if (argc != 3) { /* One argument is the program name */


fprintf(stderr, “%s: Wrong number of arguments.\n”, argv[0]);
exit(1);
} else if ((fp1 = fopen(argv[1], “r”)) == NULL) {
fprintf(stderr, “%s: Error in opening file for reading.\n”, argv[1]);
exit(2);
} else if ((fp2 = fopen(argv[2], “w”)) == NULL) {
fprintf(stderr, “%s: Error in opening file for writing.\n”, argv[2]);
exit(3);
}
while ((c = fgetc(fp1)) != EOF)
fputc(c, fp2);
fclose(fp1); fclose(fp2);
exit(0);
}
PROGRAM 15.8: file_copy2.c
$ a.out
a.out: Wrong number of arguments.
$ a.out foo
a.out: Wrong number of arguments.
$ a.out foo2 foo3
foo2: Error in opening file for reading.
$ a.out foo foo2
$ _ Prompt returns; job done

PROGRAM OUTPUT: file_copy2.c

15.9.2 validate_records.c: Detecting Lines Having Wrong Number of Fields


Program 15.9 examines a file containing student data. A valid record contains four colon-delimited
fields, but some records have a lesser number of fields. The original file contains seven records:
Larry Ellison:65:75:140
Jeff Bezos:75:160
Elon Musk:90:95:185
Sergei Brin:10:20:30
Bill Gates:99:99
Steve Jobs:99:98:97
Dennis Ritchie:22:33
502 Computer Fundamentals & C Programming

Three records of this file contain three fields, and the program uses the fgets-sscanf duo to locate
them. The valid and invalid lines are saved in two separate files. All filenames are supplied as
command-line arguments. After the program has been invoked correctly, the contents of the input
file foo are segregated and saved in the files foo_valid and foo_invalid.

/* validate_records.c: Validates records of a file using fgets and sscanf.


Filenames supplied as command line arguments. */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) /* **argv is same as char *argv[ ] */
{
FILE *fp1, *fp2, *fp3;
short m1, m2, m3, items;
char buf[80], name[20];
if (argc != 4) {
fprintf(stderr, “Usage: %s source validfile invalidfile\n”, argv[0]);
exit(1);
} else if ((fp1 = fopen(argv[1], “r”)) == NULL ||
(fp2 = fopen(argv[2], “w”)) == NULL ||
(fp3 = fopen(argv[3], “w”)) == NULL) {
perror(“”);
exit(2);
}
while (fgets(buf, sizeof(buf), fp1) != NULL) {
items = sscanf(buf, “%[^:]:%hd:%hd:%hd”, name, &m1, &m2, &m3);
if (items != 4)
fputs(buf, fp3); /* Invalid records */
else /* For valid records */
fprintf(fp2, “%s:%hd:%hd:%hd\n”, name, m1, m2, m3);
}
exit(0);
}
PROGRAM 15.9: validate_records.c
$ a.out
Usage: a.out source validfile invalidfile
$ a.out foo5 foo_valid foo_invalid
No such file or directory
$ a.out foo foo_valid foo_invalid
$_ Valid input; job done

PROGRAM OUTPUT: validate_records.c


Larry Ellison:65:75:140 Jeff Bezos:75:160
Elon Musk:90:95:185 Bill Gates:99:99
Sergei Brin:10:20:30 Dennis Ritchie:22:33
Steve Jobs:99:98:97
FILE: foo_valid FILE: foo_invalid
File Handling 503

sscanf can easily parse a record with delimited fields and isolate them. However, if the record
length is not fixed (as is the case here), it is impossible to locate and update a specific record using
the file offset pointer. The updated record could have a longer name and this would require the
record to intrude into the space occupied by the next record. But if the records are saved as an array
of structures (of equal length), then we can use the fseek function to move to a specific record and
use the fread and fwrite functions to update the record. We’ll soon be doing that, so stay tuned.

15.10 perror AND errno: HANDLING FUNCTION ERRORS


Program errors can occur for a host of reasons: a resource not available, the receipt of a signal, I/O
operational failures or invalid call arguments. File handling is associated with its own set of problems:
The file doesn’t exist.
The “r” mode is used but the file doesn’t have read permission.
The “w” mode is used but the file doesn’t have write permission.
The directory doesn’t have write permission for creating a file.
The disk may be full, so no further writes are possible.
Library functions indicate an error condition by returning a unique value. The file-handling ones
either return NULL or EOF, so to create robust code, we should always check for this condition
(unless we are certain that this check isn’t necessary).
When a library function returns an error, the operating system sets the static (global) variable,
errno, to a positive integer. This integer, represented by a symbolic constant, is associated with
an error message. For instance, the integer 2 or ENOENT signifies “No such file or directory.”
There are two things we can do when an error occurs:
Use perror to print the error message associated with errno.
Determine the cause of the error by checking errno.
We use perror only when a function returns an error. The function follows this syntax:
void perror(const char *s);
The function takes a string s as argument and prints the string, a colon and a space, followed
by the message associated with errno. So far, we have used perror using the null string (“”) but
that’s not how it should be used. The use of errno and perror are demonstrated in Program 15.10,
which tries to open a file that either doesn’t exist or is not readable. Don’t forget to include the file
errno.h, which defines errno.

Tip: Program flow is often dependent on the cause of the error, so you should check the value
of errno to determine the future course of action to take.

Caution: You must check the value of errno immediately after a function returns an error, and
before you do anything else. The behavior of errno in the event of a successful call is undefined.
Some systems leave it unchanged, but some don’t. If necessary, save the value of errno in a separate
variable if you need this value later in your program.

You might also like