This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License
In this lesson, you will learn how to write code that you can reuse and share between programs using header files and separate compilation.
Each step of the lesson asks you to answer a question or write some code. Do the steps yourself before checking your work by revealing the answer.
struct Employee
“class” that we created in the Objects lesson reusable.
To do this, we need to divide our code into two parts: a “header” file that provides all the declarations needed to use the code, and an “implementation” that provides the actual code that implements those declarations. You can think of the header file as defining the interface (in Java terms), and the implementation as providing, well, the concrete implementation of the interface.
Start with the header file. Create a file named Employee.h
, that is, the header (“.h
”) for the Employee
“class.” In this file, put the declarations only of all the functions defined in employee3.c
(that is, their “signatures,” but not the body code, like an interface in Java).
struct Employee
. We can make this explicit by introducing a typedef
that says this at the top of the file:
typedef struct Employee* Employee;
If this looks confusing, remember that the first occurance of Employee
is not a type but rather a “structure tag.” So this declaration says that the type name Employee
(the second occurance) is defined to be a struct Employee*
, that is, a pointer to an instance of struct Employee
. We used this technique towards the end of the Binary Tree lesson.struct Employee*
in your new header file with simply Employee
. That is, use the type defined by the typedef
that you just added. The result is nice and clean and quite object-oriented.extern
. This “storage class specifier” tells the C compiler that the functions may be refrenced from other “translation units” (files) and will need to be “linked” appropriately.Employee.h
should like this:
typedef struct Employee* Employee;
extern Employee new_Employee(char* name, int id);
extern void Employee_print(Employee this);
extern char* Employee_get_name(Employee this);
extern void Employee_set_name(Employee this, char* name);
extern int Employee_get_id(Employee this);
extern void Employee_set_id(Employee this, int id);
extern void Employee_free(Employee this);
Note that the header file does not define what a struct Employee
actually is. That information about the implementation is hidden from users of the Employee
“class”. This is called an opaque type since other code cannot “see” the implementation inside.
Download file: Employee.h
Employee
“class”. Create a file named Employee.c
, and copy the structure and function definitions (including bodies) from employee3.c
.
struct Employee*
with just Employee
to take advantange of the typedef
in the header file like before. Note that the call to malloc
uses
(struct Employee*)malloc(sizeof(struct Employee)
The cast in front is a pointer type, so it can be replaced. But the argument to sizeof
is the struct type itself. That cannot be replaced. You should understand the difference between sizeof(struct S)
and sizeof(struct S*)
.
Download file: Employee.c
Employee
and the functions defined in the header file. The program will have to be linked with the result of compiling Employee.c
. We will see how that works shortly.
In a new file named Employee_test.c
, write a program that tests the new Employee
“class”. Use only the types and functions defined in the Employee.h
header file. Hint: Keep it simple to start.
Download file: Employee_test.c
Employee.c
and Employee_test.c
, and link them into a single executable program.
If you are using an IDE, it may have looked after this for you. If so, try running it.
If you are working from the command shell, most compilers can do this for you in one command:
cc -o employee_test Employee.c Employee_test.c
You may need to use both gcc
or clang
or something else instead of cc
) for the C compiler.
Then run the resulting executable (which you specified with the -o
option):
./executable_test
You should see the output of the program.
-std=c99
, -Wall
, and -Werror
. These tell the compiler to use the “C99” version of the ANSI C Standard, to report all warnings, and to make warnings into errors so that even though you may not think they're important (“they're just warnings, right?”), your program won't compile if there are any warnings. You can do this on the command line as follows:
gcc -o employee_test -std=c99 -Wall -Werror Employee.c Employee_test.c
You can arrange for this in your IDE also, although the method depends on the IDE.
Employee.c
and Employee_test.c
. These are connected by the definitions in Employee.h
. Why did we do it this way? Why not just have the code for Employee
s in the same file as main
? Try it! Then think about it.
Employee.c
into the top of Employee_test.c
instead of #include "Employee.h"
. Or, equivalently, change #include "Employee.h"
to #include "Employee.c"
, which has the effect of having the C compiler do that for you (actually, the pre-processor, but I digress). So why not do it that way?
If all the code was in one file, then it would all have to be recompiled every time we compiled the program (since there's only the one file to compile). That might not be a problem for this small example, but if there was a lot of code defining employees, then it would be a waste to constantly recompile it even though nothing has changed. Furthermore, there might be many programs that need to represent employees, and they would all be constantly recompiling that code even though they never change it. Remember that when C was created, computers were much, much slower and had less memory than they do now. Compiling programs used to take real time.
Furthermore, for you to include the source code for Employee
s in your program, I need to give it to you. In the first place, this may be an intellectual property issue in some cases of commercial software or homework assignments. There is a long history of debate regarding “free” and “open source” software if you are interested.
But regardless of your opinion about code as intellectual property, if I give you the source code for the implementation, then then you know its internal details. You can write your code to take advantage of that, which would be fine, unless I want to change my implementation. For example, I might want to replace an array list implementation of a list with a linked list. If all I give you is the header file, then I can change the implementation as long as I don't change the interface promised in the header file.
Summarizing: