COMP 348: Principles of
Programming Languages
Section 4: C functions
Section 4 Topics
• Functions
• Multi-file C
• User-defined Header files
• Compiler warnings
• Function visibility
4-2
Basic C structure
• C is what is known as an imperative, procedural
language
– Imperative languages use a series of statements, each of
which can modify the state of the program
• In short, we are telling the computer HOW to produce its output
• This contrasts with with declarative languages (like the SQL
database language) that simply indicate what output you would
like to see.
– Procedural simply indicates that programming logic can be
encoded as procedures or sub-routines that can be called
from other locations in the code.
• It is the most basic form of structured programming.
• Note that OO languages also use methods to provide
“sub-routines” but, in addition, they couple data with
the methods that operate on them.
– Imperative procedural languages do not do this
4-3
Procedures in practice
• C is NOT an object oriented language.
• It consists entirely of the main() function and
an arbitrary series of additional functions that
are invoked from main (or from functions
invoked from main)
– Essentially the same as Java, except that main() does
not belong to a class, nor do any of the other
functions.
• Functions have the standard form:
return_type name(parameter_list){
function_body
}
4-4
Multi-file applications
• As is the case with other languages, C source
code is typically distributed across multiple
source files.
– Instead of a division based upon classes, the code is
simply organized around logical functionality.
– Functions associated with a certain objective/task
are organized into a single source file.
• E.g., IO functions, database functions, graphics functions
• Source files are compiled separately, and then
linked together into a single executable.
– This allows changes to large programs to be made
much more efficiently.
4-5
Multi-file compilation
• Large applications can consists of hundreds, or
even thousands, of source files.
• These can be spread across many folders and
typically require a very complex build
environment.
• Traditionally, this is supported by the use of
makefiles.
– makefiles use a special syntax/language to define
how C/C++ source files should be compiled and
linked into a single application or library
• makefiles and makefile syntax can be VERY
complex.
4-6
Simple multi-file compilation
• Fortunately, for smaller programs, we don’t
require all this complexity
• It is possible to compile several source files
into a single executable using a simple gcc
invocation
gcc file1.c file2.c myApp.c
• The above statement assumes
– These are three C source files
– One, and only one, of these files contains a
function called main (myApp.c in this case)
4-7
Things to keep in mind
• With multiple source files, functions in one file will
be invoked from functions in other source files
• Recall that the C compiler wants to know about
these functions before it actually invokes them
– Perhaps most importantly it needs to know about the size
of the return value.
– If this information is not provided, you will get some form
“implicit function definition” error.
• So we will do this by providing the “calling” source
file with the “external” function declarations it will
be using.
• NOTE: if you don’t do this, the compiler will do its
best to compile your code using “default”
information, but this is probably NOT what you
want
4-8
The easy way
• It is possible to simply include the prototypes in
the source file manually
– The convention is to place these declarations at the top of
the source file, typically just after the #include directives
• So, for an external method (i.e., one defined in
another source file) called do_something(), it
might look like this:
#include <stdio.h>
int do_something(int x); // prototype
void my_method(){
int y = do_something(4);
}
4-9
Drawbacks of manual inclusion
• While manual inclusion works, it is generally
not a good idea, for a couple of reasons:
1. There may be many external functions that will be
used by the current source file, so this represents a
lot of prototypes that must be manually added.
2. In many cases, the external functions will actually
be invoked from many source files, so this manual
inclusion would have to be done many times.
3. If the API changes (e.g., the function name is
modified), you would have to update every one of
the “calling” source files
4-10
New header files
• The “proper” way of doing this is to create
header files that expose the API of the
functions that are going to be used elsewhere.
• In effect, this is our own version of the
standard header files like <stdio.h>.
• All we have to do is create a new file, and add
the prototypes to this file.
– With C, it is generally expected that these files will
use a “.h” suffix to indicate a header file.
– Typically, the header name uses the same name as
the source file it supports.
• So the header file for graphics.c would be graphics.h
– Again, these are “conventions” but you should have a
very good reason for not following them.
4-11
A simple example
• An example for the previous code might look like this
/* file1.h */
int do_something(int x);
• Our application source file would now be:
#include <stdio.h>
#include “file1.h”
void my_method(){
int y = do_something(4);
}
1-12
Simple example…cont’d
• Note the following:
– Quote chars are used for the header file (“file1.h”) instead
of <> because this is not a standard header file
• In other words, the compiler will not find this file in folders
like /usr/include
– Here we assume that all files are in the same folder as the
source file. If not, additional path info would need to be
added with the #include directive
• E.g., “#include ../module/file1.h” if file1.h is in another
folder at the same level of the file hierarchy as the current
source folder.
– As a final note, it is also possible to provide the compiler
with a list of additional “include directories”
• This allows the compiler to find user-defined header files,
just like it finds the standard headers.
• For this, gcc provides a set of “I” (include) command line
options.
4-13
Header guards
• In practice, your header file probably would/should
look like this
#ifndef file1_H_
#define file1_H_
int do_something(int x);
#endif
• The red text represents conditional cpp directives
• They ensure that only one copy of this header file
gets included in your source file
– We refer to this a header guard
4-14
Header guards…cont’d
• Conditional inclusion is important because multiple
inclusions of the same header file into a single source
file can, and often do, cause compilation problems.
– Multiple inclusion is very easy to do. Header files can be
included in many different sources files, including other
header files.
• So the header guards are used to create a cpp “flag” that
says this header has been included during the current
compilation phase.
– If the same header is encountered again, its inclusion will be
skipped.
• Note that a duplicate inclusion of a function prototype
doesn’t generally cause a problem (it is just redundant)
– But there are other C declarations that the compiler will not
accept a second time.
– Errors for this duplicate inclusion can sometimes be very
confusing
– So USE header guards unless there is a very good reason not
to do it.
4-15
Compilation warnings
• C compilers do their best to compile your code as
it is written.
• However, we probably don’t want it to compile if
there are obvious problems.
• Consequently, you should ALWAYS compile your
code with warning messages enabled.
• With gcc, we use the –Wall flag
– This means print all warnings
– There are other options besides “all” but this is a good
place to start.
• So our compilation command would now be:
gcc –Wall file1.c file2.c myApp.c
4-16
Warnings
• With our running example, the –Wall option
would tell us if we forgot the prototypes, with
an error like
– implicit declaration of function
‘do_something’
– This tells us that gcc could not find info about this
function so it will just use defaults to produce the
object code
– NEVER rely on this – it may or may not work as you
expect
• As a rule of thumb, always list the compiler
warnings and always fix them.
4-17
Function visibility
• As noted, C is not an OO language.
• So, unlike Java, there are no public/private
methods that may (or may not ) be exposed.
• By default, all functions are externally visible.
– In other words, they are global to the entire application
– They can be invoked from any other source file (typically
by including a prototype in the calling source file)
• But this isn’t a nice way to structure code.
– With this approach, there is no notion of an API since
every function that you write becomes part of the API.
– It would be like making EVERY Java method public
– Note that while our header file contains the functions we
WANT others to use, there is nothing (so far) that prevents
a programmer from adding a prototype for a method that
is not in a header file.
4-18
Static functions
• Fortunately, C does provide some
mechanisms for modifying visibility
• One of the most basic is the “static”
specifier that can be used to qualify
function definitions.
• By using static with a function, this tells
the compiler that this function can only
be referenced/invoked from the source
file in which it is written.
– In effect, this is a little like calling a method
private in Java.
4-19
Static functions…cont’d
• With our previous example, let’s say that
do_something() uses a private “helper” function
• If so, we should write the source as:
static int helper(){
return 10;
}
int do_something(int x){
int value = helper();
return value * 2;
}
• Here, helper() is now invisible to the outside
world, but do_something() is publically accessible
4-20
Function location
• You are free to place your functions in any
order in your source file
– even main() can be anywhere in the file, though by
convention is is typically the first or last function in
the source file.
• However, you can not invoke a function even in
the same source file, if the compiler has not
seen the function declaration already
– If you do, you will get the standard warning about an
“implicit declaration”
– Again, this happens even if you only have one source
file in your application.
4-21
Location…cont’d
• So this is not a good way to write our example:
int do_something(int x){
int value = helper(); // compiler will use default here
return value * 2;
}
static int helper(){
return 10;
}
• So we have a couple of options:
1. Make sure the helper() function definition is first (as
in the previous example)
2. Include a prototype for helper() at the top of the
source file
4-22
Location…cont’d
• As a final point, header files can – and typically – are
included in the source file that is associated with the
header
– For example, mySource.c would use a cpp directive to include
its own prototypes
– #include “mySource.h”
• NOTE: static functions should NOT be included in the
header file since these functions are not part of the API.
– You will likely see a “function not defined” warning if you do
this since the compiler will fail to find a function to match
the prototype.
• NOTE: when you add a prototype for a static function in
the current source file, both the prototype and the full
function definition should include the static specifier.
– Otherwise, the compiler will consider them to be different
functions and warnings/errors will occur
Source files: firstApp.c, results.c, results.h
4-23