0% found this document useful (0 votes)
2 views39 pages

Module 4 C Programming

This document covers key concepts in C programming, focusing on functions, their structure, scope, and how to pass arguments using call by value and call by reference. It also explains the use of command line arguments in the main function through argc and argv, as well as the return statement in functions. Additionally, it highlights the importance of understanding function parameters and the implications of using pointers.

Uploaded by

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

Module 4 C Programming

This document covers key concepts in C programming, focusing on functions, their structure, scope, and how to pass arguments using call by value and call by reference. It also explains the use of command line arguments in the main function through argc and argv, as well as the return statement in functions. Additionally, it highlights the importance of understanding function parameters and the implications of using pointers.

Uploaded by

bhajantri044
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Programming in C - 1BEIT105/205

Module 4

Functions: The General Form of a Function, Understanding the Scope of a Function,

Function Arguments, argc and argv—Arguments to main(), The return Statement, What

Does main() Return?, Recursion, Function Prototypes, Declaring Variable Length

Parameter Declarations, The inline Keyword.

Pointers (Contd…): Pointers to Functions, C's Dynamic Allocation Functions.

Functions

➤ Functions are the building blocks of C and the place where all program activity occurs.
➤ This chapter examines their features, including function arguments, return values, prototypes, and
recursion.

The General Form of a Function

The general form of a function is:

ret-type function-name(parameter list)


{
body of the function
}
➤ The ret-type specifies the type of data that the function returns.
➤ A function may return any type of data except an array.
➤ The parameter list is a comma-separated list of variable names and their associated types.
➤ The parameters receive the values of the arguments when the function is called.

➤ A function can be without parameters, in which case the parameter list is empty.
Programming in C - 1BEIT105/205

➤ An empty parameter list can be explicitly specified as such by placing the keyword void inside the
parentheses.

➤ In variable declarations, you can declare several variables to be of the same type by using a comma-
separated list of variable names.

➤ In contrast, all function parameters must be declared individually, each including both the type and
name.

➤ That is, the parameter declaration list for a function takes this general form:

f(type varname1, type varname2, . . . , type varnameN)

For example, here are a correct and an incorrect function parameter declaration:

f(int i, int k, int j) /* correct */


f(int i, k, float j) /* wrong, k must have its own type
specifier */

Understanding the Scope of a Function

➤ Scope rules determine whether a piece of code can access another piece of code or data.

➤ Each function defines a block scope because a function is a discrete block of code.

➤ The code inside a function is private to that function and cannot be accessed by other functions
except through a function call.

➤ You cannot use goto to jump into another function because its code is hidden from the rest of the
program.

➤ Unless a function uses global variables, it cannot affect or be affected by any other part of the
program outside its scope.

➤ Local variables are variables defined inside a function.


Programming in C - 1BEIT105/205

➤ A local variable is created when the function is entered and destroyed when the function exits.

➤ Local variables cannot retain their values between function calls.

➤ Exception: A local variable declared with static retains its value between calls.

• Stored like a global variable, but the scope is limited to the function.

➤ Formal parameters (function parameters) also belong to the function’s scope.

• They exist only during the function call.

➤ All functions have file scope, meaning:

• A function cannot be defined inside another function.

➤ C is not a block-structured language for this reason.

Function Arguments

If a function is to accept arguments, it must declare the parameters that will receive the values of the
arguments. As shown in the following function, the parameter declarations occur after the function
name.

/* Return 1 if c is part of string s; 0 otherwise. */


int is_in(char *s, char c)
{
while (*s)
if (*s == c) return 1;
else s++;
return 0;
}

The function is_in() has two parameters: s and c. This function returns 1 if the character c is part
of the string s; otherwise, it returns 0.

➤ Parameters receive the values of arguments when the function is called.

➤ Formal parameters behave like normal local variables:


• They can be assigned new values.
• They can be used in expressions.

Call by Value, Call by Reference


Programming in C - 1BEIT105/205

➤ In a computer language, there are two ways that arguments can be passed to a subroutine.

➤ The first is call by value. This method copies the value of an argument into the formal parameter of
the subroutine. In this case, changes made to the parameter do not affect the argument.

➤ Call by reference is the second way of passing arguments to a subroutine. In this method, the
address of an argument is copied into the parameter. Inside the subroutine, the address is used to access
the actual argument used in the call. This means that changes made to the parameter affect the
argument.

➤ With few exceptions, C uses call by value to pass arguments. In general, this means that code within
a function cannot alter the arguments used to call the function.

#include <stdio.h>
int sqr(int x);
int main(void)
{
int t = 10;
printf("%d %d", sqr(t), t);
return 0;
}
int sqr(int x)
{
x = x * x;
return(x);
}

• In this example, the value of the argument to sqr(), 10, is copied into the parameter x. When the
assignment x = x * x takes place, only the local variable x is modified. The variable t, used to call
sqr(), still has the value 10.

• Hence, the output is 100 10.

Quiz time

Which of these defines a function correctly?

A) function add()
B) int add(a, b)
C) int add(int x, int y)
D) function int add()
Programming in C - 1BEIT105/205

Creating a Call by Reference

➤ Even though C uses call by value for passing parameters, you can create a call by reference by
passing a pointer to an argument, instead of passing the argument itself.

➤ Since the address of the argument is passed to the function, code within the function can change the
value of the argument outside the function.

➤ Pointers are passed to functions just like any other argument. Of course, you need to declare the
parameters as pointer types. For example, the function swap(), which exchanges the values of the
two integer variables pointed to by its arguments, shows how:

