C Programs Compilation Process Steps

C is a high level language and it needs a compiler to convert it into an executable code so that the program can be run on our machine. So, this post will give you some basic Idea on what are all the intermediate files that get generated and also focus on the processes that happens in between. If know all the steps involved in the compilation process then you will come to know how you want to write your program.

1.0 How do we compile and run a C program?

Below are the steps we use on an Ubuntu machine with gcc compiler.

  1. Open the “gnome-terminal” window. Type command “gedit” to open the editor window.

  2. Create a file called “filename.c” with following contents,

    // Program for the addition of two numbers

    #include <stdio.h>

    #define add(a,b) (a+b)

    int main ()

    {

    int a=5, b=4;

    printf(“Addition is:%d\n”, add(a,b));

    return 0;

    }

    The above program adds two numbers.

  3. Then compile the file using “gcc -Wall -save-temps=obj filename.c -o filename

    where, -Wall option will enable all compiler’s warning messages.

    -o option is used to specify the output filename.

  4. To run the executable file “./filename”.

2.0 What goes inside the compilation process?

Compiler converts a C program into an executable. There are four phases for a C program to become an executable:

2.1. Pre-processing: This is the first phase through which source code is passed. This phase include:

  • Removal of Comments.

  • Expansion of Macros.

  • Expansion of the included files.

After the pre-processing step, you will get a new source file named “filename.i”. The contents of the file are as shown below,

# 1 “filename.c”

# 1 “<built-in>”

# 1 “<command-line>”

# 1 “/usr/include/stdc-predef.h” 1 3 4

# 1 “<command-line>” 2

# 1 “filename.c”

# 1 “/usr/include/stdio.h” 1 3 4

# 27 “/usr/include/stdio.h” 3 4

# 1 “/usr/include/features.h” 1 3 4

# 367 “/usr/include/features.h” 3 4

# 1 “/usr/include/x86_64-linux-gnu/sys/cdefs.h” 1 3 4

# 410 “/usr/include/x86_64-linux-gnu/sys/cdefs.h” 3 4

# 1 “/usr/include/x86_64-linux-gnu/bits/wordsize.h” 1 3 4

# 411 “/usr/include/x86_64-linux-gnu/sys/cdefs.h” 2 3 4

# 368 “/usr/include/features.h” 2 3 4

# 391 “/usr/include/features.h” 3 4

# 1 “/usr/include/x86_64-linux-gnu/gnu/stubs.h” 1 3 4

# 10 “/usr/include/x86_64-linux-gnu/gnu/stubs.h” 3 4

# 1 “/usr/include/x86_64-linux-gnu/gnu/stubs-64.h” 1 3 4

# 11 “/usr/include/x86_64-linux-gnu/gnu/stubs.h” 2 3 4

# 392 “/usr/include/features.h” 2 3 4

# 28 “/usr/include/stdio.h” 2 3 4

# 1 “/usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h” 1 3 4

# 216 “/usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h” 3 4

# 216 “/usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h” 3 4

typedef long unsigned int size_t;

# 34 “/usr/include/stdio.h” 2 3 4

# 1 “/usr/include/x86_64-linux-gnu/bits/types.h” 1 3 4

# 27 “/usr/include/x86_64-linux-gnu/bits/types.h” 3 4

# 1 “/usr/include/x86_64-linux-gnu/bits/wordsize.h” 1 3 4

# 28 “/usr/include/x86_64-linux-gnu/bits/types.h” 2 3 4

typedef unsigned char __u_char;

typedef unsigned short int __u_short;

typedef unsigned int __u_int;

typedef unsigned long int __u_long;

typedef signed char __int8_t;

typedef unsigned char __uint8_t;

typedef signed short int __int16_t;

typedef unsigned short int __uint16_t;

typedef signed int __int32_t;

typedef unsigned int __uint32_t;

typedef signed long int __int64_t;

typedef unsigned long int __uint64_t;

typedef long int __quad_t;

typedef unsigned long int __u_quad_t;

# 121 “/usr/include/x86_64-linux-gnu/bits/types.h” 3 4

# 1 “/usr/include/x86_64-linux-gnu/bits/typesizes.h” 1 3 4

# 122 “/usr/include/x86_64-linux-gnu/bits/types.h” 2 3 4

