Lets write a simple helloworld program and using which we will try to understand the difference between static and dynamic linking. $ vim helloworld.c #include <stdio.h> int main(int argc, char **argv) { printf("Hello World !"); return 0; } Now, we will try to compile this program as, $ gcc -o helloworld helloworld.c $ file helloworld helloworld: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b397310c8ddfabbdbe75f8021bea768321218984, for GNU/Linux 3.2.0, not stripped $ ldd helloworld linux-vdso.so.1 (0x00007fffd518b000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f976be6a000) /lib64/ld-linux-x86-64.so.2 (0x00007f976c085000) Above two commands, “file” and “ldd” shows that by default “gcc” created dynamically linked executable. Now, lets try to create “statically linked” executable of same program as, $ gcc -o helloworld_static helloworld.c -static $ file helloworld_static helloworld_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=f87f0fec9544c37c004bb11e2a6369633020b31d, for GNU/Linux 3.2.0, not stripped $ ldd helloworld_static not a dynamic executable Above two commands shows we now have a “statically linked” executable.

Command line execution

Terminalbash
gcc -o helloworld helloworld.c  $ file helloworld helloworld: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b397310c8ddfabbdbe75f8021bea768321218984, for GNU/Linux 3.2.0, not stripped $ ldd helloworld 	 linux-vdso.so.1 (0x00007fffd518b000) 	 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f976be6a000) 	 /lib64/ld-linux-x86-64.so.2 (0x00007f976c085000) Above two commands, “file” and “ldd” shows that by default “gcc” created dynamically linked executable. Now, lets try to create “statically linked” executable of same program as,  $ gcc -o helloworld_static helloworld.c -static  $ file helloworld_static  helloworld_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=f87f0fec9544c37c004bb11e2a6369633020b31d, for GNU/Linux 3.2.0, not stripped  $ ldd helloworld_static  	 not a dynamic executable  Above two commands shows we now have a “statically linked” executable. Now lets try to compare the file sizes of both executable as,  $ ls -alh helloworld -rwxrwxr-x 1 myuser myuser 7.2K Sep  2 21:08 helloworld  $ size helloworld     text 	    data 	     bss 	     dec 	     hex 	 filename 1124 	     276 	       4 	    1404 	     57c 	 helloworld $ ls -alh helloworld_static  -rwxrwxr-x 1 myuser myuser 712K Sep  2 21:08 helloworld_static $ size helloworld_static     text 	    data 	     bss 	     dec 	     hex 	 filename 658287 	    4096 	    3644 	  666027 	   a29ab 	 helloworld_static So above two commands, shows that dynamically linked executable “helloworld” generates much smaller in size (7.2K) binary compared to statically linked executable “helloworld_static” (712K) So the first difference is that, statically linked executable requires more space compared to dymanically linked executables which is the disadvantage of static linking and advantage of dynamic linking. Another difference is,  If several processes call the same object module of a shared library simultaneously, Only one copy of shared library resides in memory for dynamically linked executables where as for static linking all things of library gets copied as part of static linking hence Several copies each for a process resides in memory. $ strace ./helloworld execve("./helloworld", ["./helloworld"], 0x7ffc2f026cb0 /* 52 vars */) = 0 brk(NULL)                               = 0x55b2e6aed000 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcd6360fa0) = -1 EINVAL (Invalid argument) access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=137444, ...}) = 0 mmap(NULL, 137444, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f67a48bc000 close(3)                                = 0 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f67a48ba000 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f67a46c8000 mprotect(0x7f67a46ed000, 1847296, PROT_NONE) = 0 mmap(0x7f67a46ed000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f67a46ed000 mmap(0x7f67a4865000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f67a4865000 mmap(0x7f67a48b0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f67a48b0000 mmap(0x7f67a48b6000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f67a48b6000 close(3)                                = 0 arch_prctl(ARCH_SET_FS, 0x7f67a48bb540) = 0 mprotect(0x7f67a48b0000, 12288, PROT_READ) = 0 mprotect(0x55b2e5e30000, 4096, PROT_READ) = 0 mprotect(0x7f67a490b000, 4096, PROT_READ) = 0 munmap(0x7f67a48bc000, 137444)          = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0 brk(NULL)                               = 0x55b2e6aed000 brk(0x55b2e6b0e000)                     = 0x55b2e6b0e000 write(1, "Hello World !", 13Hello World !)           = 13 exit_group(0)                           = ? +++ exited with 0 +++ $ strace ./helloworld_static  execve("./helloworld_static", ["./helloworld_static"], 0x7ffe79742f30 /* 52 vars */) = 0 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd2317cbb0) = -1 EINVAL (Invalid argument) brk(NULL)                               = 0x1e80000 brk(0x1e811c0)                          = 0x1e811c0 arch_prctl(ARCH_SET_FS, 0x1e80880)      = 0 uname({sysname="Linux", nodename="devlab", ...}) = 0 readlink("/proc/self/exe", "/home/devlab/Desktop/lynxbee/hel"..., 4096) = 46 brk(0x1ea21c0)                          = 0x1ea21c0 brk(0x1ea3000)                          = 0x1ea3000 mprotect(0x4bd000, 12288, PROT_READ)    = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0 write(1, "Hello World !", 13Hello World !)           = 13 exit_group(0)                           = ? +++ exited with

Risk level: destructive. Review the command before running it.

Implementation details

Now lets try to compare the file sizes of both executable as, $ ls -alh helloworld -rwxrwxr-x 1 myuser myuser 7.2K Sep 2 21:08 helloworld $ size helloworld text data bss dec hex filename 1124 276 4 1404 57c helloworld $ ls -alh helloworld_static -rwxrwxr-x 1 myuser myuser 712K Sep 2 21:08 helloworld_static $ size helloworld_static text data bss dec hex filename 658287 4096 3644 666027 a29ab helloworld_static So above two commands, shows that dynamically linked executable “helloworld” generates much smaller in size (7.2K) binary compared to statically linked executable “helloworld_static” (712K) So the first difference is that, statically linked executable requires more space compared to dymanically linked executables which is the disadvantage of static linking and advantage of dynamic linking. Another difference is, If several processes call the same object module of a shared library simultaneously, Only one copy of shared library resides in memory for dynamically linked executables where as for static linking all things of library gets copied as part of static linking hence Several copies each for a process resides in memory. $ strace ./helloworld execve("./helloworld", ["./helloworld"], 0x7ffc2f026cb0 /* 52 vars */) = 0 brk(NULL) = 0x55b2e6aed000 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcd6360fa0) = -1 EINVAL (Invalid argument) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=137444, ...}) = 0 mmap(NULL, 137444, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f67a48bc000 close(3) = 0 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f67a48ba000 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f67a46c8000 mprotect(0x7f67a46ed000, 1847296, PROT_NONE) = 0 mmap(0x7f67a46ed000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f67a46ed000 mmap(0x7f67a4865000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f67a4865000 mmap(0x7f67a48b0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f67a48b0000 mmap(0x7f67a48b6000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f67a48b6000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7f67a48bb540) = 0 mprotect(0x7f67a48b0000, 12288, PROT_READ) = 0 mprotect(0x55b2e5e30000, 4096, PROT_READ) = 0 mprotect(0x7f67a490b000, 4096, PROT_READ) = 0 munmap(0x7f67a48bc000, 137444) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0 brk(NULL) = 0x55b2e6aed000 brk(0x55b2e6b0e000) = 0x55b2e6b0e000 write(1, "Hello World !", 13Hello World !) = 13 exit_group(0) = ? +++ exited with 0 +++ $ strace ./helloworld_static execve("./helloworld_static", ["./helloworld_static"], 0x7ffe79742f30 /* 52 vars */) = 0 arch_prctl(0x3001 /* ARCH_???

Gotchas and common issues

  • Permission checks - verify user access rights and sudo privileges before executing system-level operations.

  • Environment configuration - double-check path variables and dependency versions to prevent runtime failures.

  • Backup safeguards - maintain configuration backups before applying system or database modifications.

Following these steps ensures clean configuration and reliable execution for difference between static and dynamic library linking.