void swap(int *x, int *y)


{
int temp;

temp = *x; /* save the value at address x */


*x = *y; /* put y into x */
*y = temp; /* put x into y */
}

The swap() function is able to exchange the values of the two variables pointed to by x and y
because their addresses (not their values) are passed. Within the function, the contents of the variables
are accessed using standard pointer operations, and their values are swapped.

Remember that swap() (or any other function that uses pointer parameters) must be called with the
addresses of the arguments. The following program shows the correct way to call swap():

#include <stdio.h>
void swap(int *x, int *y);
int main(void)
{
int i, j;
i = 10;
j = 20;

printf("i and j before swapping: %d %d\n", i, j);

swap(&i, &j); /* pass the addresses of i and j */


Programming in C - 1BEIT105/205

printf("i and j after swapping: %d %d\n", i, j);

return 0;
}

void swap(int *x, int *y)


{
int temp;
temp = *x; /* save the value at address x */
*x = *y; /* put y into x */
*y = temp; /* put x into y */
}

The output from this program is shown here:

i and j before swapping: 10 20


i and j after swapping: 20 10

In the program, i = 10 and j = 20. Then swap() is called with the addresses of i and j. (The
unary operator & is used to produce the address of the variables.) Therefore, the addresses of i and j,
not their values, are passed into the function swap().

Important Note: C++ allows you to fully automate a call by reference through the use of reference
parameters. Reference parameters are not supported by C.

Calling Functions with Arrays

➤ When an array is used as a function argument, its address is passed to a function.

➤ This is an exception to the call-by-value parameter passing convention.

➤ In this case, the code inside the function is operating on, and potentially altering, the actual contents
of the array used to call the function.

➤ For example, consider the function print_upper(), which prints its string argument in

uppercase:
#include <stdio.h>
#include <ctype.h>
void print_upper(char *string);
int main(void)
{
char s[80];
printf("Enter a string: ");
Programming in C - 1BEIT105/205

gets(s);
print_upper(s);
printf("\ns is now uppercase: %s", s);

return 0;
}
/* Print a string in uppercase. */
void print_upper(char *string)
{
register int t;
for(t = 0; string[t]; ++t) {
string[t] = toupper(string[t]);
putchar(string[t]);
}
}

Here is sample output:

Enter a string: This is a test.


THIS IS A TEST.
s is now uppercase: THIS IS A TEST.

After the call to print_upper(), the contents of array s in main() are changed to uppercase.

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

void print_upper(char *string);

int main(void)
{
char s[80];
printf("Enter a string: ");
gets(s);
print_upper(s);
printf("\ns is unchanged: %s", s);

return 0;
}

void print_upper(char *string)


{
register int t;
for(t = 0; string[t]; ++t)
putchar(toupper(string[t]));
}
Programming in C - 1BEIT105/205

Here is sample output from this version of the program:

Enter a string: This is a test.


THIS IS A TEST.
s is unchanged: This is a test.

In this case, the contents of array s remain unchanged because its values are not altered inside
print_upper().

The standard library function gets() is a classic example of passing arrays into functions.

1. Function Principle: Arrays and Pointers

• Pass-by-Address: When a character array is passed to xgets(char *s), the array name
automatically becomes a pointer to its starting memory location.

• Direct Change: Since the function has the array's address, it writes the input characters directly into
the original array.

• Safety Warning: C does not check the array size. The user is responsible for making sure the array is
large enough (at least 80 characters) to prevent a program crash.

2. How xgets() Handles Input

• Character Storage: Typed characters are stored sequentially in the array (s[t] = ch;).
• Limiting Input: The function uses a loop (t < 80) to stop input after 80 characters.
• Backspace Effect: Typing a backspace (\b) reduces the counter (t--), effectively erasing the last
character entered.
• End of String: Pressing ENTER (\n) terminates the string by placing a null character (\0) at the
current position.

argc and argv — Arguments to main()

➤ Sometimes, we need to pass information into a program when it starts running.


➤ This is typically done using Command Line Arguments (CLAs).
➤ A CLA is the information that follows the program's name on the operating system's command
line.
➤ For example, when you compile a program, you might type something like the following after the
command prompt:
cc program_name
➤ Here, program_name is a command-line argument that specifies the name of the program you
wish to compile.
Programming in C - 1BEIT105/205

Two Special Built-in Arguments for main()

➤ Two parameters are used to receive command line arguments in the main() function:

Argument Type Purpose

Holds the number of arguments on the


argc int (Integer)
command line

char *argv[] (Array of character Points to the actual command line


argv
pointers) arguments (strings)

Key Facts about argc

➤ argc is always at least 1, because the name of the program itself is counted as the first argument.

Key Facts about argv

➤ All command line arguments are received as strings.

➤ Any numbers passed must be manually converted inside the program (e.g., using atoi()).

The Standard Declaration of main()

int main(int argc, char *argv[])

➤ The most common way to declare the argument array is:

char *argv[];

➤ The empty brackets [] indicate that the array is of undetermined length (its size is determined by
argc).

➤ The names argc and argv are traditional but arbitrary; you can name them anything you like.

When No Arguments are Required


Programming in C - 1BEIT105/205

➤ If your program does not require command line arguments, it's good practice to declare main()
with void:

int main(void)

Accessing Arguments with argv

➤ Accessing Individual Arguments

• You access individual arguments by indexing argv.


• Each element in argv is a pointer to a string.

Index Content Description


