Today’s lab will help you practice working in a C project with more than one source file.
Unlike some of our recent labs, this one will not include a Makefile.
We’ll look at make again soon, but for today you will practice compiling code in the terminal again.
To get started, download multifile.tar.gz and extract it with the following terminal commands:
$ cd csc161/labs
$ tar xvzf ~/Downloads/multifile.tar.gz
$ cd multifile
The starter code included two source files, farm.c and chicken.c.
Complete the following exercises using these two source files.
Driver: student farther from the whiteboard
farm.c and chicken.c.
Make a guess about what will happen when you compile the code with this terminal command:
$ clang -o farm farm.c chicken.c
Write down your guess, then check it by running the command.
Write down a quick explanation of what happened and why. If you guessed incorrectly you may want to refer back to today’s assigned reading to see why we get this result.
farm.c to correct the provided code. (Use function declarations.)
Confirm your fix worked by compiling and running the program.As our reading discussed, it can be a nuisance to declare functions in every source file that will use them. In C, we use header files to write down declarations for functions that will be used across files. The following exercises walk you through creating a header file for this project.
Driver: student closer to the whiteboard
Create a file named chicken.h.
Take the chicken_sound declaration you wrote in farm.c in the previous part of this lab and move it to chicken.h.
farm.c and chicken.c:
#include "chicken.h"
The chicken.c file includes its own header file because this allows the compiler to check to make sure the declaration and definition match.
Compile the program with the same clang command as before.
Make sure everything still works.
chicken.h to the clang command when compiling this project.
In fact, we will never pass a .h file to a compiler.
Why do you think that is?
Discuss with your partner and write down your guess.You may have noticed two commented-out lines in farm.c that refer to other animal types.
We’re going to add these in a moment, but first we need to think about how we can avoid duplicating code.
Specifically, look at the make_sound function in chicken.c.
Each one of the farm animals our farm simulator supports will use this function, so we should put it in a place where it can be shared easily.
Driver: student farther from the whiteboard
util.h.
Now copy the implementation of make_sound into util.h.
You’ll also need to add this line to the top of util.h:
#include <stdio.h>
You might have noticed that #include can use either "" or <> around the file name, but these have slightly different meanings.
A #include with <> looks for a standard include file on the system, while an #include with "" will look in the current directory.
make_sound from chicken.c.
We’re going to use the version in util.h instead.
We can access this by including util.h in chicken.c.
Add this line to the top of chicken.c:
#include "util.h"
You should now have a project with this arrangement:
farm.cmain and includes both stdio.h and chicken.h.chicken.cchicken_sound and includes both chicken.h and util.h.chicken.hchicken_sound.util.hmake_sound.Now that you’ve moved make_sound to a header file, we’re going to create source files for two more animals in the farm simulator in the following exercises.
Driver: student closer to the whiteboard
chicken.c and chicken.h as a starting point for the next animal (a cow).
Run these terminal commands to copy the files:
$ cp chicken.c cow.c
$ cp chicken.h cow.h
Edit cow.c and cow.h so they define and declare a cow_sound function, and update the sound so it makes sense.
Add the line #include "cow.h" to farm.c, and uncomment the call to cow_sound inside of main.
$ clang -o farm farm.c chicken.c cow.c
You should see an error about a duplicated make_sound symbol when you compiled this project.
This error looks a bit different from the errors we usually get from clang because it is a linker error.
This error tells us that there are two definitions of the make_sound function: one in chicken.c and one in cow.c.
The following exercises will help you understand and resolve this error.
Driver: switch drivers after finishing part 4 below
Why do you think the linker says chicken.c and cow.c contain a definition of make_sound when it is actually written in util.h?
#ifndef SOMEFILE_H
#define SOMEFILE_H
// Declarations go in here
#endif
These include guards ensure that the header file is included only one time.
You have to be careful to make sure you choose a different constant (SOMEFILE_H in the example above) for each header file.
A more modern approach is to use this line at the top of a header file:
#pragma once
Add include guards or #pragma once to all your .h files and then move on to the next step.
$ clang -o farm farm.c chicken.c cow.c
Unfortunately, include guards do not fix the error (although they are a very good idea to use).
That’s because of how C compilation works.
When we compile a multi-file project in C the compiler actually compiles the files separately and combines them later with the linker.
The problem is that util.h defines the function make_sound, and both chicken.c and cow.c pull that definition in when they include util.h.
The real fix for our linker issue is to add one more file, util.c.
We will declare make_sound in util.h, and then define it in util.c.
Add this file along with appropriate includes and then compile the project with this new command:
$ clang -o farm farm.c chicken.c cow.c util.c
At the end of all these exercises your project should have the following organization:
farm.cmain and includes stdio.h, chicken.h, cow.h, and sheep.h.chicken.cchicken_sound and includes both chicken.h and util.h.cow.ccow_sound and includes both cow.h and util.h.sheep.csheep_sound and includes both sheep.h and util.h.util.cmake_sound and includes stdio.h.chicken.h#pragma once) and contains the declaration of chicken_sound.cow.h#pragma once) and contains the declaration of cow_sound.sheep.h#pragma once) and contains the declaration of sheep_sound.util.h#pragma once) and contains the declaration of make_sound.This is obviously a ridiculous number of source files for such a simple project, but this is a common arrangement for multifile C projects.
Most C developers will declare functions and #define constants in header files, then put their implementations in .c files.
There are other ways to share data and functions across files in C, which we’ll discuss soon in our day focused on program design.