Functions#

(chapter 5)

Function Definition#

    <Type> function_name(<parameter> list) /* header */
    {
        Declarations
                        /* body */
        Statements
    }

type hints in Python

def add(a: int, b: int) -> int:

The return statement#

    double abs_value(double x)
    {
        if (x >= 0.0)
            return x;
        else
            return -x;
    }

The evaluated value is returned:

return;
return ++a;
return (a * b);

Function Prototypes#

double sqrt( double );

When declaring a function, the names of the input parameters do not matter, only their type and order.

void f(char c, int i);  /* equivalent */
void f(char, int);

The structure of a program#

  • Preprocessor definitions/commands (start with #)

  • Function declarations (prototypes)

  • Function definitions (always main())

Example#


#include <stdio.h>
#define N 7

/* Function declaration (Prototypes) */
long power(int, int);
void prn_heading(void);
void prn_tbl_of_powers(int);

int main(void)
{
    /* Using the functions */
    prn_heading();
    prn_tbl_of_powers(N);
    return 0;
}
# @title
%%writefile power-table.c
#include <stdio.h>
#define N 7

/* Function declaration (Prototypes) */
long power(int base, int exp);
void prn_heading(void);
void prn_tbl_of_powers(int n);

int main(void)
{
    /* Using the functions */
    prn_heading();
    prn_tbl_of_powers(N);
    return 0;
}

/* Function to calculate power */
long power(int base, int exp)
{
    long result = 1;
    for (int i = 0; i < exp; i++)
    {
        result *= base;
    }
    return result;
}

/* Function to print heading */
void prn_heading(void)
{
    printf("Table of Powers\n");
    printf("================\n");
}

/* Function to print table of powers */
void prn_tbl_of_powers(int n)
{
    for (int i = 1; i <= n; i++)
    {
        printf("%d^%d = %ld\n", i, i, power(i, i));
    }
}
  Cell In[1], line 6
    *(Function, declaration, (Prototypes), */)
                                            ^
SyntaxError: invalid syntax
# @title
!gcc power-table.c -o power-table
!./power-table

Call by Value#

Summing integers from 1 to n

#include <stdio.h>
int compute_sum(int); /* Function declaration */
int main(void)
{
    int n = 3, sum = 0;

    printf("% d\n", n); /* 3 is printed */

    sum = compute_sum(n); /* what happens to n and sum here? */

    printf("% d\n", n); /* 3 is printed */
    printf("% d\n", sum); /* 6 is printed */

    return 0;
}
# @title
%%writefile call-by-value.c
# include <stdio.h>
int compute_sum(int); /* Function declaration */
int main(void)
{
    int n = 3, sum = 0;

    printf("% d\n", n); /* 3 is printed */

    sum = compute_sum(n); /* what happens to n and sum here? */

    printf("% d\n", n); /* 3 is printed */
    printf("% d\n", sum); /* 6 is printed */

    return 0;
}
/* sum the integers from 1 to n */
int compute_sum(int n)
{ /* This n is local. It gets the value of the of the variable that was sent to the function. */

    int sum = 0; /* sum is local to the function. */

    for (; n > 0; --n)
        sum += n; /* local n and sum are changed */
    return sum;
}
# @title
!gcc call-by-value.c -o call-by-value
!./call-by-value

Function invocation - summary#

  1. Each expression in the argument list is evaluated.

  2. The value of the expression is converted, if necessary, to the type of the formal parameter, and is assigned to its corresponding formal parameter (“call by value”) at the beginning of the function’s body.

  3. The body of the function is executed.

  4. If a ‘return’ statement is executed, then control is passed back to the calling environment.

  5. If the ‘return’ statement includes an expression then the value of the expression is converted, if necessary, to the type given by the type specifier of the function, and that value is passed to the calling environment as well.

  6. If the ‘return’ statement does not include an expression, no useful value is returned.

  7. If no ‘return’ statement is present, control is passed back to the calling environment at the end of the function’s body. No useful value is returned.

Activation records#

Every instance of a function execution creates an activation record Activation records hold required execution information:

  • Local data (e.g., variables)

  • The state of the machine just before the call

  • A control link pointing to the activation record of the caller function

  • Supplied parameters

  • Return value of the function

Runtime Stack#

Activation records are created and stored in an area of memory termed the runtime stack.

void f2(char c, int i){
	...
}

void f1(int i){
	...
	f2('a', i);
	...
}

int main(void){
	f1(1);
	return 0;
}

+------------------+
|    f2            | <- Stack Pointer (SP)
|------------------|
| return value     |
| actual parameters|
| control link     |
| machine status   |
| local data       |
+------------------+
|    f1            |
|------------------|
| return value     |
| actual parameters|
| control link     |
| machine status   |
| local data       |
+------------------+
|   Main           |
|------------------|
| return value     |
| actual parameters|
| control link     |
| machine status   |
| local data       |
+------------------+

Storage Organization#

  • The program stack grows each time a function call is made.

  • Infinite recursion results in a collision between the runtime stack and the heap termed a run-time stack overflow error.

image.png

Recursion#

int fib(int num)
{
    switch (num)
    {
    case 0:
        return (0);
        break;
    case 1:
        return (1);
        break;
    default:
        return (fib(num - 1) + fib(num - 2));
        break;
    }
}
# @title
%%writefile fib_rec.c
#include <stdio.h>

/* Function prototype */
int fib(int num);

int main(void)
{
    int num = 40; // Example input
    printf("Fibonacci of %d is %d\n", num, fib(num));
    return 0;
}

int fib(int num)
{
    switch (num)
    {
    case 0:
        return (0);
    case 1:
        return (1);
    default:
        return (fib(num - 1) + fib(num - 2));
    }
}
# @title
!gcc fib_rec.c -o fib_rec
!./fib_rec

Iterative (non recursive) Solution#

int fib(int num)
{
    unsigned int i = 0, fn = 0, fn_1 = 1, fn_2 = 0;

    if (num == 0)
        fn = 0;
    else if (num == 1)
        fn = 1;
    else
    {
        for (i = 2; i <= num; ++i)
        {
            fn = fn_2 + fn_1;
            fn_2 = fn_1;
            fn_1 = fn;
        }
    }
    return fn;
}
# @title
%%writefile fib_iter.c
#include <stdio.h>

/* Function prototype */
int fib(int num);

int main(void)
{
    int num = 40; // Example input
    printf("Fibonacci of %d is %d\n", num, fib(num));
    return 0;
}

int fib(int num)
{
    unsigned int i = 0, fn = 0, fn_1 = 1, fn_2 = 0;

    if (num == 0)
        fn = 0;
    else if (num == 1)
        fn = 1;
    else
    {
        for (i = 2; i <= num; ++i)
        {
            fn = fn_2 + fn_1;
            fn_2 = fn_1;
            fn_1 = fn;
        }
    }
    return fn;
}
# @title
!gcc fib_iter.c -o fib_iter
!./fib_iter

Recursive vs. iterative#

  • Function calls vs. loops

  • Recursion uses the runtime stack, less efficient

  • Recursion produces simpler and more intuitive code

Storage Classes#

  • Auto (local, not initialized)

  • Extern (global, keyword used only for variable declaration)

  • Static (private)

Scope Rules (auto)#

What is the output of this code?

{
    int a = 1, b = 2, c = 3;
    printf("% 3d % 3d % 3d\n", a, b, c);
    {
        int b = 4;
        float c = 5.0;
        printf("% 3d % 3d % 5.1f\n", a, b, c);
        a = b;
        {
            int c;
            c = b;
            printf("% 3d % 3d % 3d\n", a, b, c);
        }
        printf("% 3d % 3d % 5.1f\n", a, b, c);
    }
    printf("% 3d % 3d % 3d\n", a, b, c);
}
# @title
%%writefile scope.c
#include <stdio.h>

int main(void)
{
    int a = 1, b = 2, c = 3;
    printf("%3d %3d %3d\n", a, b, c);
    {
        int b = 4;
        float c = 5.0;
        printf("%3d %3d %5.1f\n", a, b, c);
        a = b;
        {
            int c;
            c = b;
            printf("%3d %3d %3d\n", a, b, c);
        }
        printf("%3d %3d %5.1f\n", a, b, c);
    }
    printf("%3d %3d %3d\n", a, b, c);

    return 0;
}
# @title
!gcc scope.c -o scope
!./scope

Extern/Global#

#include <stdio.h>

int a = 1, b = 2, c = 3;  // Global variables

int f(void);  // Function prototype for f, which is in another file

int main(void) {
	printf( "%3d\n", f() );         // Call to f(), prints its return value
	printf( "%3d%3d%3d\n", a, b, c );  // Prints the values of a, b, and c
	return 0;
}

Explanation:#

  • Global variables a, b, and c are declared and initialized to 1, 2, and 3 respectively. These are visible across the entire program, including in other files.

  • The main() function calls f(), a function defined externally (in another file, file2.c), and prints its return value. It also prints the values of a, b, and c.

External cont.#

int f( void ) {
	extern int a;  // Tells the compiler that 'a' is declared elsewhere (file1.c)

	int b = 0, c = 0;  // Local variables to the function 'f'

	a = b = c = 4;  // Modifies 'a', 'b', and 'c'

	return (a + b + c);  // Returns sum of a, b, and c
}

Explanation:#

  • extern int a: This tells the compiler that a is declared elsewhere, in this case in file1.c. This allows f() to access and modify the global variable a.

  • Local variables b and c are declared and initialized inside f(); they only exist within the scope of the function f().

  • In the line a = b = c = 4;, all three variables (a, b, and c) are assigned the value 4. This modifies the global variable a and the local variables b and c.

  • The function returns the sum of a, b, and c (which are all 4).

# @title
%%writefile file1.c
# include <stdio.h>

int a = 1, b = 2, c = 3;  // Global variables

int f(void);  // Function prototype for f, which is in another file

int main(void) {
    printf( "%3d\n", f() );         // Call to f(), prints its return value
    printf( "%3d%3d%3d\n", a, b, c );  // Prints the values of a, b, and c
    return 0;
}
# @title
%%writefile file2.c
int f( void ) {
    extern int a;  // Tells the compiler that 'a' is declared elsewhere (file1.c)

    int b = 0, c = 0;  // Local variables to the function 'f'

    a = b = c = 4;  // Modifies 'a', 'b', and 'c'

    return (a + b + c);  // Returns sum of a, b, and c
}
# @title
!gcc file1.c file2.c -o file1
!./file1

static#

void count_calls()
{
    static int count = 0; // Static variable, retains value across calls
    count++;              // Increment count each time function is called
    printf("Function has been called %d times\n", count);
}
# @title
%%writefile static.c
#include <stdio.h>

void count_calls()
{
    static int count = 0; // Static variable, retains value across calls
    count++;              // Increment count each time function is called
    printf("Function has been called %d times\n", count);
}

int main()
{
    count_calls(); // 1st call
    count_calls(); // 2nd call
    count_calls(); // 3rd call
    return 0;
}
# @title
!gcc static.c -o static
!./static

static external#

static int g( void ); /* function prototype */

void f( int a ) /* function definition */
{
	g() /* g() is avaiable here but not in other files */
	......
}

static int g( void ) /* function definition */
{
	......
}

Explaination#

  +--------------+        +--------------+
  |   file1.c    |        |   file2.c    |
  +--------------+        +--------------+
  | Calls f(5);  | ---->  | f() calls g()|
  | Cannot call g()!      |              |
  +--------------+        +--------------+
                            | g() is only visible here
                            +--------------+

Example Question#

image.png

Mathematical Functions#

  • The functions are declared in <math.h>

  • All the functions use doubles

  • Compile with -lm flag

Example:

  • sqrt()

  • pow()

  • exp()

  • log()

  • sin()

  • cos()

  • tan()

# @title
%%writefile tmp.c
#include <stdio.h>
#include <math.h>

int main()
{
    double num = 9.0;

    // Square root
    double square_root = sqrt(num);
    printf("Square root of %.2f is %.2f\n", num, square_root);

    return 0;
}
# @title
!gcc -o tmp tmp.c -lm
!./tmp

Assertions#

an assertion: used to enforce certain logical conditions. If conditions are not satisfied, the program terminates immediately.

#include <assert.h>
#include <stdio.h>
int f( int a, int b );

int main( void )
{
	int a = 0, b = 0, c = 0;
	....
	scanf( %d%d, &a, &b );
	c = f( a,b );
	assert( c > 0 );
	.....
}

Disabling Assertions#

  1. In the code

#define NDEBUG
#include <assert.h>
  1. During compilation

gcc -DNDEBUG program.c
# @title
%%writefile assert.c

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

int f(int a, int b);

int main(void)
{
    int a = 0, b = 0, c = 0;

    // Call function f with a and b as parameters
    c = f(a, b);

    // Assert that the result of f is greater than 0
    assert(c > 0); // If c <= 0, the program terminates here

    printf("Function returned a positive value: %d\n", c);

    return 5;
}

int f(int a, int b)
{
    // Dummy function that returns a - b
    return a - b;
}
# @title
!gcc -o assert assert.c
!./assert

assert vs if with return value#

// Using if with return
int function() {
    if (pointer == NULL) {
        return -1;     // Error needs to be handled
    }
    // continue...
}

// Using assert
void function() {
    assert(pointer != NULL);  // Program stops if false
    // continue...
}

Feature

assert

if return

Purpose

Debug/Development

Production code

When issue found

Stops program immediately

Returns error code

Compile control

Disabled with NDEBUG

Always active

Error handling

No need for error checking

Must handle errors

Performance

No overhead in release

Always evaluated

Usage

Catch programming errors

Handle runtime errors

exit in C#

  • Purpose: Terminates the entire program immediately, regardless of where it is called.

  • Scope: Global; bypasses the function call stack.

  • Execution: Performs cleanup (e.g., flushing output buffers, closing files) before terminating the program.

#include <stdio.h>
#include <stdlib.h> // Required for exit()

void example_function() {
    printf("Inside example_function\n");
    exit(1); // Terminates the program
    printf("This will not print.\n");
}

int main() {
    printf("Program started.\n");
    example_function();
    printf("This will not print.\n");
    return 0;
}