| C Programming Headers and libraries | Beginning exercises |
Alibrary in C is a collection of header files, exposed for use by other programs. The library therefore consists of aninterface expressed in a.h file (named the "header") and animplementation 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 moreobject 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 thear 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. thelibc.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.
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.
Createtemperature.c with the following contents:
doubleconversion_offset=32.0;doublec_to_f(doublecelsius){returncelsius*1.8+conversion_offset;}doublef_to_c(doublefahrenheit){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 createstemperature.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.
Recall fromC 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 intemperature.c—now compiled intotemperature.o—from a different file, we must imitate the C standard library and create aheader file which declares our functions.
Createtemperature.h with the following contents:
#ifndef TEMPERATURE_H#define TEMPERATURE_Hexterndoubleconversion_offset;doublec_to_f(doublecelsius);doublef_to_c(doublefahrenheit);#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. |
Theextern keyword signals to the compiler thatconversion_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.
Let's create a program to use our library. Createconverter.c with the following contents:
#include<stdio.h>#include<stdlib.h>#include"temperature.h"intmain(intargc,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 planetconversion_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));returnEXIT_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 aninclude 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 F80 degrees F = 26.666667 degrees C24 degrees C = 57.700000 degrees F80 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 togcc. |
The technical term for a file being compiled, after it has been fed through the preprocessor, istranslation unit. Our program is built from two.c files, so we have two translation units.
To expand onextern from earlier, when applied to a global variable declaration, it changes thelinkage of that global variable. A global variable withinternal linkage lives in and can only be accessed in the current translation unit, while a global variable withexternal 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 thatstatic on a global variable has a different meaning from when it's applied to a variable inside a function.
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, andenum declarationstypedef declarationsThe preprocessor directives intemperature.h areinclude guards:
#ifndef TEMPERATURE_H#define TEMPERATURE_H// ...#endif
They make sure that iftemperature.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 nameTEMPERATURE_H used in the include guards is arbitrary. Any name can he used here, so long as it follows thenaming 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, thenTEMPERATURE_H can't be a variable. So, what is it?
The preprocessor supportsmacros, which are tokens that it keeps track of. Macros are typically named inSCREAMING_SNAKE_CASE. Macros come from two sources:
-DNAME (or-DNAME=value) for Clang and GCC#include NAME (or#include NAME value)These simple, orobject-like, macros exist outside of the C programming language itself. As soon as the preprocessor learns through one of the above methods that a macroNAME exists with definitionvalue, all source code lines it encounters from there on out get the find-and-replace treatment, changingNAME tovalue.
#ifdef–#else–#endif is the preprocessor equivalent of anif–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 forconst 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 'worseconst', such asfunction-like macros, but this is an advanced topic that will be covered later.
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 inC 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.ctemperature.cPROGRAM=converterOBJECTS=$(SOURCES:.c=.o)$(PROGRAM):$(OBJECTS)gcc-o$@$(OBJECTS).c.o:gcc-c-Wall-o$@$< Edit |
| Remember! Makefilesrequire hard tabs! |
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 thelibm math library should be linked in.
Here is an example of a program needing to be linked withlibm:
#include<math.h>#include<stdio.h>#include<stdlib.h>intmain(intargc,char*argv[]){printf("4 ^ 6 = %lf\n",pow(4,6));returnEXIT_SUCCESS;} Output: 4 ^ 6 = 4096.000000 |
| C Programming Headers and libraries | Beginning exercises |