argv[0] First string Always the program's name
argv[1] Second string The first actual command line argument
argv[2] Third string The second command line argument, and so on

➤ Accessing Individual Characters

• To access a single character within an argument, use a second index:

argv[t][i]

• First index (t): Selects the string (the argument).


• Second index (i): Selects the character within that string.

Example 1 – A Simple Greeting Program

➤ Program Goal: To print "Hello" and a name, where the name is passed as a command-line
argument.

int main(int argc, char *argv[])


{
// Check if exactly 2 arguments were provided (program name + 1
name)
if (argc != 2) {
printf("You forgot to type your name.\n");
exit(1);
}

// argv[1] is the first argument (the user's name)


printf("Hello %s\n", argv[1]);
Programming in C - 1BEIT105/205

return 0;
}

➤ Execution: If the compiled program is named name and you run:

name Tom

➤ Output:

Hello Tom

Argument Separators and Quotation

➤ Separating Arguments

• In most environments, command line arguments must be separated by a space or a tab.

• Characters like commas ( , ) and semicolons ( ; ) are not generally considered separators.

➤ Examples

Input Resulting Arguments (argc) Strings in argv


4 arguments (argv[0] is program
run Spot, run run, Spot,, run
name)
2 arguments (argv[0] is program Herb,Rick,Fred (single
Herb,Rick,Fred
name) string)

➤ Using Double Quotes

• Some operating systems allow enclosing a string with spaces in double quotes (" ").

• This causes the entire quoted string to be treated as a single argument.

• Always check your operating system’s documentation for exact behavior.

Why use argc and argv?

➤ You typically use command-line arguments to pass initial commands needed at program startup.
Programming in C - 1BEIT105/205

➤ Common Uses

• Specifying a filename for the program to operate on.

• Passing a program option (e.g., -v for verbose mode).

• Defining an alternate behavior for the program.

➤ Checking for Missing Arguments

• A program that uses command-line arguments should always check argc to ensure the required
information was provided.

• If arguments are missing, the program should:

1. Print a helpful error message and instructions.


2. Exit gracefully (e.g., using exit(1)).

Quiz time

What is the result of the following C expression, assuming int a = 5; and int b = 2;?
a / b

a. 2.5
b. 3
c. 2
d. 1

Exam tip
Understand argc and argv[0]! Remember that argc (argument count) is always at least 1, and
argv[0] always holds the name of the executable file itself. This is a common point of confusion
and a frequent source of errors in exams.

The return Statement

➤ return has two important uses.

• First, it causes an immediate exit from the function. That is, it causes program execution to return to
the calling code.

• Second, it can be used to return a value.

Returning from a Function


Programming in C - 1BEIT105/205

➤ A function's execution terminates, and it returns control to the calling code (the caller) in one of two
fundamental ways.

Method 1: Executing the Last Statement

• When it occurs: The function returns when the last statement in the function body has executed.

