Module 4 C Programming
Module 4 C Programming
Module 4
Function Arguments, argc and argv—Arguments to main(), The return Statement, What
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.
➤ 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:
For example, here are a correct and an incorrect function parameter declaration:
➤ 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.
➤ A local variable is created when the function is entered and destroyed when the function exits.
➤ 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.
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.
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.
➤ 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.
Quiz time
A) function add()
B) int add(a, b)
C) int add(int x, int y)
D) function int add()
Programming in C - 1BEIT105/205
➤ 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:
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;
return 0;
}
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.
➤ 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]);
}
}
After the call to print_upper(), the contents of array s in main() are changed to uppercase.
#include <stdio.h>
#include <ctype.h>
int main(void)
{
char s[80];
printf("Enter a string: ");
gets(s);
print_upper(s);
printf("\ns is unchanged: %s", s);
return 0;
}
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.
• 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.
• 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.
➤ Two parameters are used to receive command line arguments in the main() function:
➤ argc is always at least 1, because the name of the program itself is counted as the first argument.
➤ Any numbers passed must be manually converted inside the program (e.g., using atoi()).
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.
➤ If your program does not require command line arguments, it's good practice to declare main()
with void:
int main(void)
argv[t][i]
➤ Program Goal: To print "Hello" and a name, where the name is passed as a command-line
argument.
return 0;
}
name Tom
➤ Output:
Hello Tom
➤ Separating Arguments
• Characters like commas ( , ) and semicolons ( ; ) are not generally considered separators.
➤ Examples
• Some operating systems allow enclosing a string with spaces in double quotes (" ").
➤ You typically use command-line arguments to pass initial commands needed at program startup.
Programming in C - 1BEIT105/205
➤ Common Uses
• A program that uses command-line arguments should always check argc to ensure the required
information was provided.
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.
• First, it causes an immediate exit from the function. That is, it causes program execution to return to
the calling code.
➤ A function's execution terminates, and it returns control to the calling code (the caller) in one of two
fundamental ways.
• 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>
int main(void)
{
pr_reverse("I like C");
return 0;
}
void pr_reverse(char *s)
{
register int t;
• Once the string is displayed, there is nothing left for pr_reverse() to do, so it returns to main()
(the caller).
• Most Common Method: Most functions use the return statement to stop execution.
Programming in C - 1BEIT105/205
Key Rule:
• The first return statement that is executed immediately halts the function's execution and transfers
control back to the caller.
Example: find_substr()
if (!*p2)
return t; /* 1st return: Substring found! */
Using multiple return statements here keeps the logic clean: success exits immediately, and the default
failure return is placed at the end.
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.
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
• A function call represents a value, not a memory location (lvalue) that can be assigned to.
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.
▪ 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).
#include <stdio.h>
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;
}
Returning Pointers
▪ Pointers are NOT Integers: They are fundamentally different from int or unsigned int.
▪ 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).
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.
return s; // 's' now holds the address of the match or the null
terminator
}
▪ The returned pointer is assigned to another pointer variable in the calling function (main):
▪ The program prints the string starting from the point of the match
(%s starts printing from the address in p).
▪ The while loop in match() terminates when it reaches the null terminator (\0) of the string.
▪ The program executes the else block and prints "No match found."
#include <stdio.h>
int main(void)
{
char s[80], *p, ch;
gets(s);
ch = getchar();
p = match(ch, s);
return 0;
}
Programming in C - 1BEIT105/205
▪ 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.
▪ It does not compute a result or report a status code that needs to be returned.
return 0;
}
Programming in C - 1BEIT105/205
▪ 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.
▪ Always use void to declare functions that perform actions but do not return a meaningful value.
▪ 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?
Correct Answer: D)
Declaring a function with a void return type clearly tells that the function does not give back any
value.
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.
Simple Example
▪ 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
/* recursive */
int factr(int n)
{
int answer;
return(answer);
}
Programming in C - 1BEIT105/205
/* non-recursive */
int fact(int n)
{
int t, answer;
answer = 1;
return(answer);
}
▪ The iterative version uses a loop that runs from 1 to n and progressively multiplies each number.
▪ 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.
▪ 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.
▪ 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.
▪ 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
▪ Prototypes enable the compiler to provide stronger type checking, similar to Pascal.
▪ This allows the compiler to find and report errors such as:
▪ 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
int main(void)
{
int x;
x = 10;
return 0;
}
Result
▪ The compiler reports an error because sqr_it() is called with an integer argument (x) instead of
the required integer pointer (int *).
▪ A function’s definition can serve as its prototype if the definition occurs before the function’s
first use.
▪ Example: If
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 only function that does not require a prototype is main() because it is the first function
called when your program begins.
▪ 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.
▪ There is a small but important difference in how C and C++ declare (prototype) a function that has
no parameters.
C++ Approach
C Approach
▪ In C,
int f();
Programming in C - 1BEIT105/205
▪ The compiler assumes the function could have parameters or none (old-style declaration).
▪ This explicitly tells the C compiler that the function has no parameters, so any call with arguments
will produce an error.
C++ Note
Final Conclusion
▪ Function prototypes help you catch bugs early and ensure your program works correctly.
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.
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.
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>
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;
}
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.
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:
The Rule
A function using a variable number of parameters must have at least one actual, fixed
parameter.
Illegal
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):
The rule applied to function return values, parameters, and local variables:
f(register a, register b) {
register c;
c = a + b;
/* ... */
return c;
}
Notes
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.
A list of parameter names goes inside the parentheses following the function name.
Note: This list contains names only, no types.
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:
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
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
You obtain the address of a function by using the function’s name without any parentheses or
arguments.
The declaration tells the compiler what type of function the pointer can point to (its return
type and parameters).
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
Assignment
Explanation
Example
Assigns the address of the standard library function strcmp to the function
p = strcmp;
pointer p.
Declaration in check()
There are two syntaxes for calling the function pointed to by cmp:
(*cmp)(a, b);
Benefit: This style “tips off” anyone reading your code that a function is being called through a
pointer.
cmp(a, b);
You can pass the function’s address directly to the function that accepts the pointer:
Direct Call:
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.
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
You must check if malloc() returns NULL before using the pointer.
Using a null pointer will almost certainly crash your program.
p = malloc(100);
if (!p) {
printf("Out of memory.\n");
exit(1);
}
Purpose: Returns previously allocated memory to the system (the heap) so it can be reused.
Prototype:
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
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.
Dynamically allocated arrays are blocks of memory requested using functions like malloc()
but operated on using standard array indexing (s[t]).
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]).
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.