typedef unsigned long int __dev_t;

typedef unsigned int __uid_t;

typedef unsigned int __gid_t;

typedef unsigned long int __ino_t;

typedef unsigned long int __ino64_t;

typedef unsigned int __mode_t;

typedef unsigned long int __nlink_t;

typedef long int __off_t;

typedef long int __off64_t;

typedef int __pid_t;

typedef struct { int __val[2]; } __fsid_t;

typedef long int __clock_t;

typedef unsigned long int __rlim_t;

typedef unsigned long int __rlim64_t;

typedef unsigned int __id_t;

typedef long int __time_t;

typedef unsigned int __useconds_t;

typedef long int __suseconds_t;

typedef int __daddr_t;

typedef int __key_t;

typedef int __clockid_t;

typedef void * __timer_t;

typedef long int __blksize_t;

typedef long int __blkcnt_t;

typedef long int __blkcnt64_t;

typedef unsigned long int __fsblkcnt_t;

typedef unsigned long int __fsblkcnt64_t;

typedef unsigned long int __fsfilcnt_t;

typedef unsigned long int __fsfilcnt64_t;

typedef long int __fsword_t;

typedef long int __ssize_t;

typedef long int __syscall_slong_t;

typedef unsigned long int __syscall_ulong_t;

typedef __off64_t __loff_t;

typedef __quad_t *__qaddr_t;

typedef char *__caddr_t;

typedef long int __intptr_t;

typedef unsigned int __socklen_t;

# 36 “/usr/include/stdio.h” 2 3 4

# 44 “/usr/include/stdio.h” 3 4

struct _IO_FILE;

typedef struct _IO_FILE FILE;

# 64 “/usr/include/stdio.h” 3 4

typedef struct _IO_FILE __FILE;

# 74 “/usr/include/stdio.h” 3 4

# 1 “/usr/include/libio.h” 1 3 4

# 31 “/usr/include/libio.h” 3 4

# 1 “/usr/include/_G_config.h” 1 3 4

# 15 “/usr/include/_G_config.h” 3 4

# 1 “/usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h” 1 3 4

# 16 “/usr/include/_G_config.h” 2 3 4

# 1 “/usr/include/wchar.h” 1 3 4

# 82 “/usr/include/wchar.h” 3 4

typedef struct

{

int __count;

union

{

unsigned int __wch;

char __wchb[4];

} __value;

} __mbstate_t;

# 21 “/usr/include/_G_config.h” 2 3 4

typedef struct

{

__off_t __pos;

__mbstate_t __state;

} _G_fpos_t;

typedef struct

{

__off64_t __pos;

__mbstate_t __state;

} _G_fpos64_t;

# 32 “/usr/include/libio.h” 2 3 4

# 49 “/usr/include/libio.h” 3 4

# 1 “/usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h” 1 3 4

# 40 “/usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h” 3 4

typedef __builtin_va_list __gnuc_va_list;

# 50 “/usr/include/libio.h” 2 3 4

# 144 “/usr/include/libio.h” 3 4

struct _IO_jump_t; struct _IO_FILE;

typedef void _IO_lock_t;

struct _IO_marker {

struct _IO_marker *_next;

struct _IO_FILE *_sbuf;

int _pos;

# 173 “/usr/include/libio.h” 3 4

};

enum __codecvt_result

{

__codecvt_ok,

__codecvt_partial,

__codecvt_error,

__codecvt_noconv

};

# 241 “/usr/include/libio.h” 3 4

struct _IO_FILE {

int _flags;

char* _IO_read_ptr;

char* _IO_read_end;

char* _IO_read_base;

char* _IO_write_base;

char* _IO_write_ptr;

char* _IO_write_end;

char* _IO_buf_base;

char* _IO_buf_end;

char *_IO_save_base;

char *_IO_backup_base;

char *_IO_save_end;

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;

int _flags2;

__off_t _old_offset;

unsigned short _cur_column;

signed char _vtable_offset;

char _shortbuf[1];

_IO_lock_t *_lock;

# 289 “/usr/include/libio.h” 3 4

__off64_t _offset;

void *__pad1;

void *__pad2;

void *__pad3;

void *__pad4;

size_t __pad5;

int _mode;

char _unused2[15 * sizeof (int) – 4 * sizeof (void *) – sizeof (size_t)];

};