• Conceptually: You can think of this as the function's ending curly brace {} being "encountered"
(though the brace isn't literally present in the object code).

• Behavior: The function simply stops executing and returns to the point in the caller from which it
was invoked.

Example: pr_reverse()

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

void pr_reverse(char *s);

int main(void)
{
pr_reverse("I like C");

return 0;
}
void pr_reverse(char *s)
{
register int t;

for (t = strlen(s) - 1; t >= 0; t--)


putchar(s[t]);
}

• Once the string is displayed, there is nothing left for pr_reverse() to do, so it returns to main()
(the caller).

• Note: This default method is less common in practice.

Method 2: Using the return Statement

• Most Common Method: Most functions use the return statement to stop execution.
Programming in C - 1BEIT105/205

• Reasons for use:


▪ A value must be returned to the caller (e.g., in a function that doesn't have a void return type).
▪ To make the function's code simpler and more efficient by allowing early exit.

Key Rule:

• A function may contain several return statements.

• The first return statement that is executed immediately halts the function's execution and transfers
control back to the caller.

➤ Example of Multiple return Statements

Example: find_substr()

This function uses two return statements for different outcomes.

Scenario Condition Return Value Purpose

Substring (s2) is found t (the starting The function is complete and


Success
within the string (s1). index) successful; exit immediately.

Loop finishes without Signifies that no match was found;


Failure -1
finding the substring. this is the final exit point.

int find_substr(char *s1, char *s2)


{
// ... loop logic to check for a match ...

if (!*p2)
return t; /* 1st return: Substring found! */

// ... rest of the loop/function body ...

return -1; /* 2nd return: No match found after checking all


possibilities */
}

Using multiple return statements here keeps the logic clean: success exits immediately, and the default
failure return is placed at the end.

Method Trigger When to Use Typical Return Type

Execution reaches Rare, usually for simple void


1. Default the conceptual functions with no need for early void
ending brace {}. exit.
Programming in C - 1BEIT105/205

Method Trigger When to Use Typical Return Type

Standard practice, essential when Any type (including


2. Explicit A return statement is returning a value, or when void for functions
(return) executed. simplifying flow control with early without a value to
exits. return)

The return statement is the most versatile and frequently used method to terminate a
function.

Returning Values

➤ All functions, except those of type void, are designed to return a value to the caller.

➤ This returned value is explicitly specified by the return statement.

Standard Non-void Function Behaviour Implication

If return is used without a value, a garbage value Bad practice; leads to


C89
is returned. unpredictable results.

C99 / Any explicit return statement must return a Enforces better coding
C++ value. practices.

➤ If execution reaches the end of a non-void function (hits the closing }), a garbage value is still
returned. This is not a syntax error, but a serious programming flaw.

Functions as Operands
▪ As long as a function is not declared as void, you can use its call as an operand in any C
expression.
▪ The compiler treats the function call as the value it returns.
Valid Expression Description
x = power(y); The returned value from power(y) is assigned to x.
if (max(x, y) > 100) The returned value from max(x, y) is compared to 100.
for (ch = getchar(); getchar() returns a character, which is checked by
isdigit(ch); ) isdigit().

General Rule: A function call cannot be on the left side of an assignment operator (=).

INCORRECT STATEMENT:

swap(x, y) = 100;
Programming in C - 1BEIT105/205

• The C compiler will flag this as an error.

• A function call represents a value, not a memory location (lvalue) that can be assigned to.

Three Types of C Functions: categorize Functions by Their Return Purpose

Type Purpose Example & Return Value

Standard library functions like


1. Computational Performs an operation on arguments and
sqrt() (returns the square root) or
(Pure) returns a value based on the calculation.
sin() (returns the sine).

Manipulates information and returns a value Standard library function fclose():


2. Status-
that simply indicates the success or failure Returns 0 on success; returns EOF on
Reporting
of the manipulation. error.

Performs actions (side effects) but has no Standard library function exit()
3. Procedural explicit return value (or the value is (terminates the program). Must be
unimportant). declared as void.

Key Declaration:
All functions that do not return values must be declared as returning type void. This
prevents accidental misuse in expressions.

Using vs. Discarding the Return Value

▪ Even if a function returns a value (like printf() returns the number of characters written), you are
not required to use it.

▪ The common question: "Do I have to assign this value to some variable?"

• Answer: No. If no assignment or use is specified, the return value is simply discarded (lost).

Example: The mul() Function


Programming in C - 1BEIT105/205

#include <stdio.h>

int mul(int a, int b);

int main(void)
{
int x, y, z;

x = 10;
y = 20;

z = mul(x, y); /* 1 */
printf("%d", mul(x,y)); /* 2 */
mul(x, y); /* 3 */

return 0;
}

int mul(int a, int b)


{
return a * b;
}

• 1: Return value is stored in variable z.


• 2: Return value is used directly in printf().
• 3: Return value is ignored (discarded).

Returning Pointers

▪ Pointers are memory addresses of a certain type of data.

▪ Pointers are NOT Integers: They are fundamentally different from int or unsigned int.

Pointer Arithmetic is Type-Relative

▪ When you increment or decrement a pointer, the change in the memory address is relative to the size
of the base type it points to.

Example:
• If an int * (integer pointer) is incremented, the address increases by sizeof(int) (e.g., 4 bytes).

• If a char * (character pointer) is incremented, the address increases by sizeof(char) (1 byte).


Programming in C - 1BEIT105/205

Crucial Requirement

▪ For the compiler to handle pointer arithmetic correctly, a function that returns a pointer must
explicitly declare the exact type of pointer it is returning.

Function Declaration Rules

To return a pointer, a function must be declared with a pointer return type.


The asterisk (*) follows the base type.

Table: Rules and Examples

Rule Example Incorrect Usage

Type A function returning a pointer to a char DO NOT use int * to return a


Matching must be declared as char *. char * pointer!

Generic If a function needs to return a pointer of an Use void * (generic pointer


Pointers unknown or generic type. type).

Example Function: match()

This function returns a pointer to the first occurrence of a character:

/* Return pointer of first occurrence of c in s. */


char *match(char c, char *s) // Return type is 'char *'
{
// Loop stops if character c is found OR if the null terminator
is reached
while (c != *s && *s)
s++;

return s; // 's' now holds the address of the match or the null
terminator
}

How the match() Function is Used

▪ The returned pointer is assigned to another pointer variable in the calling function (main):

char s[80], *p, ch; // p is a char pointer

// ... input code ...

p = match(ch, s); // p receives the address returned by match()


Programming in C - 1BEIT105/205

Outcome 1: Match Found

▪ p points to the character ch within the string s.

▪ Since p points to a non-null character, *p is true.

▪ The program prints the string starting from the point of the match
(%s starts printing from the address in p).

Outcome 2: No Match Found

▪ The while loop in match() terminates when it reaches the null terminator (\0) of the string.

▪ p points to the null terminator.

▪ In C, the null terminator (\0) evaluates to false (zero). Therefore, *p is false.

▪ The program executes the else block and prints "No match found."

Here is the same program written clearly:

#include <stdio.h>

char *match(char c, char *s); /* prototype */

int main(void)
{
char s[80], *p, ch;

gets(s);
ch = getchar();
p = match(ch, s);

if (*p) /* there is a match */


printf("%s", p);
else
printf("No match found.");

return 0;
}
Programming in C - 1BEIT105/205

Functions of Type void

What is a void Function?

▪ The void keyword is used to explicitly declare functions that do not return values.

▪ Primary Goal: To prevent the function from being used in any expression, which helps avoid
accidental misuse.

Accidental Misuse Example (What void prevents)

Incorrect Usage (if not void) Problem

The function call is mistakenly used as a Boolean


if condition. If the function returned a garbage value (or
(print_vertical("hello")) defaulted to int), the condition might be non-zero and
evaluate as true, leading to unpredictable behavior.

Procedural Function Example (void)

▪ The function print_vertical() is designed to perform a procedural task: printing a string


vertically.

▪ It does not compute a result or report a status code that needs to be returned.

▪ Therefore, it must be declared as void:

void print_vertical(char *str)


{
while (*str)
printf("%c\n", *str++);
}

Usage Example in main()

int main(int argc, char *argv[])


{
// Function call is a standalone statement, not part of an
expression.
if (argc > 1)
print_vertical(argv[1]);

return 0;
}
Programming in C - 1BEIT105/205

Historical Context (Early C)

▪ The Problem: Early versions of the C language did not define the void keyword.

▪ In older C programs, functions that did not return values simply defaulted to type int, even though
no value was actually returned.

▪ Consequence: This practice was problematic because it allowed these functions to be mistakenly
used in expressions, where they would effectively contribute a garbage integer value.

Modern C Practice (Since C89)

▪ Always use void to declare functions that perform actions but do not return a meaningful value.

▪ This is a fundamental safeguard against programming errors.

What does main() Return?

▪ The main() function returns an integer to the calling process, which is usually the operating
system.

▪ Returning a value from main() is equivalent to calling exit() with the same value.

▪ If main() does not explicitly return a value, the value passed to the calling process is technically
undefined.

▪ In practice, most C compilers automatically return 0, but you should not rely on this if portability is
important.

Quiz time

What is the primary reason for declaring a function with a void return type in C?

A) To allow the function to return a memory address.


B) To make the function default to an integer return type.
C) To ensure the function runs faster than other types.
D) To explicitly prevent the function from being used as an operand in an expression.

Correct Answer: D)

Declaring a function with a void return type clearly tells that the function does not give back any
value.

Since it does not give a value:


Programming in C - 1BEIT105/205

▪ It cannot be used in calculations


▪ It cannot be treated like a number or data
▪ It is meant only to perform an action

So, using void prevents the function from being used where a value is expected, such as in
expressions.

Exam tip:

Always ensure the function prototype (the declaration at the top) exactly matches the function
definition (the actual code block). Pay close attention to the return type and the number and type of
arguments for both. Inconsistent declarations are a major source of compiler errors.

Recursion
▪ Definition: A function is said to be recursive when it calls itself.

▪ This is the process of defining something in terms of itself, sometimes called a circular definition.

▪ In C, a function can call itself.

Simple Example

▪ The function factr() computes the factorial of an integer (n!).

▪ The factorial of a number n is the product of all the whole numbers between 1 and n.

▪ Example:
3 factorial = 1 × 2 × 3 = 6

1. Recursive Version: factr(int n)

/* recursive */
int factr(int n)
{
int answer;

if(n == 1) return(1); // Base Case: Stops the recursion

answer = factr(n - 1) * n; /* recursive call */

return(answer);
}
Programming in C - 1BEIT105/205

2. Non-Recursive Version: fact(int n) (Iterative)

/* non-recursive */
int fact(int n)
{
int t, answer;

answer = 1;

for(t = 1; t <= n; t++)


answer = answer * t;

return(answer);
}

▪ The iterative version uses a loop that runs from 1 to n and progressively multiplies each number.

The Operation of factr()

▪ When n = 1: The function returns 1 (the base case).

▪ Otherwise: It returns the product of factr(n - 1) * n.


▪ To evaluate this, factr() is called again with n - 1.
▪ This continues until n equals 1, and then the calls begin returning values.

Trace Example: Computing 3!

Step Call Expression Action


1 factr(3) factr(2) * 3 Calls factr(2)
2 factr(2) factr(1) * 2 Calls factr(1)
3 factr(1) Returns 1 Base Case Hit!
4 factr(2) Evaluates 1 × 2 = 2 Returns 2
5 factr(3) Evaluates 2 × 3 = 6 Returns 6

Memory and Execution: The Call Stack

▪ New Variables: When a function calls itself, a new set of local variables and parameters are
allocated storage on the stack.
Programming in C - 1BEIT105/205

▪ A recursive call does not make a new copy of the function. Only the values being operated upon
are new.

▪ Execution Flow: The function code is executed from the top with these new variables.

▪ Telescoping: As each recursive call returns, the old local variables and parameters are removed
from the stack, and execution resumes immediately after the recursive call inside the function.

▪ Recursive functions are said to “telescope” out and back.

1. Disadvantages of Recursion (Overhead)

▪ Slower Execution: Recursive routines often execute a bit slower than their iterative equivalents.
▪ Reason: Overhead of repeated function calls (managing stack frames).

▪ Memory/Code: Recursive routines seldom significantly reduce code size or improve memory
utilization.

2. The Danger: Stack Overrun

▪ Cause: Storage for function parameters and local variables is on the stack, and each new call creates
a new copy.

▪ Risk: Many recursive calls could exhaust the stack memory, causing a stack overrun.

▪ Result: A stack overrun usually causes a program to crash when a recursive function runs wild (i.e.,
doesn’t stop).

Main Advantage

▪ Clarity and Simplicity: You can use recursion to create clearer and simpler versions of several
complex algorithms.
▪ Example: The quicksort algorithm is difficult to implement in an iterative way.

▪ Problem Suitability: Some problems, especially ones related to artificial intelligence, lend
themselves naturally to recursive solutions.

▪ Personal Preference: Some people seem to think recursively more easily than iteratively.

The Essential Rule


Programming in C - 1BEIT105/205

▪ Termination Condition: You must have a conditional statement (such as an if) somewhere to
force the function to return without the recursive call being executed. (This is the Base Case.)

▪ The Error: Omitting the conditional statement is a common error. If you don’t, the function will
never return once you call it, leading to a stack overrun.

