Notes and Talks/UJEP/UPC/04

Functions ๐Ÿ Š program structure

Function definitions, declarations and calls

A function must be declared or defined before it is called.
(in early C versions existed implicit declaration).

Function declaration (prototype)

Function definition

Function call

Binary calling convention:

Compiler-specific directives for calling conventions (GCC):

cint __attribute__((cdecl))    f(int a, int b);  // caller cleans up the stack (C default)
int __attribute__((stdcall))  g(int a, int b);  // callee cleans up (Pascal-like, Win32 API)
int __attribute__((fastcall)) h(int a, int b);  // first args in ECX/EDX
int __attribute__((regparm(3))) k(int a, int b, int c); // first 3 args in EAX/EDX/ECX
int __attribute__((ms_abi))   w(int a);         // x86-64: use the Microsoft ABI
int __attribute__((sysv_abi)) s(int a);         // x86-64: use the System V ABI

BNF

BNF of the C language:

bnf<function-declaration> ::= <declaration-specifiers> <declarator> ";"

<declaration-specifiers> ::= <type-specifier> | <type-qualifier> <declaration-specifiers>
<type-specifier>        ::= "void" | "char" | "int" | "float" | "double" | <typedef-name>

<declarator>            ::= <direct-declarator>
<direct-declarator>     ::= <identifier> "(" <parameter-type-list>? ")"

<parameter-type-list>   ::= <parameter-list> ["," "..."]
<parameter-list>        ::= <parameter-declaration> | <parameter-list> "," <parameter-declaration>
<parameter-declaration> ::= <declaration-specifiers> <declarator>
bnf<function-definition> ::= <declaration-specifiers> <declarator> <compound-statement>

<compound-statement>  ::= "{" <block-item-list>? "}"

<block-item-list>     ::= <block-item> | <block-item-list> <block-item>
<block-item>          ::= <declaration> | <statement>

<statement>           ::= <labeled-statement> | <expression-statement>
                          | <selection-statement> | <iteration-statement>
                          | <jump-statement>
bnf<function-call>         ::= <postfix-expression> "(" <argument-expression-list>? ")"

<postfix-expression>    ::= <identifier>
                            | <postfix-expression> "[" <expression> "]"
                            | <postfix-expression> "." <identifier>

<argument-expression-list> ::= <assignment-expression>
                               | <argument-expression-list> "," <assignment-expression>

Parameters, arguments and return values

Passing arguments by value (pass by value):

Example:

cvoid inc(int x) {
    x++;
}

int a = 5;
inc(a);
// a is still 5

Passing by pointer (pass by pointer/reference):

Example:

cvoid inc(int *x) {
    (*x)++;
}

int a = 5;
inc(&a);  // we pass the address of a
// a is now 6

return and return values

Example:

cint add(int a, int b) {
    return a + b;
}

void say_hello() {
  printf("Hello, World!\n");
}

Early return

Example:

cint max(int a, int b) {
    if (a > b)
        return a;
    else
        return b;
}

With early return, simplified:

cint max(int a, int b) {
    if (a > b) return a;
    return b;
}

Variable scope and lifetime

Local variables ("automatic")

Global variables

External variables

Static variables

Register variables

Dynamic variables

Summary

Scope/visibility of variables

Lifetime

Modular program structure

MakefileCC = cc
CFLAGS = -std=c23 -Wall -Wextra # -g

# Targets that are not files; always run when requested
.PHONY: all run clean

# Default target (plain `make`): build the executable
all: main

# Link step: combine object files into the executable `main`
# $@ = target name (main), $^ = all prerequisites (main.o funs.o scope.o)
main: main.o funs.o scope.o
	$(CC) $(CFLAGS) -o $@ $^

# Pattern rule: build any X.o from X.c
# Depends on X.h too, so editing a header triggers recompilation
# (note: main.h does not exist, %.h is replaced by space)
# -c = compile only, no linking; $< = first prerequisite (X.c)
%.o: %.c %.h
	$(CC) $(CFLAGS) -c -o $@ $<

# Build, then run the program
run: all
	./main

# Remove build artifacts (-f: no error if files don't exist)
clean:
	rm -f main *.o a.out
main.c#include <stdio.h>

#include "funs.h"
#include "scope.h"

static void header(const char* title) {
    // OR: static void header(char const* title) {
    printf("\n=== %s ===\n", title);
}

// ends with semicolon: "function-like" macro
#define HEADER(title) header(title);