typedef struct _IO_FILE _IO_FILE;

struct _IO_FILE_plus;

extern struct _IO_FILE_plus _IO_2_1_stdin_;

extern struct _IO_FILE_plus _IO_2_1_stdout_;

extern struct _IO_FILE_plus _IO_2_1_stderr_;

# 333 “/usr/include/libio.h” 3 4

typedef __ssize_t __io_read_fn (void *__cookie, char *__buf, size_t __nbytes);

typedef __ssize_t __io_write_fn (void *__cookie, const char *__buf,

size_t __n);

typedef int __io_seek_fn (void *__cookie, __off64_t *__pos, int __w);

typedef int __io_close_fn (void *__cookie);

# 385 “/usr/include/libio.h” 3 4

extern int __underflow (_IO_FILE *);

extern int __uflow (_IO_FILE *);

extern int __overflow (_IO_FILE *, int);

# 429 “/usr/include/libio.h” 3 4

extern int _IO_getc (_IO_FILE *__fp);

extern int _IO_putc (int __c, _IO_FILE *__fp);

extern int _IO_feof (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__));

extern int _IO_ferror (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__));

extern int _IO_peekc_locked (_IO_FILE *__fp);

extern void _IO_flockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));

extern void _IO_funlockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));

extern int _IO_ftrylockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));

# 459 “/usr/include/libio.h” 3 4

extern int _IO_vfscanf (_IO_FILE * __restrict, const char * __restrict,

__gnuc_va_list, int *__restrict);

extern int _IO_vfprintf (_IO_FILE *__restrict, const char *__restrict,

__gnuc_va_list);

extern __ssize_t _IO_padn (_IO_FILE *, int, __ssize_t);

extern size_t _IO_sgetn (_IO_FILE *, void *, size_t);

extern __off64_t _IO_seekoff (_IO_FILE *, __off64_t, int, int);

extern __off64_t _IO_seekpos (_IO_FILE *, __off64_t, int);

extern void _IO_free_backup_area (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));

# 75 “/usr/include/stdio.h” 2 3 4

typedef __gnuc_va_list va_list;

# 90 “/usr/include/stdio.h” 3 4

typedef __off_t off_t;

# 102 “/usr/include/stdio.h” 3 4

typedef __ssize_t ssize_t;

typedef _G_fpos_t fpos_t;

# 164 “/usr/include/stdio.h” 3 4

# 1 “/usr/include/x86_64-linux-gnu/bits/stdio_lim.h” 1 3 4

# 165 “/usr/include/stdio.h” 2 3 4

extern struct _IO_FILE *stdin;

extern struct _IO_FILE *stdout;

extern struct _IO_FILE *stderr;

extern int remove (const char *__filename) __attribute__ ((__nothrow__ , __leaf__));

extern int rename (const char *__old, const char *__new) __attribute__ ((__nothrow__ , __leaf__));

extern int renameat (int __oldfd, const char *__old, int __newfd,

const char *__new) __attribute__ ((__nothrow__ , __leaf__));

extern FILE *tmpfile (void) ;

# 209 “/usr/include/stdio.h” 3 4

extern char *tmpnam (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ;

extern char *tmpnam_r (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ;

# 227 “/usr/include/stdio.h” 3 4

extern char *tempnam (const char *__dir, const char *__pfx)

__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;

extern int fclose (FILE *__stream);

extern int fflush (FILE *__stream);

# 252 “/usr/include/stdio.h” 3 4

extern int fflush_unlocked (FILE *__stream);

# 266 “/usr/include/stdio.h” 3 4

extern FILE *fopen (const char *__restrict __filename,

const char *__restrict __modes) ;

extern FILE *freopen (const char *__restrict __filename,

const char *__restrict __modes,

FILE *__restrict __stream) ;

# 295 “/usr/include/stdio.h” 3 4

# 306 “/usr/include/stdio.h” 3 4

extern FILE *fdopen (int __fd, const char *__modes) __attribute__ ((__nothrow__ , __leaf__)) ;

# 319 “/usr/include/stdio.h” 3 4

extern FILE *fmemopen (void *__s, size_t __len, const char *__modes)

__attribute__ ((__nothrow__ , __leaf__)) ;

extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __attribute__ ((__nothrow__ , __leaf__)) ;

extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__));

extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf,

int __modes, size_t __n) __attribute__ ((__nothrow__ , __leaf__));

extern void setbuffer (FILE *__restrict __stream, char *__restrict __buf,

size_t __size) __attribute__ ((__nothrow__ , __leaf__));

extern void setlinebuf (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int fprintf (FILE *__restrict __stream,

const char *__restrict __format, …);

extern int printf (const char *__restrict __format, …);

extern int sprintf (char *__restrict __s,

const char *__restrict __format, …) __attribute__ ((__nothrow__));

extern int vfprintf (FILE *__restrict __s, const char *__restrict __format,

__gnuc_va_list __arg);

extern int vprintf (const char *__restrict __format, __gnuc_va_list __arg);

extern int vsprintf (char *__restrict __s, const char *__restrict __format,

__gnuc_va_list __arg) __attribute__ ((__nothrow__));

extern int snprintf (char *__restrict __s, size_t __maxlen,

const char *__restrict __format, …)

__attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 4)));

extern int vsnprintf (char *__restrict __s, size_t __maxlen,

const char *__restrict __format, __gnuc_va_list __arg)

__attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 0)));

# 412 “/usr/include/stdio.h” 3 4

extern int vdprintf (int __fd, const char *__restrict __fmt,

__gnuc_va_list __arg)

__attribute__ ((__format__ (__printf__, 2, 0)));

extern int dprintf (int __fd, const char *__restrict __fmt, …)

__attribute__ ((__format__ (__printf__, 2, 3)));

extern int fscanf (FILE *__restrict __stream,

const char *__restrict __format, …) ;

extern int scanf (const char *__restrict __format, …) ;

extern int sscanf (const char *__restrict __s,

const char *__restrict __format, …) __attribute__ ((__nothrow__ , __leaf__));

# 443 “/usr/include/stdio.h” 3 4

extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, …) __asm__ (“” “__isoc99_fscanf”)

;

extern int scanf (const char *__restrict __format, …) __asm__ (“” “__isoc99_scanf”)

;

extern int sscanf (const char *__restrict __s, const char *__restrict __format, …) __asm__ (“” “__isoc99_sscanf”) __attribute__ ((__nothrow__ , __leaf__))

;

# 463 “/usr/include/stdio.h” 3 4

extern int vfscanf (FILE *__restrict __s, const char *__restrict __format,

__gnuc_va_list __arg)

__attribute__ ((__format__ (__scanf__, 2, 0))) ;

extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg)

__attribute__ ((__format__ (__scanf__, 1, 0))) ;

extern int vsscanf (const char *__restrict __s,

const char *__restrict __format, __gnuc_va_list __arg)

__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0)));

# 494 “/usr/include/stdio.h” 3 4

extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ (“” “__isoc99_vfscanf”)

__attribute__ ((__format__ (__scanf__, 2, 0))) ;

extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __asm__ (“” “__isoc99_vscanf”)

__attribute__ ((__format__ (__scanf__, 1, 0))) ;

extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ (“” “__isoc99_vsscanf”) __attribute__ ((__nothrow__ , __leaf__))

__attribute__ ((__format__ (__scanf__, 2, 0)));

# 522 “/usr/include/stdio.h” 3 4

extern int fgetc (FILE *__stream);

extern int getc (FILE *__stream);

extern int getchar (void);

# 550 “/usr/include/stdio.h” 3 4

extern int getc_unlocked (FILE *__stream);

extern int getchar_unlocked (void);

# 561 “/usr/include/stdio.h” 3 4

extern int fgetc_unlocked (FILE *__stream);

extern int fputc (int __c, FILE *__stream);

extern int putc (int __c, FILE *__stream);

extern int putchar (int __c);

# 594 “/usr/include/stdio.h” 3 4

extern int fputc_unlocked (int __c, FILE *__stream);

extern int putc_unlocked (int __c, FILE *__stream);

extern int putchar_unlocked (int __c);

extern int getw (FILE *__stream);

extern int putw (int __w, FILE *__stream);

extern char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream)

;

# 640 “/usr/include/stdio.h” 3 4