▪ Debugging Tip: Use printf() liberally during program development so that you can watch what
is going on and abort execution if you see a mistake.

Function Prototypes

▪ In modern, properly written C programs, all functions must be declared before they are used. This
is done using a function prototype.

▪ History: Prototypes were added to C by the C89 standard (they were not in the original language).

Requirement Status

▪ C: Technically optional (for compatibility), but strongly encouraged.

▪ C++: Prototypes are required for every function.

▪ Prototypes enable the compiler to provide stronger type checking, similar to Pascal.

▪ This allows the compiler to find and report errors such as:

1. Questionable type conversions between arguments and parameters.


2. Differences in the number of arguments used in the call versus the number of parameters in
the function.

General Form of a Function Prototype

type func_name(type parm_name1, type parm_name2, ..., type


parm_nameN);

▪ The use of parameter names is optional. However, they help the compiler identify type mismatches,
so it is a good idea to include them.
Programming in C - 1BEIT105/205

Example: Catching a Type Mismatch

▪ The following program illustrates the value of prototypes by generating an error:

/* This program uses a function prototype to enforce strong type


checking */

void sqr_it(int *i); /* prototype: expects an integer POINTER */

int main(void)
{
int x;

x = 10;

sqr_it(x); /* ERROR: Type mismatch—passing an integer (x) */

return 0;
}

void sqr_it(int *i)


{
*i = *i * *i;
}

Result

▪ The compiler reports an error because sqr_it() is called with an integer argument (x) instead of
the required integer pointer (int *).

Function Definitions and Prototypes

▪ A function’s definition can serve as its prototype if the definition occurs before the function’s
first use.

▪ Example: If

void f(int a, int b) { ... }


Programming in C - 1BEIT105/205

is defined before main() calls f(), no separate prototype is needed.

Practice

▪ While possible in small programs, a separate prototype is normally used in large, multi-file
programs.
▪ This is the standard way C code is written.

The main() Exception

▪ The only function that does not require a prototype is main() because it is the first function
called when your program begins.

Old C Code and Porting

▪ Since early versions of C did not support full prototype syntax, prototypes are technically optional
in C to support older pre-prototype C code.

▪ Porting: If moving old C code to C++, you must add full function prototypes before it will
compile, since C++ requires them.

Functions with No Parameters (C vs C++)

▪ There is a small but important difference in how C and C++ declare (prototype) a function that has
no parameters.

C++ Approach

▪ An empty parameter list means no parameters.

int f(); // C++: function with NO parameters

C Approach

▪ In C,

int f();
Programming in C - 1BEIT105/205

means no parameter information is given (not necessarily zero parameters).

▪ The compiler assumes the function could have parameters or none (old-style declaration).

Correct C Prototype for No Parameters

▪ You must use void inside the parameter list:

float f(void); // C: function with NO parameters

▪ This explicitly tells the C compiler that the function has no parameters, so any call with arguments
will produce an error.

C++ Note

▪ In C++, using void in an empty parameter list is allowed but redundant.

Final Conclusion

▪ Function prototypes help you catch bugs early and ensure your program works correctly.

Old-Style Function Declarations

 In the early days of C, prior to the creation of function prototypes, there was still a need to
inform the compiler about a function’s return type in advance.
 Why?
The size of the return type needs to be known so the proper code can be generated when the
function is called (since sizes of different data types differ).
 This was accomplished using a function declaration that did not contain any parameter
information.
 Status:
This approach is considered archaic by today’s standards and should not be used for new
code.
 Relevance:
It can still be found in older code, making it important to understand how it works.

Old-Style Function Declaration (General Form)

 The old-style function declaration statement has the following general form:

type_specifier function_name();
Programming in C - 1BEIT105/205

 Notice that the parameter list is empty. Even if the function takes arguments, none are listed in
its type declaration.

What It Tells the Compiler

 The old-style function declaration only tells the compiler about the function’s return type and
its name.
 Crucially: It does not say anything about the parameters the function takes.

Illustrative Program

 Using the old-style approach, the function’s return type and name are declared near the start of
the program.

#include <stdio.h>

double div(); /* old-style function declaration */

int main(void)
{
// The compiler knows div() returns a double, but knows nothing
about its arguments.
printf("%f", div(10.2, 20.0));
return 0;
}

double div(double num, double denom)


{
return num / denom;
}

 The declaration double div(); tells the compiler that div() returns an object of type
double.
 This allows the compiler to correctly generate code for calls to div().

 The old-style function declaration is outmoded and should not be used for new code.
 This old approach is incompatible with C++. C++ requires full function prototypes that
specify both the return type and the parameter list.

Standard Library Function Prototypes

 Any standard library function used by your program must be prototyped.


 To accomplish this, you must include the appropriate header for each library function. All
necessary headers are provided by the C compiler.
 In C, the library headers are (usually) files that use the .h extension.
Programming in C - 1BEIT105/205

 A header contains two main elements:


o any definitions used by the library functions
o the prototypes for the library functions

Declaring Variable Length Parameter Lists

 C allows you to define a function that accepts a variable number of parameters.


 The most common example of this is the standard library function printf().

How to Declare

 To inform the compiler that an unknown number of arguments will be passed, you must
terminate the parameter list with three periods (...).
 This applies to both the function’s prototype and its definition.

Syntax Example

 This prototype specifies that func() requires at least two fixed integer parameters (a and b),
followed by an unknown number (including zero) of additional parameters:

