A Multifile Program
A First Attempt
Here we've attempted to break the cowchat
program into two files: cowchat.cpp
and printmoos.cpp
.
printmoos.cpp
:
#include <iostream> // for std::cin, std::cout
// Prints the given number of "moos"
void printMoos(int numMoos) {
std::cout << "Moo";
for (int i = 1; i < numMoos; ++i) {
std::cout << " moo";
}
std::cout << ".";
}
cowchat.cpp
:
#include <iostream> // for std::cin, std::cout
#include <string> // for std::string
int main() {
std::cout << "Chat with a cow!";
std::string userIn;
do {
std::cout << std::endl // New line
<< "> "; // The prompt
std::getline(std::cin, userIn);
int nMoos = 1 + userIn.size() % 2;
// ^-- Either 1 or 2 moos;
printMoos(nMoos);
} while (std::cin.good() && userIn != "");
std::cout << "\n\nBye!\n";
return 0;
}
The Problem
When I try it, printmoos.cpp
compiles just fine, but I when I try
to compile cowchat.cpp
, I get
cowchat.cpp:13:9: error: use of undeclared identifier 'printMoos'
printMoos(nMoos);
^
1 error generated.
The Problem: printmoos
Not Declared!
The problem is that we call the function printmoos()
in cowchat.cpp
, but the compiler has no idea what printmoos()
is. The compiler doesn't dig around in any of our other files, so it doesn't know such a function exists.
So from the compiler's perspective, it's just like a made-up nonsense word. It could be
feebleglorp
, as far as the compiler is concerned.You have contacted Feebleglorp. Why do you summon me?
Hay, there! No, no. That was just an example. Sorry!
Why does the compiler need to know ahead of time?
The compiler checks things as it goes through the code it's provided. It needs to know
- That
printmoos
is a genuine function. - What parameters it takes (both how many and what types they are).
- What type of result it returns.
The solution is to declare the function (saying it exists) without defining it (giving all the details of how it works, which we already did in printmoos.cpp
).
Fixing the Problem
cowchat.cpp
(fixed!):
#include <iostream> // for std::cin, std::cout
#include <string> // for std::string
// This line declares the printMoos function.
// It ends in a ; instead of { } because the function definition is elsewhere.
void printMoos(int numMoos);
int main() {
std::cout << "Chat with a cow!";
std::string userIn;
do {
std::cout << std::endl // New line
<< "> "; // The prompt
std::getline(std::cin, userIn);
int nMoos = 1 + userIn.size() % 2;
// ^-- Either 1 or 2 moos;
printMoos(nMoos);
} while (std::cin.good() && userIn != "");
std::cout << "\n\nBye!\n";
return 0;
}
The new line (highlighted) is called a forward declaration. It tells the compiler that somewhere in the code there is a function with this name that takes these parameters and has this return type.
That's enough information to check whether the code in this file has consistent types and correct names.
Now we can compile
cowchat.cpp
intocowchat.o
. Of coursecowchat.o
is kind of incomplete. It refers to a function that it doesn't define.In order to have a complete, self-contained executable file, we need to combine both object files (
cowchat.o
andprintmoos.o
) together!
The process of combining the files is called linking.
It's not really a form of compilation (since we aren't really converting anything). Instead, the linker just fills in gaps where pieces of code refer to things that are defined elsewhere.
Altogether, the commands needed to compile both of these files into a single executable are
clang++ -c printmoos.cpp
, which compilesprintmoos.cpp
intoprintmoos.o
.clang++ -c cowchat.cpp
, which compilescowchat.cpp
intocowchat.o
.clang++ -o cowchat cowchat.o printmoos.o
, which links the object files into the executable filecowchat
.
(When logged in, completion status appears here.)