1. Homepage of Dr. Zoltán Porkoláb
    1. Home
    2. Archive
  2. Teaching
    1. Timetable
    2. Multiparadigm programming (MSc)
    3. C programming (BSc for physicists)
    4. Project tools (BSc)
    5. Bolyai College
    6. C++ (for foreign studenst)
    7. Software technology lab
    8. BSc and MSc thesis
  3. Research
    1. Templight
    2. CodeChecker
    3. CodeCompass
    4. Projects
    5. Publications (up to 2011)
    6. PhD students
  4. Affiliations
    1. Dept. of Programming Languages and Compilers
    2. Ericsson Hungary Ltd

The C programming language – Lecture 8.

Functions, parameter passing

Functions are fundamental building blocks in procedural programming, they allow us to break large, complex code into smaller more maintainable parts, hiding implementation details from the view of a higher abstraction, reuse earlier implemented actions and apply variability by using parameters.

The C language has been designed to make functions easy tom implement and use, and effective to call.

A set of functions (and perhaps other global objects) can be compiled in separate translation unit and can be used as a library.

Wording: the name of a parameter in a function declaration is called parameter, while the actual value passed in the call is argument. The full list of parameters is called prototype.

Function declaration

The return type and the parameter list is part of the type of the function.

int f(void);
int *fip(void);
int ( *pfi )(void);

declare a function with no parameters returning int, a function with no parameters returning pointer to int, and a pointer to function with no parameters returning int. Functions with no intended return value should be declared with void return type. Array return type is not allowed.

For declarations, we can use formal parameter names, but they are only for descriptive purposes:

1
2
int f(int *x, int *y);    // x and y has no role in declaration
int f(int *, int *);      // equivalent to the above

For historical reasons, parameter specification can be omitted:

1
2
int f(void);      // function with no parameters
int g();          // function with no parameter specification

Here f() and g() are different. We know, that f() has no parameter, but we do not know about the parameters of g() anything. The missing parameter specification is reverse compatible with early C language versions, but it is not supposed to use in new code.

When we want to express that we do not know the exact parameters of a function, like in the case of printf we use the ellipsis notation:

1
2
int printf(const char *format, ...); 
int fprintf(FILE *stream, const char *format, ...); 

The meaning of the ellipsis is that zero or more additional parameters of unknown type. To write such variadic parameter function is tricky (and usually done by the va_ macros from <stdarg.h> header.

Function call

When a function f is called, then the declaration prototype should match to the call:

  • The number of the parameters should be the same.
  • Each argument of the call shell have a type such that its value may be assigned to an object of the type of the corresponding parameter of the prototype.

If the number of parameters does not match with the number of arguments of the call, the behavior is undefined. If the function definition prototype have ellipsis or the argument in the call are incompatible with the correspondent parameter, the behavior is undefined.

If the called function has no prototype then default argument promotions will performed. These are the integer promotions and float to double promotions.

If the called function has a prototype then the arguments are implicitly converted, as if by assignment, to the corresponding parameters. Ellipsis stops the conversions, only the default argument promotions are executed.

1
2
3
double fahr2cels(double);
//...
printf("%f\n", fahr2cels(36));  // ok, 36 is converted to double

Two other conversion is allowed:

  • signedunsigned conversions, when the value is representable in both types.
  • void *char * conversions.

The function call is a sequence point, i.e. first the argument expressions are evaluated in undefined order, only then the function body is starting to execute. (The comma between parameters are different from the comma operator.)

( *t[f1()] ) ( f2(), f3(), f4() );

The functions f1, f2, f3, f4 can be called in any order.

Recursive functions are allowed both directly or indirectly:

1
2
3
4
5
6
7
int factorial(int n)
{
  if ( 1 == n ) 
    return 1;
  else 
    return n * factorial(n-1);
}

When this function is called with an argument smaller then one, infinite recursion happens and the program behavior is undefined.

Parameter passing

Arguments are passed by value, i.e. they are copied from the argument expression to the parameter as it would be a local automatic variable declared in the beginning of the function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
void increment(int i)
{
  ++i;
  printf("i in increment() = %d\n",i);
} 
int main()
{
  int i = 0;
  increment(i);
  increment(i);
  printf("i in main() = %d\n",i);
  return 0;    
}
$ gcc -ansi -pedantic -Wall -W f.c 
$ ./a.out 
i in increment() = 1
i in increment() = 1
i in main() = 0

We can simulate the parameter passing by address with pointers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
void increment(int *ip)
{
  ++*ip;
  printf("i in increment() = %d\n",*ip);
} 
int main()
{
  int i = 0;
  increment(&i);
  increment(&i);
  printf("i in main() = %d\n",i);
  return 0;    
}
$ gcc -ansi -pedantic -Wall -W f.c 
$ ./a.out 
i in increment() = 1
i in increment() = 2
i in main() = 2

Array parameters are passed as pointers to the first array element.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int f( int *t, int i)
{
  return t[i];
}
int main()
{
  int arr[] = {1, 2, 3, 4};
  printf("%d\n", f(arr,1));
  printf("%d\n", f(&arr[0],1));
  return 0;
}
$ gcc -ansi -std=c99 -Wall -W a.c
$ ./a.out 
2
2

Therefore these parameter declarations are equivalent:

1
2
3
4
5
6
int f( int *t, int i)   { return t[i]; }
int f( int t[], int i)  { return t[i]; }
int f( int t[4], int i) { return t[i]; }

// array limits are not checked: this is not reported as error
int f( int t[4], int i)  { return t[6]; }

Pointers to function

Type of a pointer to function includes the return type and the parameter list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
void increment(int *ip)
{
  ++*ip;
  printf("i in increment() = %d\n",*ip);
} 
int main()
{
  void (*fp)(int *); // pointer to void (int*) 
  void (*gp)();      // pointer to function with unknown parameters
  int i = 0;
  fp = increment;
  gp = fp;
  (*fp)(&i);    // call increment()
    fp (&i);    // simplified notation
    gp (&i);    // call increment(), no check for parameters
  printf("i in main() = %d\n",i);
  return 0;    
}
$ gcc -ansi -pedantic -Wall -W f.c 
$ ./a.out 
i in increment() = 1
i in increment() = 2
i in increment() = 3
i in main() = 3

A pointer to function can be:

  • point to a function by a compatible type.
  • assigned to a similar pointer variable.
  • checked against NULL pointer.
  • indirected in a function call.

Return type

Return values are converted to the return type of the function:

1
2
3
4
5
6
double f(void)
{
  int i;
  // ...
  return i;   // converted to double
}

Parameters of the main function

The main function is declared in one of the following ways:

1
2
3
4
5
int main(void) { /* ... */ }
int main( int argc, char *argv[]) { /* ... */ }

// only when the host environment supports it (mainly UNIX):
int main( int argc, char *argv[], char *envp[]) { /* ... */ }

If argc is defined then argv[argc] is NULL pointer. If argc is greater than zero, then argv[0] is the name of the program, and argv[1] … argv[argc-1] are the program parameters. The argv[i] parameters are pointers to NULL terminated character arrays.

In usual environments, argc is always greater than zero.

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(int argc, char *argv[])
{
  printf("name of the program = %s\n", argv[0]);
  for (int i = 1; i < argc; ++i)
    printf("argv[%d] = %s\n", i, argv[i]);
  return 0;    
}
$ gcc -ansi -std=c99 -Wall -W -o mainpars mainpars.c 
$ ./mainpars
name of the program = ./mainpars
$ ./mainpars first second third
name of the program = ./mainpars
argv[1] = first
argv[2] = second
argv[3] = third

Stackframe

alt text