int func(int a, int b, ...);

The Rule

 A function using a variable number of parameters must have at least one actual, fixed
parameter.

Illegal

 Declaring only the ellipsis is incorrect:

int func(...); /* illegal */

The "Implicit int" Rule

 Definition: The original C language included the "implicit int" rule (also called the "default
to int" rule).
 Rule: If a type specifier was not explicitly provided, the type int was assumed.

Historical Use

 This was most commonly used for the return type of functions in older code.
 For a function returning int:
o Modern (Explicit):

int f(void) { /* ... */ return 0; }


Programming in C - 1BEIT105/205

o Old Style (Implicit):

f(void) { /* return type int by default */ /* ... */


return 0; }

Broader Application (C89 and Earlier)

 The rule applied to function return values, parameters, and local variables:

/* Here, the return type of f() defaults to int; so do the types


of parameters a and b, and local variable c. */

f(register a, register b) {
register c;
c = a + b;
/* ... */
return c;
}

Notes

 The implicit int rule was eliminated by the C99 standard.


 It is not supported by C++.
 Recommendation: Because it is obsolete, its use is discouraged. It is best to explicitly specify
every type used by your program.

Old-Style vs. Modern Function Parameter Declarations

 The Forms: Early versions of C used a different parameter declaration method than modern
versions (C89, C99, and C++).
 Old Form: Sometimes called the Classic Form.
 Modern Form: The approach used by this book and recommended by Standard C.
 Standard C supports both forms, but strongly recommends the modern form.
 C++ supports only the modern parameter declaration method.
 Why Learn Old Style? You should know the old-style form because many older C programs
still use it.

 The old-style method consists of two separate parts:

1. Parameter List (Inside Parentheses)

 A list of parameter names goes inside the parentheses following the function name.
 Note: This list contains names only, no types.

2. Parameter Declarations (Before the Brace)


Programming in C - 1BEIT105/205

 The actual parameter declarations (the types) go between the closing parentheses and the
function’s opening curly brace ({}).
 The general form of the old-style parameter definition is:

type func_name(parm1, parm2, . . . parmN)


type parm1;
type parm2;
.
.
type parmN;
{
function code
}

1. Modern Function Declaration

 All types and names are declared within the parentheses.

float f(int a, int b, char ch)


{
/* . . . */
}

2. Old-Style Function Declaration

 The same function looks like this in the classic form:

float f(a, b, ch) // Parameter names listed here


int a, b; // Types declared here (can declare multiple of the
same type)
char ch; // Another type declared here
{
/* . . . */
}

Key Difference

 The old-style form allows the declaration of more than one parameter in a list after the type
name (e.g., int a, b;).

Note

 The old-style form of parameter declaration is designated as obsolete by Standard C and is


not supported by C++.
Programming in C - 1BEIT105/205

The inline Keyword

 C99 has added the keyword inline, which applies to functions.


 By preceding a function declaration with inline, you are telling the compiler to optimize
calls to the function.
 Typically, this means that the function’s code will be expanded in line, rather than called.
 However, inline is only a request to the compiler, and it can be ignored.
 NOTE: The inline specifier is also supported by C++.

Inline Function vs Normal Function

Inline Function Normal Function

Defined with the inline keyword Defined without the inline keyword

Function call is replaced by the function’s Function call involves a normal call with a
code (inline substitution) stack push

Can improve performance by eliminating May have overhead due to function call and
function call overhead return mechanisms

May increase binary size if overused (due to Doesn’t affect code size as much, uses
repeated code) memory only for function calls

Quiz Time

In modern C, what is the explicit way to declare a function's prototype when it accepts no
parameters?

A) int myFunction();
B) int myFunction(void);
C) int myFunction(0);

Exam tip

Before writing a single line of actual C code, write out the steps in plain English (or simple math)
first. This is called pseudo-code.

Answer:
Programming in C - 1BEIT105/205

B) int myFunction(void);

The (void) explicitly tells the C compiler that the function takes no arguments, avoiding
the ambiguity of the old-style declaration.

Pointers to Functions

What is a Function Pointer?

 A function has a physical location in memory (its "entry point").


 This address can be assigned to a pointer.
 Once a pointer points to a function, the function can be called through that pointer.
 Function pointers allow functions to be passed as arguments to other functions.

Obtaining a Function’s Address

 You obtain the address of a function by using the function’s name without any parentheses or
arguments.

The Key to Declaration

 The declaration tells the compiler what type of function the pointer can point to (its return
type and parameters).

Declaration in main() vs Explanation

int (*p)(const char *, const char *);

 p is a pointer to a function that:


o takes two const char * parameters
o returns an int result

Note: The parentheses around *p are necessary for the compiler to properly interpret this as a
function pointer, not a function that returns an int *.

Assignment

 To make the pointer point to a function, use the function’s name:


Programming in C - 1BEIT105/205

Assignment
Explanation
Example
Assigns the address of the standard library function strcmp to the function
p = strcmp;
pointer p.

Passing a Function Pointer

 A function pointer can be passed as an argument.


 The check() function parameter must be declared to match the function pointer’s signature.

Declaration in check()

void check(char *a, char *b,


int (*cmp)(const char *, const char *));

 cmp can receive a pointer to a function that:


o takes two const char * arguments
o returns an int result.

Calling a Function via its Pointer

 There are two syntaxes for calling the function pointed to by cmp:

1. Original C Style (Explicit Dereference)

(*cmp)(a, b);

 Benefit: This style “tips off” anyone reading your code that a function is being called through a
