“Modern C”: Notes on chapter 7 “Functions”
By Dmitry Kabanov
These are my notes taken while reading chapter 7 “Functions” from the book “Modern C” by Jens Gustedt.
This chapter discusses structuring C program using functions; particularly, attention is given to recursion, that is, to the ability of C functions to invoke themselves.
The table of contents for all notes for this book are available in that post.
Functions represent parts of code to which control can be transferred during program execution and serve several important roles.
The main reasons for functions are modularity and code reuse.
- Functions allow avoiding code repeatition. Useful functionality is implemented only once and used multiple times.
- Functions decrease compilation time. Functions are compiled only once at the definition point, not where there are used.
- Functions simplify code reuse.
- Functions provide clear interfaces as the inputs to the functions and its output are specified. Also one can specify pre- and post-conditions.
- Functions simplify formulation of algorithms that required a stack of intermediate values.
7.1 Simple functions
All functions must have prototypes:
bool leapyear(unsigned);
Here, the name of the function argument is not given, but it is not important anyway. What is important is that C compiler knows about the types of input arguments and the return type.
Function that do not return anything are declared with return type
void
.
They can omit the keyword return
.
Other functions must have at least one return
with the expression
of the type compatible with the declared return type.
If a function is to be called without parameters, the argument list
must be declared with keyword void
.
Omitting this keyword in such function declarations leads to warnings
from static analyzers like clang-tidy
and compilers, and will be
removed from future C version.
Functions can have a variable argument list (like printf
),
which is manipulated via functions from the header stdargs.h
,
However, such functions are usually not very user-friendly
and should be avoided as compiler cannot give useful warnings
and it is completely up to a programmer to ensure correct usage.
7.2 main is special
Function main is the entry point to a C program. Two possible prototypes are:
int main(void);
int main(int argc, char* argv[argc + 1]);
There are variations on these prototypes, for example,
some implementations could require void
return type or provide
extra arguments such as “execution environment”.
Guaranted return values for main
are
EXIT_SUCCESS
and EXIT_FAILURE
,
therefore, it is recommended to return only these two values.
However, main
has an exception such that if no return
statement
is provided, it is treated as returning EXIT_SUCCESS
.
Function void exit(int status)
ends main
in the same way
as return status
.
All argument values from the array argv
are strings.
The interpretation of them is up to the programmer.
For example, the function strtod
can be used to convert
the given string
value to the corresponding double
value.
The first element, argv[0]
holds the name of the program.
The last element, argv[argc]
is zero.
7.3 Recursion
Functions are implemented in such way, that even if a function calls itself, a new set of all the variables in this function is created and newly initialized. This useful property allows for recursion, which is useful for implementing many CS algorithms.
For example, Euclid’s algorithm for finding the greatest common divisor:
size_t gcd(size_t a, size_t b) {
assert (a <= b);
if (!a) return b;
size_t rem = b % a;
return gcd(rem, a);
}
By the way, we use here the assert
macro from assert.h
.
To avoid infinite recursion that will easily hang your computer,
it is crucial to check the termination condition as the first thing
in the recursive function (here the condition is if a
is not zero).
In the above function assert
is needed only on the first invokation
as the following invokations, the remainder is always less or equal
than the second argument to the function.
To avoid this assert
on each recursion iteration,
one can remove it, and use an interface
function that wraps gcd
and check all necessary preconditions:
// This was gcd before; renamed to emphasize that is private
// function now (can be augmented with the static keyword).
size_t _gcd(size_t a, size_t b) {
if (!a) return b;
size_t rem = b % a;
return gcd(rem, a);
}
size_t gcd(size_t a, size_t b) {
assert(a);
assert(b);
if (a < b) {
return _gcd(a, b);
} else {
return _gcd(b, a);
}
}