A static library is a collection of object modules that are placed together in an archive file. Think of it as a repository of code, which is linked with your object code at link time, rather than at runtime. In this section, you will examine how to create, use, and maintain a static library.
Figure 13.1 shows how a small program memory image is allocated in FreeBSD and Linux. Other UNIX platforms will use similar arrangements, but their addresses will be different.
The addresses indicated in Figure 13.1 are only approximate. In the uppermost region of memory are the environment variables. Below them is the top of the stack, which grows downward for most UNIX platforms. At the bottom of the stack is a slack area of unallocated memory.
At the left side of the figure is a series of boxes that represent static library modules and program object modules that are used as input to the linking process. The arrows show how the linker brings them together to form a memory image, which begins at 0x80000000 and works its way up to 0x80049F18. This collection of regions forms what is stored in the executable file.
The region below 0x80000000 is reserved for any dynamic (shared) libraries that may need to be brought into memory when the program begins its execution. This area is also used for attaching to shared data regions of memory.
The library code, which is linked to the executable image and resides beneath the main program in Figure 13.1, is called static library code. It is static because once it is linked to the program, it never changes. This is in contrast to shared library modules, which are loaded at execution time beneath the address 0x80000000. If you change the shared libraries, it is the changed libraries that are loaded and executed with your program. The participating static library code never changes once the executable file has been written.
To demonstrate the use of a static library, a small project that implements a Passwd class is used. This project reinforces the concepts that were covered in Chapter 12, "User ID, Password, and Group Management." Listing 13.1 shows the class definition.
The code in Listing 13.2 implements the methods for Passwd_getuid() and Passwd::getnam().
Listing 13.3 shows code that implements the protected methods Passwd::_import() and Passwd::_dispose(). These methods manage dynamic string memory allocation and destruction.
In order to test the Passwd class that is implemented in Listings 13.1 to 13.3, a main() program is provided in Listing 13.4.
The main() program instantiates the Passwd class in line 10 of Listing 13.4. The first test (lines 18–23) simply looks up root's home directory and reports it (line 20).
The second group of tests are performed in the for loop of lines 27–35. This loop looks up the account names uucp, xyxxy, and games. The shell program for each is listed if the account exists. Account xyzzy is not expected to exist on most systems and is provided as a test of the error exception raised by the object pw.
The result of compiling and running this test should be something like this:
$ make getuid cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall -fPIC -fhandle-exceptions -g import.cc cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall -fPIC -fhandle-exceptions -g getuid.cc ar -r libpasswd.a import.o getuid.o cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall -fPIC -fhandle-exceptions -g main.cc cc -o getuid main.o -L/home/wwg/book3-code/13 -lpasswd -lstdc++ $ ./getuid Root's home dir is /root. Account uucp uses the shell /usr/libexec/uucp/uucico. No such file or directory: looking up account xyzzy. Account games uses the shell /sbin/nologin. $
To aid you with following the upcoming text, please remove the archive file, which the make file above has produced:
$ rm libpasswd.a
The project, when compiled, consists of the following object files, which form input to the linker:
The main program | main.o |
Some protected methods | import.o |
Methods getuid and getnam | getuid.o |
The object module main.o is not a reusable piece of code, but the import.o and getuid.o modules implement a class that can be used by other projects. These two object modules will be placed into a static library for general use.
The ar(1) command is used to create and maintain archive files. Since a static library is a special form of an archive, then the ar(1) command can be used to create a static library.
If you have the object modules import.o and getuid.o, the static library libpasswd.a can be created as follows:
$ ar r libpasswd.a import.o getuid.o
The ar(1) command is one of those UNIX commands that break from the traditional getopt(3) processing standard. However, most UNIX platforms today now support a leading hyphen character for this command, allowing it to be given as follows:
$ ar -r libpasswd.a import.o getuid.o
The -r (or simply r) that follows the command name is an option letter that causes the archive libpasswd.a to be created if necessary and replaces the listed object modules if they already exist. If they do not exist in the archive, the listed object modules are added to it.
The normal convention for a library is that it begins with the three letters lib. Archives use the suffix .a. Following these conventions, you end up with a static library named libpasswd.a.
Archives can be updated after their initial creation. If you discover that the getuid.o module has bugs in it, you can replace it with the fixed and recompiled version of the object module, as follows:
$ ar -r libpasswd.a getuid.o
This type of update is generally performed only for large libraries. Smaller archives are usually re-created from scratch by the make file. The following example shows how a make file creates the static library libpasswd.a:
$ make libpasswd.a
cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall -fhandle-exceptions import.cc
cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall -fhandle-exceptions getuid.cc
ar -r libpasswd.a import.o getuid.o
$
At the completion of this session, the static library libpasswd.a is ready to be used by other projects.
You can list the contents of an existing archive by performing the following:
$ ar -t libpasswd.a
import.o
getuid.o
$
The option -t (or simply t) causes ar(1) to list the table of contents for the archive named.
More information can be displayed by adding the option letter v for a verbose table of contents:
$ ar -tv libpasswd.a
rw-r----- 1001/2010 2536 May 11 12:18 2000 import.o
rw-r----- 1001/2010 2948 May 11 12:18 2000 getuid.o
$
The leftmost column shows the permission bits that were present when the module was added to the archive. These are displayed in the same form as the ls(1) command. The numbers 1001 and 2010 in the example represent the user ID and group ID numbers, respectively. The date and time are also shown, just left of the module filenames.
The link step for shared libraries is easy to accomplish. The filename of the static library can be placed on the link command line like any object module. Alternatively, you can place the library in a certain directory and link with it using the -l option. The following example shows the former method of specifying the filename:
$ cc -o getuid main.o libpasswd.a -lstdc++
In the command shown, the file libpasswd.a is simply specified on the command line, where any *.o object file could have been given. In larger projects, it's often desirable to place the shared library in a central directory, /usr/local/lib, for example. When this is done, you need to tell the linker where this special directory is, using the -L option. One or more library can then be specified using the -l option. The following is a simple example:
$ make getuid
cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall -fhandle-exceptions main.cc
cc -o getuid main.o -L/home/me/myproject -lpasswd -lstdc++
$
In this example, the link step specified -L/home/me/myproject to indicate that libraries will be found there. The option -lpasswd caused the linker to look for the library libpasswd.a, in the indicated directory (in addition to system standard directories).
The highlights of the linking process are shown in Figure 13.2.
The steps used in the linking process can be summarized as follows:
The linking process begins with the loading of the main.o module (in this example).
Then, the linker notes that there are undefined references to symbols Passwd:getuid() and Passwd::getnam() referenced by the main() function.
Since a library has been provided (libpasswd.a), this archive file is searched for an object module that defines the symbol Passwd::getuid. The linker locates a function named Passwd:getnam() in the object module getuid.o, which is contained within the archive file. The linker then extracts module getuid.o from the archive file and loads it. In the process of doing this, the symbol Passwd::getnam() is resolved as well, since it is also contained in the same object module.
This is an oversimplification of the linking process, since references to the C library functions such as free(3) were ignored (for the delete C++ keyword). However, this illustrates what happens when a static library is involved in the linking process.
From this process, it has been demonstrated that object modules are brought in by the linker only as required. This bodes well for those who wish to use only a few functions in a large collection of functions. After all, you do not want to link with every object module if you need only a small part of the library.
3.147.89.24