pointer.

2. Simpler Syntax (Modern C)

cmp(a, b);

Calling without an Extra Variable

 You can pass the function’s address directly to the function that accepts the pointer:
 Direct Call:

check(s1, s2, strcmp);


Programming in C - 1BEIT105/205

 This eliminates the need for an additional pointer variable like p.

Why Use Function Pointers?

1. Pass Functions as Parameters (Flexibility):

1. Allows a function (like check) to perform different actions based on a comparison/operation


function that is passed to it.
2. Example: Using strcmp for string equality or compvalues for numeric equality within
the same check() function.

2. Create an Array of Functions (Optimization):

1. Scenario: Writing an interpreter where a parser needs to call various support functions (sine,
cosine, I/O, etc.).
2. Alternative: Instead of a large switch statement, you can create an array of function pointers.
3. Benefit: The proper function is selected simply by its index in the array, making the code
cleaner and more extensible.

Dynamic Memory Allocation

The Heap and Allocation

 Dynamic memory allocation is managed by the heap.


 The heap is a "free region of memory" available for your program to use.

The malloc() Function

 Purpose: Allocates a specified number of bytes from the heap.


 Prototype:

void *malloc(size_t size);

 Return Value:
o A void * pointer to the first byte of the allocated memory.
o NULL if an allocation failure occurs (not enough memory).

Example
Programming in C - 1BEIT105/205

Example Explanation

char *p; Declares a character pointer.

p = malloc(1000); Allocates 1,000 bytes. p points to the start.

p = malloc(50 * Allocates space for 50 integers. Using sizeof ensures


sizeof(int)); portability.

Essential Rules for Dynamic Memory

Rule 1: Always Check the Return Value

 You must check if malloc() returns NULL before using the pointer.
 Using a null pointer will almost certainly crash your program.

Proper Allocation and Test:

p = malloc(100);
if (!p) {
printf("Out of memory.\n");
exit(1);
}

Rule 2: Releasing Memory with free()

 Purpose: Returns previously allocated memory to the system (the heap) so it can be reused.
 Prototype:

void free(void *p);

 Argument:
p must be a pointer to memory previously allocated using malloc().
 Critical Warning:
Never call free() with an invalid pointer — this can damage the memory system and cause
undefined behavior.

Key Uses

 Supports important structures like linked lists and binary trees.


 Used for dynamically allocated arrays.
Programming in C - 1BEIT105/205

C’s Dynamic Allocation Functions

 C’s dynamic allocation functions are a set of standard library functions (found in
<stdlib.h>) that allow a program to allocate and deallocate memory during runtime
(while the program is executing), rather than at compile time.
 This memory is allocated from a pool called the heap.
 This is crucial for handling situations where the required memory size is unknown until the
program runs, such as reading data from a file or creating flexible data structures like linked
lists.

C’s Dynamic Allocation Functions Overview


Function Name Purpose Key Difference
Allocates a single block of specified size in The allocated memory is
Memory
malloc() bytes. Returns a void * pointer to the uninitialized (contains
ALLOCation
first byte. garbage values).
Allocates memory for an array of a The allocated memory is
Contiguous
calloc() specified number of elements and size per initialized to zero (all bits
ALLOCation
element. Returns a void * pointer. are set to 0).
Can grow or shrink the
Changes (resizes) the size of a previously
RE- block. It may move the
realloc() allocated memory block. It takes the
ALLOCation block to a new location in
original pointer and the new size in bytes.
memory.
Deallocates/releases the memory previously Crucial to prevent
allocated by malloc(), calloc(), or memory leaks (where
free() Free
realloc(), returning it to the heap for memory is reserved but
future use. inaccessible).

Dynamically Allocated Arrays

 Dynamically allocated arrays are blocks of memory requested using functions like malloc()
but operated on using standard array indexing (s[t]).

How They Work (One-Dimensional):

 You allocate memory (e.g., s = malloc(80);).


 The returned pointer (s) is treated just like the name of a standard array.
 You can use array indexing (s[t]) to access individual elements.
 Crucial Step: Always check if the memory allocation succeeded (if (!s)) to prevent using
a null pointer.
 The memory should be released when no longer needed using free(s);

How They Work (Multidimensional):


Programming in C - 1BEIT105/205

 You must declare a pointer to an array to handle the dimensions correctly.


 The declaration must specify all but the leftmost (first) dimension.
 Example Declaration:

int (*p)[10];

This means p is a pointer to an array of 10 integers (its base type is a 10-int array, representing a row).

 Allocation:
The total memory for all elements is calculated and allocated
(e.g., p = malloc(40 * sizeof(int)); for a 4×10 array of integers).
 Indexing:
The pointer (p) can then be indexed as a standard two-dimensional array
(e.g., p[i-1][j-1]).

C++ Compatibility (Casting):

 In C++, all pointer conversions must be cast.


 To make the code compatible with C++, the pointer returned by malloc() should be
explicitly cast to the correct pointer-to-array type:
p = (int (*)[10]) malloc(40 * sizeof(int));

Quiz time

What standard C library function is used to request a block of memory for a dynamically allocated
array at runtime?

A. calloc()
B. new
C. malloc()
D. alloc()

Exam tip:

Always choose the smallest data type that can safely hold the range of values you need.

 Why? Using a smaller data type saves memory. While memory is cheap now, this practice is
crucial in systems with limited resources (like embedded systems) and improves performance
in large-scale applications by increasing data locality and cache efficiency.

You might also like