Jump to content

C Programming/Headers and libraries

From Wikibooks, open books for an open world

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 
Note 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.

Note You can think of extern on variable declarations as a way to tell the compiler to treat it the same way that function declarations are treated—that it may exist elsewhere. In fact, you can put extern at the beginning of a function declaration, but it won't do anything because it is already the default!

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:

Example 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.

Note 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...

  • default to internal linkage
  • get external linkage with extern
  • ignore static

...while functions:

  • default to external linkage
  • get internal linkage with static
  • ignore extern

Remember that static on a global variable has a different meaning from when it's applied to a variable inside a function.

Warning Take care not to reuse names for functions and global variables that have external linkage. If you reuse names, individual translation units (or files) will compile successfully, but at link time, the linker will find multiple definitions for the same name, be unsure which one to pick, and give up.

In large C projects, there may be a policy of marking private functions that will never be used outside of their file as static. This ensures their names will never collide with other functions' names in other translation units.

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, and enum declarations
  • typedef declarations
  • 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.

Note 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 ifelse 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:

Example
SOURCES=converter.c temperature.c PROGRAM=converter OBJECTS=$(SOURCES:.c=.o)  $(PROGRAM): $(OBJECTS) gcc -o $@ $(OBJECTS)  .c.o: gcc -c -Wall -o $@ $< 

Edit SOURCES and PROGRAM for your project.

Note 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:

Example
#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 
Note The library search path, which is modified by -L and -l for GCC and Clang, is separate from the #include search path. We can compile the above example regardless of compiler because the C standard library header math.h is always in the include search path, even though the math library object code must be linked separately. For these same compilers, the #include search path can be modified with -I.

References

[edit | edit source]