C Programming/Headers and libraries
A library in C is a collection of header files, exposed for use by other programs. The library therefore consists of an interface expressed in a .h file (named the "header") and an implementation expressed in a .c file. This .c file might be precompiled or otherwise inaccessible, or it might be available to the programmer. (Note: Libraries may call functions in other libraries such as the Standard C or math libraries to do various tasks.)
The format of a library varies with the operating system and compiler one is using. For example, in the Unix and Linux operating systems, a library consists of one or more object files, which consist of object code that is usually the output of a compiler (if the source language is C or something similar) or an assembler (if the source language is assembly language). These object files are then turned into a library in the form of an archive by the ar archiver (a program that takes files and stores them in a bigger file without regard to compression). The filename for the library usually starts with "lib" and ends with ".a"; e.g. the libc.a file contains the Standard C library and the "libm.a" the mathematics routines, which the linker would then link in. Other operating systems such as Microsoft Windows use a ".lib" extension for libraries and an ".obj" extension for object files. Some programs in the Unix environment such as lex and yacc generate C code that can be linked with the libl and liby libraries to create an executable.
In this chapter, we will create a small library for converting temperatures between Celsius and Fahrenheit and use it in a separate program.
Code across multiple files
[edit | edit source]Up through now, all of our programs have been built from a single source file. Before creating a true library, we must learn to split our code into multiple files.
Compiling code separately
[edit | edit source]Create temperature.c with the following contents:
double conversion_offset = 32.0; double c_to_f(double celsius) { return celsius * 1.8 + conversion_offset; } double f_to_c(double fahrenheit) { return (fahrenheit - conversion_offset) / 1.8; } conversion_offset is broken out into a separate, non-constant variable. We want our library to be configured for reality by default, but configurable for exploring hypothetical scenarios.
If you are using GCC, compile this using:
gcc -c temperature.c
This creates temperature.o, which contains object code for the variable and two functions. The -c flag tells GCC to not automatically link the compiled object code into an executable and instead output just the object code.
Preparing the header file
[edit | edit source]Recall from C Programming/Basics of compilation that the executables we build are automatically linked—'glued'—with the C standard library, but standard library headers must still be #included so the compiler knows how to call the functions being used. In order to use the variable and functions in temperature.c—now compiled into temperature.o—from a different file, we must imitate the C standard library and create a header file which declares our functions.
Create temperature.h with the following contents:
#ifndef TEMPERATURE_H #define TEMPERATURE_H extern double conversion_offset; double c_to_f(double celsius); double f_to_c(double fahrenheit); #endif Don't worry about the preprocessor directives starting with #; they'll be covered shortly. Just know that they aren't statements and don't need to end with semicolons. |
The extern keyword signals to the compiler that conversion_offset doesn't actually exist in the file being compiled and instead exists in another module, which will be linked into the executable.
Each function declaration is just the signature for the associated function, but followed by a semicolon instead of an implementation block.
Using our library
[edit | edit source]Let's create a program to use our library. Create converter.c with the following contents:
#include <stdio.h> #include <stdlib.h> #include "temperature.h" int main(int argc, char *argv[]) { printf("24 degrees C = %lf degrees F\n", c_to_f(24)); printf("80 degrees F = %lf degrees C\n", f_to_c(80)); // Switch to another planet conversion_offset = 14.5; printf("24 degrees C = %lf degrees F\n", c_to_f(24)); printf("80 degrees F = %lf degrees C\n", f_to_c(80)); return EXIT_SUCCESS; } #include "temperature.h" like we were including a C standard library header, but use double quotes (") instead of angle brackets. Angle brackets tell #include to use an include search path (which automatically contains the C standard library) to find the file, while double quotes cause the search path for that file to also include the current directory.
Now, we can finally build and run a program:
Build and run: gcc -o converter converter.c temperature.o ./converter Output: 24 degrees C = 75.200000 degrees F 80 degrees F = 26.666667 degrees C 24 degrees C = 57.700000 degrees F 80 degrees F = 36.388889 degrees C |
Congratulations, you have created a reusable temperature conversion library! Anybody can use this library in their program without the source code, so long as they have the header file.
You can mix .c source code files and .o object code files on the command line to gcc. |
Linkage
[edit | edit source]The technical term for a file being compiled, after it has been fed through the preprocessor, is translation unit. Our program is built from two .c files, so we have two translation units.
To expand on extern from earlier, when applied to a global variable declaration, it changes the linkage of that global variable. A global variable with internal linkage lives in and can only be accessed in the current translation unit, while a global variable with external linkage can be accessed by code in other translation units. Functions have linkage, too, but functions and global variables have different defaults for linkage. In particular:
| Global variables...
| ...while functions:
|
Remember that static on a global variable has a different meaning from when it's applied to a variable inside a function.
What to put in header files
[edit | edit source]As a general rule, headers should contain any declarations and macro definitions (preprocessor #defines) to be "seen" by the other modules in a program.
Possible declarations:
struct,union, andenumdeclarationstypedefdeclarations- external function declarations
- global variable declarations
Include guards
[edit | edit source]The preprocessor directives in temperature.h are include guards:
#ifndef TEMPERATURE_H #define TEMPERATURE_H // ... #endif They make sure that if temperature.h were included more than once in a translation unit, the unit would only see the contents once. This avoids compilation errors due to repeated declarations. The name TEMPERATURE_H used in the include guards is arbitrary. Any name can he used here, so long as it follows the naming rules (don't start with an underscore!) and is unlikely to be used by someone else.
Alternatively, #pragma once in a header file, while nonstandard, can also be used to achieve the same thing in most compilers. |
If the preprocessor is separate from the C programming language, then TEMPERATURE_H can't be a variable. So, what is it?
Simple macros
[edit | edit source]The preprocessor supports macros, which are tokens that it keeps track of. Macros are typically named in SCREAMING_SNAKE_CASE. Macros come from two sources:
- flags passed to the compiler, such as
-DNAME(or-DNAME=value) for Clang and GCC - the preprocessor directive
#include NAME(or#include NAME value)
These simple, or object-like, macros exist outside of the C programming language itself. As soon as the preprocessor learns through one of the above methods that a macro NAME exists with definition value, all source code lines it encounters from there on out get the find-and-replace treatment, changing NAME to value.
#ifdef–#else–#endif is the preprocessor equivalent of an if–else block for checking whether a macro exists, substituting #ifndef to invert the check.
For now, use macros as part of include guards, and consider reaching for const instead whenever you are tempted to define an object-like macro with a value. Macros don't have the type information that constants do, and debugging compilation errors involving macros can be trickier; the line the compiler errors out on won't be the same as the equivalent line in your source file because the preprocessor will have expanded the macro. There's more to macros that make them different from being a 'worse const', such as function-like macros, but this is an advanced topic that will be covered later.
Lazy building
[edit | edit source]Our small programs build nearly instantly on modern computers. However, when building on older computers, or when building a sufficiently large program, compiling all that code can take a long time. When code is split into multiple source files and compiled into multiple object files, it becomes no longer necessary to recompile everything whenever a single file is changed. This speeds up builds. If a single .c file is modified, only that file needs to be recompiled, and the program can be relinked using object files from previous builds. If a single .h file is modified, only files that include that header (whether directly or indirectly) need to be recompiled.
Back in C Programming/Basics of compilation#Automation, we provided a sample Makefile for use with a single source file. Here is an example of a more capable version, which can handle a multi-file project and takes advantage of lazy building:
SOURCES=converter.c temperature.c PROGRAM=converter OBJECTS=$(SOURCES:.c=.o) $(PROGRAM): $(OBJECTS) gcc -o $@ $(OBJECTS) .c.o: gcc -c -Wall -o $@ $< Edit |
| Remember! Makefiles require hard tabs! |
Linking libraries into executables
[edit | edit source]For historical reasons, while most of the C standard library is automatically linked with your program, one part—the math library—isn't always linked by default. As of writing, GCC currently links it by default, but not all compilers do.
Linking libraries into executables varies by operating system and compiler/linker used. For Clang and GCC, directories of linked object files are specified with the -Lpath flag to the compiler, and individual libraries are specified with the -lname option. Following this pattern, -lm option specifies that the libm math library should be linked in.
Here is an example of a program needing to be linked with libm:
#include <math.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { printf("4 ^ 6 = %lf\n", pow(4, 6)); return EXIT_SUCCESS; } Output: 4 ^ 6 = 4096.000000 |
References
[edit | edit source]- C FAQ: "I'm wondering what to put in .c files and what to put in .h files. (What does ".h" mean, anyway?)"
- PIClist thread: "Global variables in projects with many C files."
- "How do I use extern to share variables between source files in C?".