Combined - Itp3 To 11
Combined - Itp3 To 11
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 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.
A block is also called a compound statement since it consists of one or many statements combined as a single unit.
For example:
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 == 0)
• 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 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 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.
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.
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.
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 ‘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
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.
Reading Summary
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
>=65 - Good
>=50 - 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.
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).
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.
• 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.
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.
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.
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.
A Null Body
There is a difference between while (num > 0) and while (num > 0);
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.
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.
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.
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’:
Let us write a C program to ensure that the user enters a positive integer. That is:
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
‘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.
A while loop within a while loop is called a nested while loop. Following is the simplest syntax of a nested while loop:
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
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.
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 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
• The power of a while loop is that it can be used when the number of iterations is not known
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.
Use a While loop to write C programs that enable the user to enter a number and keep entering till
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:
f(odd) = 3*odd + 1
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:
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.
For loop is a powerful iterative construct defined in C. The structure of a for loop is as follows:
1. Initialization
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
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
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.
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 can be understood very easily from the flow chart in Fig 2.
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.
6. The condition statement is executed, i<=3 is checked, which evaluates to True because i = 2.
9. The condition statement is executed, i<=3 is checked, which evaluates to True because i = 3
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.
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.
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.
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.
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
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.
for loop
while loop
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:
• How to choose between a while and for loop for a given problem
C Programming Practice Lab3
Assignment Questions:
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.
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.
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
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.
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);
}
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: ");
if(marks >=90)
printf("Your grade is Extraordinary");
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("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:
");
return 0;
}
Practice Lab 2_Question 3
#include<stdio.h>
int main(){
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;
}
int main(){
int month_number; // 1 to 12
switch(month_number){ case 1:
printf("January"); break;
return 0;
/*
input: 9
output: September
October
November
December
*/
int main(){
int month_number; // 1 to 12
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");
return 0;
int main(){
int N;
int sum = 0;
sum += i*i;
}
printf("Sum of squares from 1 to %d is %d\n", N, sum);
return 0;
int N;
printf("Enter the value of N - ");
scanf(" %d", &N);
int sum = 0;
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;
printed - ");
printf("%d",(i+j+1)%2);
int num_rows;
printed - ");
printf("%d",(i+j+1)%2);
printf("\n");
printf("\n");
return 0;
}
printf("\n");
return 0;
}
Practice Lab 3_Question 4
#include<stdio.h>
int main(){
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
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
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.
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.
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.
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.
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.
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?
switch (exp) {
case value1 : statements1;
break;
case value2 : statements2;
break;
...
default : statements;
}
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
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.
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.
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.
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;
}
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.
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;
}
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.
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.
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.
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.
statements;
break;
statements;
continue;
statements;
}
statements;
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;
}
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
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
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.
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.
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.
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.
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.
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.
/* 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;
}
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.
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.
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.
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:
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
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
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.
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.
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.
Here is another example of a function definition where the function print_nums takes two int values, prints them, and
returns nothing.
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.
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
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:
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 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.
• The return type: return type in the definition is int, and the value returned is stored in an int variable ‘z’.
Let us examine the flow of program execution with the example of a program given in Figure 8:
• 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.
Reading Summary
In this reading, you have learned the following:
• 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.
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.
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.
Testcase 1
The average of 4 numbers:
Enter 4 numbers: 3 5 6 8
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.
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.
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.
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.
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
• 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.
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.
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.
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.
• 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.
• 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.”
Reading Summary
• Example of a function that takes an integer value and returns its factorial
Appendix
Assignment Brief
PracticeLab2_Question1
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.
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
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.
Testcase4
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.
• Scope of the variable or the sections of code in which we can read/modify the value of the variable
• 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.
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.
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:
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.
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:
• This is the default storage class assigned to any variable that is declared inside a function or block.
• 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.
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 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.
• 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.
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.
• 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.
• 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.
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
Consider the following exercise for reflect and practice: Given a program below. Please compile and execute it to observe
the output.
Reading Summary:
• How variables of different storage classes are stored in appropriate segments inside the memory allocated to a
program on the RAM
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
#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
#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 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;
}
#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
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
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.
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.
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.
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.”
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.
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.
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.
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.
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
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.
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
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.
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
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.
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.”
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
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.
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.
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;
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).
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).
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:
Here, we declare an integer array of six elements and immediately assign those six elements to it (by making use of the
curly braces { }).
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).
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.
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
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).
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.
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:
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:
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:
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.
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.
Here is a sample main program that uses the above function to sort an array nums of integers:
Reading Summary:
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.
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:
• 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
include stdio h
int main
int n
printf Enter the number of values
scanf d n
if n
int arr n
printf Enter the values
for int i i n i
scanf d arr i
include stdio h
int i ans
int n
printf Enter the number of values
scanf d n
if n
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
include stdio h
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
include stdio h
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
include stdio h
int i j min
for i i n i
min i
for j i j n j
int n
printf Enter the number of values
scanf d n
if n
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
include stdio h
int n
printf Enter number of elements in the arra
scanf d n
if n
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
found
break
if found
else
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
count
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
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
WHAT TO LEARN
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.
a[0] a[1] a[2] a[3] a[4] a[5] [a6] [a7] [a8] [a9] Unallocated memory
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.
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.
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
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.
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.
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.
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.
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
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.
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.
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.
13 Strings
WHAT TO LEARN
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.
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.
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
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
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.
return 0;
}
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.
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.
This is best understood with the help of examples. Consider the following valid ways of initializing some 2D
arrays in C:
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 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 a[N][M]={1,2,3,4,5,6,7,8,9,10,11,12};
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.
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:
• 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
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.
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).
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.
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.
int main()
{
// Declaring and initializing the matrices and their dimensions
scanf(“%d”,&row1);
scanf(“%d”,&col1);
scanf(“%d”,&row2)
scanf(“%d”,&col2)
if(row1!=row2 || col1!=col2)
return;
for(i=0;i<row1;i++)
for(j=0;j<col1;j++)
scanf(“%d”,&M1[i][j]);
printf(“\n”);
for(i=0;i<row2;i++)
{
for(j=0;j<col2;j++)
scanf(“%d”,&M2[i][j]);
printf(“\n”);
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:
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.
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.
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
M3[i][j] = 0
4. Display matrix M3
C Program for Multiplying Two Matrices
#include <stdio.h>
#define row1 4
#define col1 3
#define row2 3
#define col2 4
#define row3 4
#define col3 4
void main()
int i, j, k;
scanf(“%d”, &M1[i][j]);
printf(“\n”);
scanf(“%d”, &M2[i][j]);
}
printf(“\n”);
if (col1 != row2)
Return;
M3[i][j] = 0;
printf(“\n”);
{
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:
• What matrix multiplication is and how it is defined in linear algebra. We understood this by learning the
operation from the first principles
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.
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):
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.
// . . . 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:
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.
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).
char name[20];
char address[100];
We declare strings by enclosing a bunch of characters within double quotes, as shown below:
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.
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:
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.
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.
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);
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()
int i, j;
if(s[i] != ‘ ’)
ws[j++] = s[i];
ws[j]=‘\0’;
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:
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.
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:
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:
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 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
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.
int main(void)
else
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.
int main(void)
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”.
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:
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.
Problem 1: Read a string from the user character by character and print its length.
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];
printf("Enter a string");
len = i-1;
if(str[left] != str[right])
flag = 0;
break;
if(flag)
else
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.
char names[5][30]; // 5 names, each having max 30 characters char words[100][20]; // 100 words, each having
max 20 characters
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:
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.
Reading Summary:
• 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.
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
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);
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;
}
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]);
}
}
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;
}
}
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);
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];
}
}
}
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
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
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);
for (int i 0; i n; i )
{
if (isalpha(str[i]))
{
alphabets ;
}
else if (isdigit(str[i]))
{
digits ;
}
else
{
others ;
}
}
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;
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)
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.
short a[3][4] = {{1, 2, 3, 4}, {11, 22, 33, 44}, {111, 222, 333, 444}};
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.
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.
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.
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.
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
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.
Transposed matrix:
1 4 7
2 5 8
3 6 9
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
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
13 Strings
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.
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.
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
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
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.
return 0;
}
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
int main(void)
{
char line[SIZE], diff = ‘a’ - ‘A’;
int i;
return 0;
}
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.
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’.
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]);
Enter a line of text: FPUTS AND SPRINTF ARE OFTEN USED TOGETHER.
fputs and sprintf are often used together.
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
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.
int main(void)
{
char line1[SIZE], line2[SIZE];
char stg1[6], stg2[5], stg3[2];
short pan_length;
return 0;
}
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
int main(void)
{
short i = 0, j;
char stg[80];
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;
}
int main(void)
{
char stg[SIZE];
short i = 0, flag = 1, count = 0;
return 0;
}
int main(void)
{
short i, j, length;
char stg[100], tmp;
return 0;
}
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;
}
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.
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 ...
int main(void)
{
char *months = “JanFebMarAprMayJunJulAugSepOctNovDec”;
char *p;
short index, len;
return 0;
}
430 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.
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.
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.
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.
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.
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.
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.
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?
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!
Reading Summary:
• 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
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.
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:
Output:
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:
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 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!).
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:
• 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:
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
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.
Question: Define a structure for representing two-dimensional (2D) grid points on a plane. Using
the structure, define functions to answer the following questions:
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:
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?
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:
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:
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.
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
WHAT TO LEARN
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.
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
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.
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.
Note: The structure tag is necessary when a structure is declared and defined at two separate places.
User-Defined Data Types 449
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);
/* 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;
}
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.
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
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.
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
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.)
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 */
return 0;
}
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.
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.
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.
EXAMINEE stud2;
fputs(“Enter name: “, stdout);
scanf(“%[^\n]”, stud2.name); /* Possible to enter multiple words */
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.
i++;
fputs(“Enter name: “, stdout);
scanf(“%[^\n]”, bat[i].name);
FLUSH_BUFFER
fputs(“Enter team: “, stdout);
scanf(“%[^\n]”, bat[i].team);
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;
}
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}
};
return 0;
}
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.
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.
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.
int main(void)
{
TIME t1, t2, t3;
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
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.
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.
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.
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.
int main(void) {
int a = 5;
return 0;
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.
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).
char *x;
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;
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);
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.
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 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++;
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
• Ways to access the value of a variable using a pointer . Steps to perform pointer arithmetic
Practice Lab 1: Pointers in C
Assignment Brief
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 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:
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.
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.)
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.
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.
You can calculate the memory address of any element by using the formula:
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:
But we can also store a string as a pointer to the first character of the string:
*(str + i)
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)
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.
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.
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]
int a = 10;
int b = 20;
arr[0] = &a;
arr[1] = &b;
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.
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]
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:
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().
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.
• 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.
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;
student *ptr;
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;
};
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
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.
*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.
*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.
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:
char name[20];
int age;
} 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:
char name[20];
int age;
} 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
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:
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.
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.
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.
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.
*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.
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)
x[i] = x[i] + 1;
int main()
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:
• 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
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.
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 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.
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."
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.
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:
int length;
int width;
} rectangle;
int area(rectangle r) {
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:
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.
(*r).width *= 2;
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:
double_width(&r);
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.
#define NULL 0
So, if you want to declare a pointer that is not pointing to anything, you can do it like this:
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)
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:
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:
- cost_weight_ratio: the cost per kilogram of shipping the package in dollars that this package represents.
• Calculates the cost per kilogram for each package and stores it in the cost_weight_ratio field
• 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
12 Pointers
WHAT TO LEARN
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.
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.
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.
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;).
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
Value: 420
Address: 1234
Value: 1234/1238
Value: 840
Address: 2345
Address: 1238
Pointer variable int variables
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
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
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.
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.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.
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.
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
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.
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.
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;
}
int main(void)
{
TIME t1, t2, t3;
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
• How to dynamically allocate and deallocate memory using malloc, calloc, realloc, and free
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.
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.
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:
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.
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.
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 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:
• 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: -
• 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
• 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.
• Complete order: // This function should free the memory allocated to the order
• Destroy pizza: // This function should free the memory allocated to the pizza
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.
Stack
Heap
Data
Program Code
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.
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
#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 */
}
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.
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.
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.
#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;
}
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.
** 1 2 3 4 5
*
11 22 33 44 55
*
55 66 77 88 99
*
int main(void)
{
short rows, columns, i, j;
short **p; /* Signifies a 2D array p[ ][ ] */
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);
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);
}
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.
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.
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
num = i;
if (i > 0) {
fputs(“Printing array elements ...\n”, stderr);
for (i = 0; i < num; i++)
printf(“%d “, p[i]);
}
exit(0);
}
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.
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:
Mode Abbreviation
Read r
Write w
Here is an example of opening a file called foo.txt in read-only mode (that is, to take input):
• 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.
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.
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:
• fputc:
• fgets:
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:
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?
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:
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
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
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.
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.
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.
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.
Name: Potato
Price: 30.56
Quantity: 20
Name: Rice
Price: 13.45
Quantity: 6
Name: Apple
Price: 22.45
Quantity: 100
Name: Oil
Price: 101.34
Quantity: 12
Name: Watermelon
Price: 88.65
Quantity: 22
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
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.
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
15 File Handling
WHAT TO LEARN
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.
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.
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.
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.
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.
/* 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];
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
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
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.
int main(void)
{
FILE *fp;
int c, x;
char buf[80] = “A1234 abcd”;
char stg[80];
exit(0);
}
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).
/* 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;
fclose(fp1);
fclose(fp2);
exit(0);
}
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.
/* 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);
}
int main(void)
{
FILE *fp;
short i, size, roll_no, marks;
char buf[BUFSIZE], name[20];
rewind(fp);
Juhi Agarwal 11 97
Paridhi Jalan 22 80
Shiksha Daga 33 92
Paul Oomen 44 100
Abhijith 55 96
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.
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.
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.
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.
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.