Linking libraries with applications

By February 24, 2016

Using external libraries with compiled programs

Libraries are collections of functions that are already compiled and that can be included in your program without your having to write the functions yourself or compile them separately.

Why you might use libraries

Saving yourself time by not having to write the functions is one obvious reason to use a library. Additionally, many of the libraries focus on high performance and accuracy. Many of the libraries are very well-tested and proven. Others can add parallelism to computationally intensive functions without you having to write your own parallel code. In general, libraries can provide significant performance or accuracy dividends with a relatively low investment of time. They can also be cited in publications to assure readers that the fundamental numerical components of your work are fully tested and stable.

Compiling and linking with libraries

To use libraries you must link them with your own code. When you write your own code, the compiler turns that into object code, which is understandable by the machine. Even though most modern compilers hide it from you, there is a second step where the object code it created for you must be glued together with all the standard functions you include, and any external libraries, and that is called linking.When linking libraries that are not included with your compiler, you must tell the compiler/linker where to find the file that contains the library – typically .so and/or .a files. For libraries that require prototypes (C/C++, etc.) you must also tell the preprocessor/compiler where to find the header (.h) files. Fortran modules are also needed, if you are compiling Fortran code.

Environment variables from the module

When we install libraries on Flux, we usually create modules for them that will set the appropriate environment variables to make it easier for you to provide the right information to the compiler and the linker.The naming scheme is, typically, a prefix indicating the library, for example, FFTW, followed by a suffix to indicate the variable–s function, for example, _INCLUDE for the directory containing the header files. So, for example, the module for FFTW3 includes the variables FFTW_INCLUDE and FFTW_LIB for the include and library directories, respectively. We also, typically, set a variable to the top level of the library path, for example, FFTW_ROOT. Some configuration schemes want that and infer the rest of the directory structure relative to it.Libraries can often be tied to specific versions of a compiler, so you will want to run

$ module av

to see which compilers and versions are supported.One other variable that is often set by the library module is the LD_LIBRARY_PATH variable, which is used when you run the program to tell it where to find the libraries needed at run time. If you compile and link against an external library, you will almost always need to load the library module when you want to run the program so that this variable gets set.To see the variable names that a module provides you can use the show option to the module command to show what is being set by the module. Here is an edited example of what that would print if you were to run it for FFTW3.

[markmont@flux-login2 ~]$ module show fftw/3.3.4/gcc/4.8.5
-------------------------------------------------------------------------------
   /sw/arcts/centos7/modulefiles/fftw/3.3.4/gcc/4.8.5.lua:
-------------------------------------------------------------------------------
help([[
FFTW consists of libraries for computation of the discrete Fourier transform
in one or more dimensions.  In addition to adding entries to the PATH, MANPATH,
and LD_LIBRARY_PATH, the following environment variables are created.

    FFTW_ROOT       The root of the FFTW installation folder
    FFTW_INCLUDE    The FFTW3 include file folder
    FFTW_LIB        The FFTW3 library folder, which includes single (float),
                    double, and long-double versions of the library, as well
                    as OpenMP and MPI versions.  To use the MPI libary, you
                    must load the corresponding OpenMPI module.

An example of usage of those variables on a compilation command is, for gcc and
icc,

    $ gcc -o fftw3_prb fftw3_prb-c -I${FFTW_INCLUDE} -L${FFTW_LIB} -lfftw3 -lm
    $ icc -o fftw3_prb fftw3_prb-c -I${FFTW_INCLUDE} -L${FFTW_LIB} -lfftw3 -lm

]])
whatis("Name: fftw")
whatis("Description: Libraries for computation of discrete Fourier transform.")
whatis("License information: http://www.fftw.org/fftw3_doc/License-and-Copyright.html")
whatis("Category: Library, Development, Core")
whatis("Package documentation: http://www.fftw.org/fftw3_doc/")
whatis("Version: 3.3.4")
prepend_path("PATH","/sw/arcts/centos7/fftw/3.3.4-gcc-4.8.5/bin")
prepend_path("MANPATH","/sw/arcts/centos7/fftw/3.3.4-gcc-4.8.5/share/man")
prepend_path("LD_LIBRARY_PATH","/sw/arcts/centos7/fftw/3.3.4-gcc-4.8.5/lib")
prepend_path("FFTW_ROOT","/sw/arcts/centos7/fftw/3.3.4-gcc-4.8.5")
prepend_path("FFTW_INCLUDE","/sw/arcts/centos7/fftw/3.3.4-gcc-4.8.5/include")
prepend_path("FFTW_LIB","/sw/arcts/centos7/fftw/3.3.4-gcc-4.8.5/lib")
setenv("FFTW_HOME","/sw/arcts/centos7/fftw/3.3.4-gcc-4.8.5")

[markmont@flux-login2 ~]$

In addition to the environment variables being set, the show option also displays the names of other modules with which FFTW3 conflicts (in this case, just itself), and there may be links to documentation and the vendor web site (not shown above).

Compile and link in one step

Here is an example of compiling and linking a C program with the FFTW3 libraries.

gcc -I$FFTW_INCLUDE -L$FFTW_LIB mysource.c -lfftw3 -o myprogram

Here is a breakdown of the components of that command.

  • -I$FFTW_INCLUDE The -I option to the compiler indicates a location for header files and, in this case, points to a directory that holds the fftw3.h header file.
  • -L$FFTW_LIB The -L compiler option indicates a library location and, in this case, points to a directory that holds the libfftw3.a and libfftw3.so files, which are the library files. Note, you will want to make sure that the -L option precedes the -l option.
  • mysource.c This is the source code that refers to the FFTW3 library functions; that is, your program.
  • -lfftw3 The -l compiler option indicates the name of a library that contains a function referenced in the source code. The compiler will look through the standard library (linker) paths the compiler came with, then the ones added with -L, and it wil link the first libfftw3.* file that it finds (that will be libfftw3.so if you are specifying dynamic linking and libfftw3.a if you are statically linking).
  • -o myprogram The -o option is followed by the name of the final, executable file, in this case myprogram.

Compile and link in multiple steps

Sometimes you will need or want to compile some files without creating the final executable program, for example, if you have many smaller source files that all combine to make a complete executable. Here is an example.

gcc -c -I$FFTW_INCLUDE source1.c 
gcc -c -I$FFTW_INCLUDE source2.c 
gcc -L$FFTW_LIB source1.o source2.o -o myprogram -lfftw3

The -c compiler option tells the compiler to compile an object file only. Note that only the -I option is needed if you are not linking. The header files are needed to create the object code, which contain references to the functions in the library.The last line does not actually compile anything, rather, it links the components. The -L and -l options are the same as on the one-step compilation and linkage command and specifies where the binary library files are located. The -o option specifies the name of the final executable, in this case source.The location of the header files are only needed before linking. Thus the -I flags can be left off for the final step. The same is true for the -L and -l flags, which are only needed for the final link step, and so can be left off the compilation. Note that all the object files to be linked need to be named.

You will typically see this method used in large, complex projects, with many functions spread across many files with lots of interdepenencies. This method minimizes the amount of time it takes to recompile and relink a program if only a small part of it is changed. This is best managed with make and make files.