# 665 “/usr/include/stdio.h” 3 4

extern __ssize_t __getdelim (char **__restrict __lineptr,

size_t *__restrict __n, int __delimiter,

FILE *__restrict __stream) ;

extern __ssize_t getdelim (char **__restrict __lineptr,

size_t *__restrict __n, int __delimiter,

FILE *__restrict __stream) ;

extern __ssize_t getline (char **__restrict __lineptr,

size_t *__restrict __n,

FILE *__restrict __stream) ;

extern int fputs (const char *__restrict __s, FILE *__restrict __stream);

extern int puts (const char *__s);

extern int ungetc (int __c, FILE *__stream);

extern size_t fread (void *__restrict __ptr, size_t __size,

size_t __n, FILE *__restrict __stream) ;

extern size_t fwrite (const void *__restrict __ptr, size_t __size,

size_t __n, FILE *__restrict __s);

# 737 “/usr/include/stdio.h” 3 4

extern size_t fread_unlocked (void *__restrict __ptr, size_t __size,

size_t __n, FILE *__restrict __stream) ;

extern size_t fwrite_unlocked (const void *__restrict __ptr, size_t __size,

size_t __n, FILE *__restrict __stream);

extern int fseek (FILE *__stream, long int __off, int __whence);

extern long int ftell (FILE *__stream) ;

extern void rewind (FILE *__stream);

# 773 “/usr/include/stdio.h” 3 4

extern int fseeko (FILE *__stream, __off_t __off, int __whence);

extern __off_t ftello (FILE *__stream) ;

# 792 “/usr/include/stdio.h” 3 4

extern int fgetpos (FILE *__restrict __stream, fpos_t *__restrict __pos);

extern int fsetpos (FILE *__stream, const fpos_t *__pos);

# 815 “/usr/include/stdio.h” 3 4

# 824 “/usr/include/stdio.h” 3 4

extern void clearerr (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int feof (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern int ferror (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern void clearerr_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int feof_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern int ferror_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern void perror (const char *__s);

# 1 “/usr/include/x86_64-linux-gnu/bits/sys_errlist.h” 1 3 4

# 26 “/usr/include/x86_64-linux-gnu/bits/sys_errlist.h” 3 4

extern int sys_nerr;

extern const char *const sys_errlist[];

# 854 “/usr/include/stdio.h” 2 3 4

extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

# 872 “/usr/include/stdio.h” 3 4

extern FILE *popen (const char *__command, const char *__modes) ;

extern int pclose (FILE *__stream);

extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));

# 912 “/usr/include/stdio.h” 3 4

extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

# 942 “/usr/include/stdio.h” 3 4

# 3 “filename.c” 2

# 4 “filename.c”

int main ()

{

int a=5, b=4;

printf(“Addition is:%d\n”, (a+b));

return 0;

}

When we look into the file “filename.i” we see that all the comments are removed. Also, we will see a lot of codes corresponding to the printf function called from include file <stdio.h>. We can also notice that the macro add(a,b) is replaced by (a,b) in printf function.

2.2. Compilation:

The next step is to compile filename.i and produce an; intermediate compiled output file filename.s. This file is in assembly level instructions.

The contents of filename.s is as given below,

.file “filename.c”

.section .rodata

.LC0:

.string “Addition is:%d\n”

.text

.globl main

.type main, @function

main:

.LFB0:

.cfi_startproc

pushq %rbp

.cfi_def_cfa_offset 16

.cfi_offset 6, -16

movq %rsp, %rbp

.cfi_def_cfa_register 6

subq $16, %rsp

movl $5, -8(%rbp)

movl $4, -4(%rbp)

movl -8(%rbp), %edx

movl -4(%rbp), %eax

addl %edx, %eax

movl %eax, %esi

movl $.LC0, %edi

movl $0, %eax

call printf

movl $0, %eax

leave

.cfi_def_cfa 7, 8

ret

.cfi_endproc

.LFE0:

.size main, .-main

.ident “GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609”

.section .note.GNU-stack,””,@progbits

2.3. Assembly:

In this phase the filename.s is taken as input and turned into filename.o by assembler. This file contain machine level instructions. At this phase, only existing code is converted into machine language, the function calls like printf() are not resolved.

2.4. Linking:

This is the final phase in which all the linking of function calls with their definitions are done. Linker knows where all these functions are implemented. Linker does some extra work also, it adds some extra code to our program which is required when the program starts and ends. For example, there is a code which is required for setting up the environment like passing command line arguments. This task can be easily verified by using $size filename.o and $size filename. Through these commands, we know that how output file increases from an object file to an executable file. This is because of the extra code that linker adds with our program.

Note that GCC by default does dynamic linking, so printf() is dynamically linked in above program.

3.0. How to Compile multiple C files together?

Now, I want to rewrite the C program with separate C files that have features for “addition” and “subtraction” of two numbers. The below are the programs.

Filename: main.c

// Program for the addition of two numbers

#include <stdio.h>

#include “add.h”

#include “sub.h”

int main ()

{

int a=5, b=4;

printf(“Addition is:%d\n”, add(a,b));

printf(“Substraction is:%d\n”, sub(a,b));

return 0;

}

Filename: add.c

// C file for “Addition”

#include “add.h”

int add (int a, int b)

{

return a+b;

}

Filename: add.h

// C Header file for “Addition”

int add (int a, int b);

Filename: sub.c

// C file for “Subtraction”

#include “sub.h”

int sub (int a, int b)

{

return a-b;

}

Filename: sub.h

// C Header file for “Subtraction”

int sub (int a, int b);

I compiled all files together with command in gnome-terminal window,

gcc -Wall main.c add.c sub.c -o output

To run the program “./output

The output was,

vijay@vijay-X55U:~/ccaddsub$ ./output

Addition is:9

Subtraction is:1

vijay@vijay-X55U:~/ccaddsub$

Here, both object files “add.o” and sub.o got merged into main.o during linking process. That means the object files are now a part of final executable file “output”. So, if you are writing one more program main2.c with the same functions, which are inside the “add.c” and “sub.c”, being called then again the merging of the obj files takes place in the similar way. If you are writing a large number of programs, then you might end up consuming more memory for programs to store since you are actually using the same obj files everywhere again and again. But more complex programs may have thousands of such files and it might become difficult to include all of them during compilation. So, programmers usually create libraries of extra C files that need to be included during compilation. Then they use the library name instead of lengthy list of object files during compilation process.

4.0. What is a Library?

A libraries are simply a collections of binary object files they help during linking. A library contains hundreds or thousands of object files. Libraries avoid the situation of specifying lengthy list of object files needed during linking process.

There are two types of libraries that can be used during linking process,

  • Static Libraries

  • Dynamic Libraries

    5.1. Static Libraries:

Static libraries are bundle of relocatable object files. Usually they have .a
extension. Now let us demonstrate the use of static libraries below,

Let us consider the above example programs where we have files named main.c, add.h, add.c, sub.h and sub.c. The contents of main.c may change at any point of time depending on the requirements but the add.c and sub.c will remain same and hence we can use them to form a static library here.

First of all let us compile individual files add.c and sub.c to generate add.o and sub.o object files respectively.

Command : “gcc -c add.c

Command : “gcc -c sub.c

Please note the options used here is “-c” which indicates compile source file without linking. After executing above two commands, you will get two new object files named “add.o” and “sub.o”.

Now, let us use these two files and combine them together to form the Static Library named “libmymath.a” file.

Before this, let us be familiar with “ar” command first.

GNU “ar” can optionally create a thin archive, which contains a symbol index and references to the original copies of the member files of the archives. Such an archive is useful for building libraries for use within a local build, where the relocatable objects are expected to remain available, and copying the contents of each object would only waste time and space. Thin archives are also flattened, so that adding one or more archives to a thin archive will add the elements of the nested archive individually. The paths to the elements of the archive are stored relative to the archive itself.

Command: “ar rs libmymath.a add.o sub.o

The above command uses options which are explained below,

r Insert the files member… into archive (with replacement).

s – Write an object-file index into the archive, or update an existing one, even if no other change is made to the archive. You may use this modifier flag either with any operation, or alone.

After executing the above command, you will have a new file “libmymath.a” created which itself can be used as a Static Library for the Program.

Now let us compile the program using the static library. But you have to do following changes,

1. All object files are not needed anymore. Delete them all.

2. The main.c file remains as is,

// Program for the addition of two numbers

#include <stdio.h>

#include “add.h”

#include “sub.h”

int main ()

{

int a=5, b=4;

printf(“Addition is:%d\n”, add(a,b));

printf(“Substraction is:%d\n”, sub(a,b));

return 0;

}

Method 1: This is the one method to compilation.

Command : “gcc -c main.c

Command : “gcc -o output main.o libmymath.a

Method 2: This is another method for compilation.

Command : “gcc main.c -o output -L . -lmymath

In the first method, we compile the main.c to main.o first. Then, we combine main.o and the static library libmymath.a to form the final output file “output”.

In the second method, we directly compile the main.c file along-with the static library we created. Please note that “-L .” is used to indicate that current directory is the path to the location of static library. The option “-lmymath” excludes “lib” which was a part of filename libmymath.a. “-lmymath” can also be written as “-l mymath”.

So, We successfully created a Static Library and executed the program using it.

The Static Libraries in Linux have “.a” extension for filename and have “.lib” extension for Windows environment.

4.2. Dynamic Libraries or Shared Libraries in Linux:

Dynamic linking defers much of the linking process until a program starts running. It performs the linking process “on the fly” as programs are executed in the system. During dynamic linking the name of the shared library is placed in the final executable file while the actual linking takes place at run time when both executable file and library are placed in the memory. The main advantage to using dynamically linked libraries is that the size of executable programs is dramatically reduced because each program does not have to store redundant copies of the library functions that it uses. Also, when DLL functions are updated, programs that use them will automatically obtain their benefits.

The Dynamic Libraries in Linux are called Shared Libraries and have “.so” extension for filename. In Windows environment, they are known as “dynamic link library and have extension “.dll”. In dynamic linking, object files are not combined with programs at compile time, also, they are not copied permanently into the final executable file; therefore, a shared library reduces the size of final executable.

Shared libraries or dynamic link libraries (dlls) serve a great advantage of sharing a single copy of library among multiple programs, hence they are called shared libraries, and the process of linking them with multiple programs is called dynamic linking.

Shared libraries are loaded into memory by programs when they start. When a shared library is loaded properly, all programs that start later automatically use the already loaded shared library. Following text will demonstrate how to create and use shared library on Linux.

4.2.1. Generating Dynamic Libraries:

Now, Let us continue generating shared libraries for the above set of files that we used earlier.

Earlier we had created object files for add.c and sub.c for creating static libraries. Please note that you can not re-use here, but you have to compile with extra options to get fresh object files add.o and sub.o.

Command : “gcc -Wall -fPIC -c add.c

Command : “gcc -Wall -fPIC -c sub.c

Note: Here we have used a fresh option “-fPIC” which enables “position independent code” generation which is special requirement for shared libraries. You can use “-fpic” too.

Now build the library libmymath.so
using the following command.

Command : “gcc -shared -o libmymath.so add.o sub.o

This newly created shared library libheymath.so
can be used as a substitute of libmymath.a.
But to use a shared library is not as straightforward as static
library was. Once you create a shared library you will have to
install it. And, the simplest approach of installation is to copy the
library into one of the standard directories (e.g., /usr/lib) and
run “
ldconfig” command.

Thereafter
executing “ldconfig” command
as
root
or super user
,
the “
output” executable
can be built as follows. I recompile 
main.c also.
You can omit it if 
main.o is
already there in your working directory.

Method 1: This is the one method to compilation.

Command : “gcc -c main.c

Command : “gcc -o output main.o libmymath.so

Method 2: This is another method for compilation.

Command : “gcc –o output main.c -lmymath

You
can list the shared library dependencies which your executable is
dependent upon. The ldd <name-of-executable> command
does that for you.
vijay@vijay-X55U:~/ccaddsub$
ldd output

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

libmymath.so
=> /usr/lib/libmymath.so (0x00007fd22d181000)

libc.so.6
=> /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd22cdb7000)

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

vijay@vijay-X55U:~/ccaddsub$

5.0. References:

1. http://cs-fundamentals.com/c-programming/static-and-dynamic-linking-in-c.php

2. http://www.geeksforgeeks.org/compiling-a-c-program-behind-the-scenes/

Leave a Reply

Your email address will not be published. Required fields are marked *