Compile/Link a Simple C Program

Linux 

 14 Jul 2021 - Turkey



Follow
Follow
Follow

12 minutes to read

GNU Toolchain, including GCC, is included in all Unixes. It is the standard compiler for most Unix-like operating systems. Open a Terminal, and enter “gcc –version”. If gcc is not installed, the system will prompt you to install gcc.

$ gcc --version
// The GNU C and C++ compiler are called gcc and g++, respectively.
$ g++ --version 

Below is the Hello-world C program hello.c:

// hello.c
#include <stdio.h>
 
int main() {
    printf("Hello, world!\n");
    return 0;
}
$ chmod a+x a.out
$ ./a.out
// hello.cpp
#include <iostream>
using namespace std;
 
int main() {
   cout << "Hello, world!" << endl;
   return 0;
}

// Compile-only with -c option

> g++ -c -Wall -g Hello.cpp

o: specifies the output executable filename.

-Wall: prints “all” Warning messages.

-g: generates additional symbolic debuggging information for use with gdb debugger.

The options are:

-c: Compile into object file “Hello.o”. By default, the object file has the same name as the source file with extension of “.o” (there is no need to specify -o option). No linking with other object files or libraries.

Linking is performed when the input file are object files “.o” (instead of source file “.cpp” or “.c”). GCC uses a separate linker program (called ld) to perform the linking.

GCC compiles a C/C++ program into executable in 4 steps as shown in the above diagram. For example, a “gcc -o hello hello.c” is carried out as follows:

Pre-processing: via the GNU C Preprocessor (cpp), which includes the headers (#include) and expands the macros (#define).

> cpp hello.c > hello.i

The resultant intermediate file “hello.i” contains the expanded source code.

Compilation: The compiler compiles the pre-processed source code into assembly code for a specific processor.

> gcc -S hello.i

The -S option specifies to produce assembly code, instead of object code. The resultant assembly file is “hello.s”. Assembly: The assembler (as.exe) converts the assembly code into machine code in the object file “hello.o”.

> as -o hello.o hello.s

Linker: Finally, the linker (ld.exe) links the object code with the library code to produce an executable file “hello.exe”.

> ld -o hello.exe hello.o ...libraries...

Suppose that your program has two source files: file1.cpp, file2.cpp. You could compile all of them in a single command:

> g++ -o myprog file1.cpp file2.cpp 

You need to use g++ to compile C++ program, as follows. We use the -o option to specify the output file name.

compile

Compiling & Linking

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.

// fn1.c
int product(int a,int b)

{

return a*b;

}
// fn2.c
int add(int a,int b)

{

return a+b; 

}

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 static.a 

output:

fn1.o
fn2.o

linking

//main.c
#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

	/usr/bin/ld: /tmp/ccEFTV3u.o: in function `main':
	main.c:(.text+0x11): undefined reference to `product'
	/usr/bin/ld: main.c:(.text+0x27): undefined reference to `add'
	collect2: hata: ld çıkış durumu 1 ile döndü


> gcc -o execute main.c fn1.c fn2.c
	./execute
	5 6

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

> gcc -o execute main.c static.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

000000000040039c r __abi_tag
0000000000401161 T add
0000000000404030 B __bss_start
0000000000404030 b completed.0
0000000000404020 D __data_start
0000000000404020 W data_start
0000000000401070 t deregister_tm_clones
00000000004010e0 t __do_global_dtors_aux
0000000000403e08 d __do_global_dtors_aux_fini_array_entry
0000000000404028 D __dso_handle
0000000000403e10 d _DYNAMIC
0000000000404030 D _edata
0000000000404038 B _end
00000000004011e8 T _fini
0000000000401110 t frame_dummy
0000000000403e00 d __frame_dummy_init_array_entry
0000000000402154 r __FRAME_END__
0000000000404000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
000000000040200c r __GNU_EH_FRAME_HDR
0000000000402000 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000401000 T _init
0000000000403e08 d __init_array_end
0000000000403e00 d __init_array_start
00000000004011e0 T __libc_csu_fini
0000000000401170 T __libc_csu_init
                 U __libc_start_main@GLIBC_2.2.5
0000000000401116 T main
                 U printf@GLIBC_2.2.5
000000000040115b T product
00000000004010a0 t register_tm_clones
0000000000401040 T _start
0000000000404030 D __TMC_END__

Dynamic Linking

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

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

Create a Dynamic Library (shared objects as libshared.so)

> gcc -shared -W -o libshared.so fn1.o fn2.o

dynamic

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. Also, you can check the dependencies for the executable.

ldd driver
	linux-vdso.so.1 (0x00007fff6c1f3000)
	libshared.so => not found
	libc.so.6 => /usr/lib/libc.so.6 (0x00007f2e9e716000)
	/usr/lib64/ld-linux-x86-64.so.2 (0x00007f2e9e911000)

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 “static.a” to Shared Object “shared.so”

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

Searching for Header Files and Libraries (-I, -L and -l)

When compiling the program, the compiler needs the header files to compile the source codes; the linker needs the libraries to resolve external references from other object files or libraries. The compiler and linker will not find the headers/libraries unless you set the appropriate options, which is not obvious for first-time user.

For each of the headers used in your source (via #include directives), the compiler searches the so-called include-paths for these headers. The include-paths are specified via -Idir option (or environment variable CPATH). Since the header’s filename is known (e.g., iostream.h, stdio.h), the compiler only needs the directories.

The linker searches the so-called library-paths for libraries needed to link the program into an executable. The library-path is specified via -Ldir option (uppercase ‘L’ followed by the directory path) (or environment variable LIBRARY_PATH). In addition, you also have to specify the library name. In Unixes, the library libxxx.a is specified via -lxxx option (lowercase letter ‘l’, without the prefix “lib” and “.a” extension). In Windows, provide the full name such as -lxxx.lib. The linker needs to know both the directories as well as the library names. Hence, two options need to be specified.

Default Include-paths, Library-paths and Libraries

Try list the default include-paths in your system used by the “GNU C Preprocessor” via “cpp -v”:

> cpp -v
......
#include "..." araması buradan başlıyor:
#include <...> araması buradan başlıyor:
 /usr/lib64/gcc/x86_64-solus-linux/10/include
 /usr/lib64/gcc/x86_64-solus-linux/10/include-fixed
 /usr/include

Static Library vs. Shared Library

A library is a collection of pre-compiled object files that can be linked into your programs via the linker. Examples are the system functions such as printf() and sqrt().

There are two types of external libraries: static library and shared library.

Because of the advantage of dynamic linking, GCC, by default, links to the shared library if it is available.

You can list the contents of a library via “nm filename”.

The utility “ldd” examines an executable and displays a list of the shared libraries that it needs. For example,

> ldd hello
	linux-vdso.so.1 (0x00007ffc2b881000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007f73db5be000)
	/usr/lib64/ld-linux-x86-64.so.2 (0x00007f73db7b9000)

“nm” Utility - List Symbol Table of Object Files

The utility “nm” lists symbol table of object files. For example,

> nm main.o
                 U add
0000000000000000 T main
                 U printf
                 U product
                 
> nm execute | grep main
                 U __libc_start_main@GLIBC_2.2.5
0000000000401116 T main

[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



Last Update: 14 Jul 2021

Share on: