C Programming/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.
We're going to use as an example a library that contains one function: a function to parse arguments from the command line. Arguments on the command line could be by themselves:
-i
have an optional argument that is concatenated to the letter:
-ioptarg
or have the argument in a separate argv-element:
-i optarg
The library also has four declarations that it exports in addition to the function: three integers and a pointer to the optional argument. If the argument does not have an optional argument, the pointer to the optional argument will be null.
In order to parse all these types of arguments, we have written the following "getopt.c" file:
#include <stdio.h> /* for fprintf() and EOF */
#include <string.h> /* for strchr() */
#include "getopt.h" /* consistency check */
/* variables */
int opterr = 1; /* getopt prints errors if this is on */
int optind = 1; /* token pointer */
int optopt; /* option character passed back to user */
char *optarg; /* flag argument (or value) */
/* function */
/* return option character, EOF if no more or ? if problem.
The arguments to the function:
argc, argv - the arguments to the main() function. An argument of "--"
stops the processing.
opts - a string containing the valid option characters.
an option character followed by a colon (:) indicates that
the option has a required argument.
*/
int
getopt (int argc, char **argv, char *opts)
{
static int sp = 1; /* character index into current token */
register char *cp; /* pointer into current token */
if (sp == 1)
{
/* check for more flag-like tokens */
if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
return EOF;
else if (strcmp (argv[optind], "--") == 0)
{
optind++;
return EOF;
}
}
optopt = argv[optind][sp];
if (optopt == ':' || (cp = strchr (opts, optopt)) == NULL)
{
if (opterr)
fprintf (stderr, "%s: invalid option -- '%c'\n", argv[0], optopt);
/* if no characters left in this token, move to next token */
if (argv[optind][++sp] == '\0')
{
optind++;
sp = 1;
}
return '?';
}
if (*++cp == ':')
{
/* if a value is expected, get it */
if (argv[optind][sp + 1] != '\0')
/* flag value is rest of current token */
optarg = argv[optind++] + (sp + 1);
else if (++optind >= argc)
{
if (opterr)
fprintf (stderr, "%s: option requires an argument -- '%c'\n",
argv[0], optopt);
sp = 1;
return '?';
}
else
/* flag value is next token */
optarg = argv[optind++];
sp = 1;
}
else
{
/* set up to look at next char in token, next time */
if (argv[optind][++sp] == '\0')
{
/* no more in current token, so setup next token */
sp = 1;
optind++;
}
optarg = 0;
}
return optopt;
}
/* END OF FILE */
The interface would be the following "getopt.h" file:
#ifndef GETOPT_H
#define GETOPT_H
/* exported variables */
extern int opterr, optind, optopt;
extern char *optarg;
/* exported function */
int getopt(int, char **, char *);
#endif
/* END OF FILE */
At a minimum, a programmer has the interface file to figure out how to use a library, although, in general, the library programmer also wrote documentation on how to use the library. In the above case, the documentation should say that the provided arguments **argv
and *opts
both shouldn't be null pointers (or why would you be using the getopt
function anyway?). Specifically, it typically states what each parameter is for and what return values can be expected in which conditions. Programmers that use a library, are normally not interested in the implementation of the library -- unless the implementation has a bug, in which case he would want to complain somehow.
Both the implementation of the getopts library, and programs that use the library should state #include "getopt.h"
, in order to refer to the corresponding interface. Now the library is "linked" to the program -- the one that contains the main() function. The program may refer to dozens of interfaces.
In some cases, just placing #include "getopt.h"
may appear correct but will still fail to link properly. This indicates that the library is not installed correctly, or there may be some additional configuration required. You will have to check either the compiler's documentation or library's documentation on how to resolve this issue.
What to put in header files
[edit | edit source]As a general rule, headers should contain any declarations and macro definitions (preprocessor #define
s) 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
In the above getopt.h
example file, one function (getopt
) is declared and four global variables (optind
, optopt
, optarg
, and opterr
) are also declared. The variables are declared with the storage class specifier extern
in the header file because that keyword specifies that the "real" variables are stored elsewhere (i.e. the getopt.c
file) and not within the header file.
The #ifndef GETOPT_H/#define GETOPT_H
trick is colloquially called include guards. This is used so that if the getopt.h
file were included more than once in a translation unit, the unit would only see the contents once. Alternatively, #pragma once
in a header file can also be used to achieve the same thing in some compilers (#pragma
is an unportable catchall).
Linking Libraries Into Executables
[edit | edit source]Linking libraries into executables varies by operating system and compiler/linker used. In Unix, directories of linked object files can be specified with the -L option to the cc command and individual libraries are specified with the -l (small ell) option. The -lm option specifies that the libm math library should be linked in, for example.