#define PRINT_1_(expr) printf("%s = %d\n", #expr, (int) expr);
#define PRINT_2_(prefix, expr)                                                 \
    printf("%s: %s = %d\n", prefix, #expr, (int) expr);

int main(void) {
    HEADER("Functions: declare, define, call")

    PRINT_1_(add(2, 3))
    PRINT_1_(max(7, 4))

    HEADER("Argument passing")

    int a = 5;
    PRINT_1_(a);

    inc_by_value(a);
    PRINT_2_("after inc_by_value(a)", a);

    inc_by_pointer(&a);
    PRINT_2_("after inc_by_pointer(&a)", a);

    HEADER("Recursion")

    for (int i = 0; i <= 5; ++i) {
        printf("factorial(%d) = %d\n", i, factorial(i));
    }

    HEADER("Dynamic variables (malloc/free)")

    int values[] = {2, 4, 6, 8};
    PRINT_1_(avg_dynamic(values, 4));

    HEADER("Scope, lifetime, extern, static")

    PRINT_2_("start", global_counter);
    set_global_counter(10);
    inc_global_counter();
    PRINT_2_("end", global_counter);

    PRINT_2_("start", get_file_private_state());
    set_file_private_state(250);
    PRINT_2_("end", get_file_private_state());

    PRINT_1_(call_count_demo());
    PRINT_1_(call_count_demo());
    PRINT_1_(call_count_demo());

    HEADER("Structs and typedefs")

    Point p1 = create_point(2, 3);
    Point p2 = create_point(4, 5);
    Point p3 = add_points(p1, p2);
    printf("p3 = (%d, %d)\n", p3.x, p3.y);

    // HEADER("Out of bounds stack")
    // for (int i = 2;; i *= 2) {
    //     printf("factorial(%d) = %d\n", i, factorial(i));
    // }

    return 0;
}
funs.h#pragma once

int add(int a, int b);
int max(int a, int b);

void inc_by_value(int x);
void inc_by_pointer(int* x);

int factorial(int n);
double avg_dynamic(const int* values, int n);

typedef struct {
    int x;
    int y;
} Point;

Point create_point(int x, int y);
Point add_points(Point p1, Point p2);
funs.c#include "funs.h"

#include <stdlib.h>

static int sum_array(const int* values, int n);

int add(int a, int b) {
    return a + b;
}

int max(int a, int b) {
    if (a > b)
        return a;
    return b;
}

void inc_by_value(int x) {
    x++;
    (void) x;
}

void inc_by_pointer(int* x) {
    if (x)
        (*x)++;
}

int factorial(int n) {
    if (n <= 1)
        return 1;
    return n * factorial(n - 1);
}

double avg_dynamic(const int* values, int n) {
    if (!values || n <= 0)
        return 0.0;

    // not needed, only to demonstrate dynamic memory allocation
    int* copy = (int*) malloc((size_t) n * sizeof(int));
    if (!copy)
        return 0.0;

    for (int i = 0; i < n; ++i) {
        copy[i] = values[i];
    }

    int sum = sum_array(copy, n);
    // free the dynamically allocated memory
    // comment out to demonstrate memory leak
    free(copy);

    return (double) sum / (double) n;
}

static int sum_array(const int* values, int n) {
    int sum = 0;
    for (int i = 0; i < n; ++i) {
        sum += values[i];
    }
    return sum;
}

Point create_point(int x, int y) {
    Point p = {x, y};
    return p;
}

Point add_points(Point p1, Point p2) {
    Point result = {p1.x + p2.x, p1.y + p2.y};
    return result;
    // return (Point){p1.x + p2.x, p1.y + p2.y, 9};
}

/* >>> */

static int file_private_state = 100;

__attribute__((unused)) static int get_file_private_state(void) {
    return file_private_state;
}

__attribute__((unused)) static void set_file_private_state(int value) {
    file_private_state = value;
}
scope.h#pragma once

extern int global_counter; /* >>> */

void set_global_counter(int value);
void inc_global_counter(void);

int get_file_private_state(void);
void set_file_private_state(int value);

int call_count_demo(void);
scope.c#include "scope.h"

int global_counter = 0;

static int file_private_state = 100;

void set_global_counter(int value) {
    global_counter = value;
}

// note: (void) is required in pre-C23, but optional (ignored) in C23
void inc_global_counter(void) {
    global_counter++;
}

int get_file_private_state(void) {
    return file_private_state;
}

void set_file_private_state(int value) {
    file_private_state = value;
}

int call_count_demo(void) {
    static int calls = 0;
    calls++;
    return calls;
}

Recursion

Example:

cint fact(int n) {
    if (n <= 1) return 1;   // termination condition
    return n * fact(n - 1); // recursive call
}

Types of recursion