Compiling & Linking

Compiling & Linking

The process of converting source code, written in a language such as C, to a binary file ready to be executed is called compiling. The widely used GNU C Compiler, more commonly referred to as gcc, almost completely implements the C99 standard (ISO/IEC 9899:1999(E)). However it also implements a range of extensions to the standard which programmers will often use to gain extra functionality, at the expense of portability to another compiler. These extensions are usually related to very low level code and are much more common in the system programming field; the most common extension being used in this area being inline assembly code.

The type (e.g. char, int, etc) tells the compiler about what we expect to store in a variable; the compiler can then both allocate sufficient space for this usage and check that the programmer does not violate the rules of the type. In C, char is 1 byte, int is 4 bytes.

Qualifiers are all intended to pass extra information about how the variable will be used to the compiler. This means two things; the compiler can check if you are violating your own rules (e.g. writing to a const value) and it can make optimizations based upon the extra knowledge.

Linking result in a single executable out of each object code from several source files. Interpreted program on the other hand interprets each line of the input file and executes it as code. This way the program does not need to be compiled, and any changes will be seen the next time the interpreter runs the code.

Important Notes

We may want to use/share the functions in our code that have been compiled as object codes and archived in a library (shared components/objects or archive libraries). The C standard libraries and C++ STL are examples of the shared component.

Share libraries are ".so (shared object)" files which are referenced by the executable (programs) at run-time. Static libraries are ".a (archive)" files which are linked into the program at compile time and has the copies of the code that it uses from the static library which itself becomes the part of a program.

Shared libraries, therefore, reduces the amount of code that is duplicated in each program keeping the binary small, however, unlike static libraries, the dynamic libraries need to be carried along with the program. As the code is connected at compile time, there are no on-time loading costs for static libraries.

Let’s create a library that has multiplication and addition functions fn1 and fn2 respectively as showed below.

cat fn1.c

output:

int product(int a,int b)

{

return a*b;

}

cat fn2.c

output:

int add(int a,int b)

{

return a+b; 

}

Since both the functions do not have main, you will get the error if you try to compile it.

gcc -o driver fn1.c 

output:

/usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../lib64/crt1.o: In function `_start':

(.text+0x20): undefined reference to `main'

collect2: ld returned 1 exit status

So, just create the object files for the functions. You should see object files fn1.o and fn2.o

gcc –c fn1.c fn2.c 

Static Linking

Bundle the object files into an archive called a library (static.a) 

ar rcs static.a fn1.o fn2.o

View the object files and the functions in the library.

nm static.a

output: 

fn1.o:

0000000000000000 T product

 

fn2.o:

0000000000000000 T add

Just want to view the object files in the library?

ar -t libstatic.a 

Create a main file and call the functions as showed:

cat main.c

output:

#include<stdio.h>

main(){

printf("%d %d\n",add(2,3),product(2,3));

}

When you try to compile main.c without linking it to the library, you will get the following errors:

gcc -o execute main.c

output:

/tmp/ccOEPIUM.o: In function `main':

main.c:(.text+0x19): undefined reference to `product'

main.c:(.text+0x2f): undefined reference to `add'

collect2: ld returned 1 exit status

So, you need to link the library. Now, you can find the "execute" as an executable.

gcc -o execute main.c libstatic.a

If the library is not in the current path, you need to specify the Library path and the library:

gcc -o execute main.c -L<path-to-library> –lstatic

(Note: -lstatic not -llibstatic)

The fn1 and fn2 are defined by symbol type T: Normal Code Section

nm execute

output:

T add

000000000040040c t call_gmon_start

0000000000600938 b completed.6349

0000000000600930 W data_start

0000000000600940 b dtor_idx.6351

00000000004004a0 t frame_dummy

00000000004004c4 T main

                 U printf@@GLIBC_2.2.5

0000000000400518 T product

 ...

...

Run the Executable

./execute

output:

5 6

Dynamic Linking

Create position Independent Code (fPIC) objects; -wall: warning

gcc -Wall -fPIC -c fn1.c fn2.c

Create a Dynamic Library (shared objects)

gcc -shared -Wl -o libshared.so fn1.o fn2.o

Compile the code:

gcc -Wall -o driver main.c -L<path-to-library> –lshared

You are linking to the dynamic library i.e. the executable looks for the objects in the shared library on the fly. If you run the executable, you will get the error.

./driver: error while loading shared libraries: libshared.so: cannot open shared object file: No such file or directory

Also, you can check the dependencies for the executable.

ldd driver

output:        

linux-vdso.so.1 =>  (0x00007fff2856b000)

        libshared.so => not found

        libc.so.6 => /lib64/libc.so.6 (0x0000003b93e00000)

        /lib64/ld-linux-x86-64.so.2 (0x0000003b93a00000)

So, you need to make the library accessible. You can set LD_LIBRARY_PATH variable in the shell (interactively) or in .bashrc file.

export LD_LIBRARY_PATH=<path-to-library>:$LD_LIBRARY_PATH

Or, include the path in the /etc/ld.so.conf file (only admin)

Execute now.

./driver

Static to Shared Library

Conver the static library "libblas.a" to Shared Object "libblas.so"

gcc -shared -o libblas.so -Wl,--whole-archive libblas.a -Wl,--no-whole-archive

Makefile

Makefile [3] helps in compiling the code using “make” utility which avoid the command-line compiling.

i. Macros: are similar to variables. Macros are defined in a Makefile as = pairs.

Examples: 

CC: C Compiler; CC=gcc implies that the compiler is the gcc

CFLAGS: Compiler Flags for creating object files and compiling; CFLAGS=-c is a flag  to create object files; -O3 is optimization flag for speed; -Wall is the flag for Warnings, -I<include-dir>;

EXECUTABLE: Flag for the executable name

OBJECTS: name of the object files

LIBS: libraries

INCLUDE: <include-directory)

SOURCES: source files

LDFLAGS: linker flags

ii. Automatic Variables: They are the special feature of make. These variables have values computed afresh for each rule that is executed, based on the target and prerequisites of the rule. In the following makefile template, ‘$@’ is used for the object file name and ‘$<’ for the source file name

iii. Rules: Semantics to build targets

Template:

targets : prerequisites

             recipe

             ...

cat Makefile

output:

CC=gcc

CFLAGS=-c -Wall -I

LIB=<path-to-library>

LDFLAGS=-L$(LIB) -lstatic

SOURCES=main.c fn1.c fn2.c

OBJECTS=$(SOURCES:.c=.o)

EXECUTABLE=driver

all: $(SOURCES) $(EXECUTABLE)

        

$(EXECUTABLE): $(OBJECTS)

        $(CC) $(LDFLAGS) $(OBJECTS) -o $@

.cpp.o:

        $(CC) $(CFLAGS) $< -o $@

clean:

        rm -rf *.o driver

install:

        @echo Need root permission. Executables and others can be placed at the right places usually at /usr/local

Create the executable file "driver" by running make utility from the location where there is a Makefile.

make

Try other make utilities - make install & make clean.

References:

[1] Yolinux.com Tutorial: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

[2] Configure Script: https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Developer_Guide/cmd-autotools-config.html

[3] GNU Manual: http://www.gnu.org/software/make/manual/make.html#toc_Top

[4] Computer Science from Bottom up - https://www.bottomupcs.com/index.xhtml