Fun with Static Libraries
Welcome to another episode of Ariadna shows you weird (SFW, though) stuff! It’s sunny here in Pamplona, which is quite nice considering this is the cold and bitter North, and after some days I was feeling sort of in need of disconnecting from programming and FOSS-y things, I’m back with a topic I’ve wanted to write about for quite some time… Static libraries!
Warning I’m adding after reading this post myself: LOOOOONG POST!
OK, why?
Let me start with a disclaimer, a positive one. I don’t have any strong opinion against dynamic libraries. I am aware that dynamic libraries bring way less benefits than people think, especially when compared against static libraries, but I don’t worry too much about the fact that dynamic libraries are the standard way of doing things nowadays. I use Arch, everything is based on them in this distro. On the other hand, I find static libraries to be much more fun to develop, test, and use in general for someone like me.
Ah, and more convenient when developing simple things, which I will explain in a bit… Well, actually towards the end of this… long… long… OK, you get the idea!
What Is a Static Library?
You might recognize static libraries as files that look like this:
libfoo.a
, compared to dynamic (shared) libraries, which look like:
libbar.so
.1 If we run file
on a static library vs. a dynamic one,
this is what we get:
$ file /usr/lib/libm-2.33.a
/usr/lib/libm-2.33.a: current ar archive
$ file /usr/lib/libm-2.33.so
/usr/lib/libm-2.33.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=2b8fd1f869ecab4e0b55e92f2f151897f6818acf, for GNU/Linux 4.4.0, stripped
If you have some knowledge about building libraries, you know what’s going on
here and you may skip this paragraph. Dynamic libraries are built precisely the
same way as executables are. In fact, actually, executables in modern Linux are
rather a special case of dynamic libraries; if you run file
on any
executable, you’ll see exactly the same output as the one we get for a dynamic
library. On the other hand, to build a static library, what you do is to grab
all the object files you compiled and glue them all together into an ar
archive, prepending an index to it so that it’s easy for the linker to know
what’s inside when you use that library. The ar
format is the actual POSIX
archiving format, predating both the tar
(tape ar, because it was meant
for archiving to tape) and cpio
archive formats. Yup, you could use ar
to archive whatever other stuff you’d like to… it’s in no way limited to
building static libraries.
So far, no big deal, right? OK, now let us read some hexdumps! First things first, before getting into reading stuff in binary/hexcode. Let’s not jump over our heads, shall we?
You might be very well aware that The Right Way To Compile Things(tm) when you’ve got a codebase with more than one module is to split compilation into the modules that need to be recompiled, and then relinking everything back together. This is probably the reason why we all use Makefiles, right? If I build, let’s say, sline, this is the result: 2
$ make
Build options:
CPPFLAGS = -DVERSION="0.5.0" -D_POSIX_C_SOURCE=200809L
CFLAGS = -std=c99 -Wpedantic -Wall -Wextra
LDFLAGS =
CC = cc
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"0.5.0\" -D_POSIX_C_SOURCE=200809L -c -o history.o history.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"0.5.0\" -D_POSIX_C_SOURCE=200809L -c -o sline.o sline.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"0.5.0\" -D_POSIX_C_SOURCE=200809L -c -o strlcpy.o strlcpy.c
ar -rcs libsline.a history.o sline.o strlcpy.o
$ ls -l *.o
-rw-r----- 1 ari ari 2536 ago 22 13:04 history.o
-rw-r----- 1 ari ari 12408 ago 22 13:04 sline.o
-rw-r----- 1 ari ari 1360 ago 22 13:04 strlcpy.o
If I was to modify one thing on, let’s say, history.c
, only history.o
is rebuilt:
$ touch history.c
$ make
Build options:
CPPFLAGS = -DVERSION="0.5.0" -D_POSIX_C_SOURCE=200809L
CFLAGS = -std=c99 -Wpedantic -Wall -Wextra
LDFLAGS =
CC = cc
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"0.5.0\" -D_POSIX_C_SOURCE=200809L -c -o history.o history.c
ar -rcs libsline.a history.o sline.o strlcpy.o
Now, if we look at an object file using hexdump, this is what we’d see.
$ hexdump -C history.o | head -n 15
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 a8 06 00 00 00 00 00 00 |................|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 0d 00 0c 00 |....@.....@.....|
00000040 55 48 89 e5 89 7d fc 83 7d fc 00 78 0b 8b 05 00 |UH...}..}..x....|
00000050 00 00 00 39 45 fc 7e 07 b8 00 00 00 00 eb 18 8b |...9E.~.........|
00000060 45 fc 48 98 48 8d 14 c5 00 00 00 00 48 8d 05 00 |E.H.H.......H...|
00000070 00 00 00 48 8b 04 02 5d c3 55 48 89 e5 8b 05 00 |...H...].UH.....|
00000080 00 00 00 48 98 48 8d 14 c5 00 00 00 00 48 8d 05 |...H.H.......H..|
00000090 00 00 00 00 48 8b 04 02 0f b6 00 84 c0 74 21 8b |....H........t!.|
000000a0 05 00 00 00 00 83 c0 01 89 05 00 00 00 00 8b 05 |................|
000000b0 00 00 00 00 83 f8 31 7e 08 e8 00 00 00 00 eb 01 |......1~........|
000000c0 90 5d c3 55 48 89 e5 48 83 ec 10 c7 45 fc 01 00 |.].UH..H....E...|
000000d0 00 00 eb 49 48 8b 15 00 00 00 00 8b 45 fc 48 98 |...IH.......E.H.|
000000e0 48 8d 0c c5 00 00 00 00 48 8d 05 00 00 00 00 48 |H.......H......H|
Oh, that’s the ELF signature, which every ELF object, executable, or library
has in order to tell the world what it is, right? Now let’s inspect the static
library libsline.a
. We already know not to expect an ELF file here, but an
ar
archive… but how does that look like?
$ hexdump -C libsline.a | head -n 35
00000000 21 3c 61 72 63 68 3e 0a 2f 20 20 20 20 20 20 20 |!<arch>./ |
00000010 20 20 20 20 20 20 20 20 30 20 20 20 20 20 20 20 | 0 |
00000020 20 20 20 20 30 20 20 20 20 20 30 20 20 20 20 20 | 0 0 |
00000030 30 20 20 20 20 20 20 20 32 35 34 20 20 20 20 20 |0 254 |
00000040 20 20 60 0a 00 00 00 10 00 00 01 42 00 00 01 42 | `........B...B|
00000050 00 00 01 42 00 00 01 42 00 00 01 42 00 00 01 42 |...B...B...B...B|
00000060 00 00 01 42 00 00 01 42 00 00 0b 66 00 00 0b 66 |...B...B...f...f|
00000070 00 00 0b 66 00 00 0b 66 00 00 0b 66 00 00 0b 66 |...f...f...f...f|
00000080 00 00 0b 66 00 00 3c 1a 68 69 73 74 6f 72 79 00 |...f..<.history.|
00000090 68 69 73 74 5f 63 75 72 72 00 68 69 73 74 5f 70 |hist_curr.hist_p|
000000a0 6f 73 00 68 69 73 74 5f 65 6e 74 72 79 5f 73 69 |os.hist_entry_si|
000000b0 7a 65 00 68 69 73 74 6f 72 79 5f 67 65 74 00 68 |ze.history_get.h|
000000c0 69 73 74 6f 72 79 5f 6e 65 78 74 00 68 69 73 74 |istory_next.hist|
000000d0 6f 72 79 5f 72 6f 74 61 74 65 00 68 69 73 74 6f |ory_rotate.histo|
000000e0 72 79 5f 73 65 74 00 73 6c 69 6e 65 5f 65 72 72 |ry_set.sline_err|
000000f0 00 73 6c 69 6e 65 00 73 6c 69 6e 65 5f 65 6e 64 |.sline.sline_end|
00000100 00 73 6c 69 6e 65 5f 65 72 72 6d 73 67 00 73 6c |.sline_errmsg.sl|
00000110 69 6e 65 5f 73 65 74 75 70 00 73 6c 69 6e 65 5f |ine_setup.sline_|
00000120 73 65 74 5f 70 72 6f 6d 70 74 00 73 6c 69 6e 65 |set_prompt.sline|
00000130 5f 76 65 72 73 69 6f 6e 00 73 74 72 6c 63 70 79 |_version.strlcpy|
00000140 00 00 68 69 73 74 6f 72 79 2e 6f 2f 20 20 20 20 |..history.o/ |
00000150 20 20 30 20 20 20 20 20 20 20 20 20 20 20 30 20 | 0 0 |
00000160 20 20 20 20 30 20 20 20 20 20 36 34 34 20 20 20 | 0 644 |
00000170 20 20 32 35 33 36 20 20 20 20 20 20 60 0a 7f 45 | 2536 `..E|
00000180 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 01 00 |LF..............|
00000190 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 |>...............|
000001a0 00 00 00 00 00 00 a8 06 00 00 00 00 00 00 00 00 |................|
000001b0 00 00 40 00 00 00 00 00 40 00 0d 00 0c 00 55 48 |..@.....@.....UH|
000001c0 89 e5 89 7d fc 83 7d fc 00 78 0b 8b 05 00 00 00 |...}..}..x......|
000001d0 00 39 45 fc 7e 07 b8 00 00 00 00 eb 18 8b 45 fc |.9E.~.........E.|
000001e0 48 98 48 8d 14 c5 00 00 00 00 48 8d 05 00 00 00 |H.H.......H.....|
000001f0 00 48 8b 04 02 5d c3 55 48 89 e5 8b 05 00 00 00 |.H...].UH.......|
00000200 00 48 98 48 8d 14 c5 00 00 00 00 48 8d 05 00 00 |.H.H.......H....|
00000210 00 00 48 8b 04 02 0f b6 00 84 c0 74 21 8b 05 00 |..H........t!...|
00000220 00 00 00 83 c0 01 89 05 00 00 00 00 8b 05 00 00 |................|
Excuse the long shell paste, but I want you to notice a couple of things there. I don’t fully understand the format, but that isn’t needed to see what’s going on here, with respect to static libraries.
- The header is different, of course. This isn’t an ELF file.
- You should easily there’s a list of symbols (the exported ones) after the header.
- After
history.o/
there’s what appears to be some metadata and… the ELF header… and exactly the same content we got fromhistory.o
’s hexdump.
So far, everything is expected. We have a bunch of object files thrown together, with an index of public, exported symbols for the linker… Seriously, Ariadna, don’t waste our time, OK? We already knew this.
This was necessary… though… Very necessary…
The Static Binary Size Myth Debunked
There’s this very widespread idea that if you compile against a static library, the result is a huge binary because you’re “copying the whole library into your executable.” That is a misconception that probably comes from this very bad idea, namely comparing the sizes of an executable compiled against the dynamic version of glibc vs. the static version of glibc. I’m using schain here as an example here because it’s a very small program and it doesn’t require any library except the C Standard Library:
schain is 18K built against the glibc dynamic library:
$ make
Build options:
CPPFLAGS = -DVERSION="2.3.0" -D_POSIX_C_SOURCE=200809L
CFLAGS = -std=c99 -Wpedantic -Wall -Wextra
LDFLAGS =
CC = cc
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o date.o date.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o schain.o schain.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o strlcpy.o strlcpy.c
cc -o schain date.o schain.o strlcpy.o
$ ls -lh schain
-rwxr-x--- 1 ari ari 18K ago 22 13:25 schain
And almost 1M when building it statically against glibc:
$ make
Build options:
CPPFLAGS = -DVERSION="2.3.0" -D_POSIX_C_SOURCE=200809L
CFLAGS = -std=c99 -Wpedantic -Wall -Wextra
LDFLAGS = -static
CC = cc
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o date.o date.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o schain.o schain.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o strlcpy.o strlcpy.c
cc -o schain date.o schain.o strlcpy.o -static
$ ls -lh schain
-rwxr-x--- 1 ari ari 931K ago 22 13:26 schain
“Oh, static libraries suck! Yeah, this is because you’re copy-pasting the whole of glibc into the executable… and glibc is huge!”
What about this, though?
$ make
Build options:
CPPFLAGS = -DVERSION="2.3.0" -D_POSIX_C_SOURCE=200809L
CFLAGS = -std=c99 -Wpedantic -Wall -Wextra
LDFLAGS = -static
CC = musl-gcc
musl-gcc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o date.o date.c
musl-gcc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o schain.o schain.c
musl-gcc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o strlcpy.o strlcpy.c
musl-gcc -o schain date.o schain.o strlcpy.o -static
$ ls -lh schain
-rwxr-x--- 1 ari ari 71K ago 22 13:27 schain
Yup, statically compiling against MUSL makes the executable just 71K…
Oh, OK… MUSL is known to be much smaller than glibc. But this isn’t just about MUSL being smaller than glibc… If we look at how big the static libraries are, you might wanna rethink that idea that using static libraries is copy-pasting a whole library into your program:
$ ls -lh /usr/lib/libc.a
-rw-r--r-- 1 root root 5,1M may 12 21:18 /usr/lib/libc.a
$ ls -lh /usr/lib/musl/lib/libc.a
-rw-r--r-- 1 root root 2,6M ene 19 2021 /usr/lib/musl/lib/libc.a
So static glibc is actually 5,1M and static MUSL libc is 2,6M… None of the static schain builds get to those numbers, respectively. So, we’re not copy-pasting the whole library in any case! What are we doing, then? OK, to answer that I must take a short detour… It’ll be short, trust me!
A Small but Necessary (Musical?) Intermission!
You surely have experienced in the past, when learning C, that you can’t
declare a public symbol more than once across your whole codebase, right? If I
copied strlcpy.c
into strlcpy_dup.c
, without any further modification,
and I expected schain to be built including both files, this would happen:
$ make
Build options:
CPPFLAGS = -DVERSION="2.3.0" -D_POSIX_C_SOURCE=200809L
CFLAGS = -std=c99 -Wpedantic -Wall -Wextra
LDFLAGS =
CC = cc
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o date.o date.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o schain.o schain.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o strlcpy.o strlcpy.c
cc -std=c99 -Wpedantic -Wall -Wextra -DVERSION=\"2.3.0\" -D_POSIX_C_SOURCE=200809L -c -o strlcpy_dup.o strlcpy_dup.c
cc -o schain date.o schain.o strlcpy.o strlcpy_dup.o
/usr/bin/ld: strlcpy_dup.o: in function `strlcpy':
strlcpy_dup.c:(.text+0x0): multiple definition of `strlcpy'; strlcpy.o:strlcpy.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:28: schain] Error 1
That linker error means strlcpy()
was found twice, so it doesn’t know which
one to use. When compiling, you just cannot have a symbol name defined as
non-static more than
once across your object files.
Now Back to Our Show…
Let’s build cras. cras uses sline, so when built, the linker is going to include sline into cras, right? We already know that we’re somehow not including all of sline, as we saw that not the whole of glibc or MUSL libc were being included into schain earlier… but what are the criteria, then?
I want you to look at these directory listings though, before building cras.
For cras:
$ ls -l
total 64
-rw-r----- 1 ari ari 564 may 8 01:21 config.def.h
-rw-r----- 1 ari ari 564 may 8 01:07 config.h
-rw-r----- 1 ari ari 386 ago 18 16:31 config.mk
-rw-r----- 1 ari ari 3266 ago 11 22:21 cras.1
-rw-r----- 1 ari ari 8266 ago 19 09:44 cras.c
-rw-r----- 1 ari ari 1669 jun 20 14:09 date.c
-rw-r----- 1 ari ari 178 jun 19 19:19 date.h
-rw-r----- 1 ari ari 1205 ago 19 09:44 LICENSE
-rw-r----- 1 ari ari 952 ago 3 22:18 Makefile
-rw-r----- 1 ari ari 1600 ago 3 22:18 README.md
-rw-r----- 1 ari ari 1510 abr 10 02:18 strlcpy.c
-rw-r----- 1 ari ari 115 abr 10 02:18 strlcpy.h
-rw-r----- 1 ari ari 3911 ago 19 09:44 tasklst.c
-rw-r----- 1 ari ari 823 ago 8 21:56 tasklst.h
For sline:
$ ls -l
total 56
-rw-r----- 1 ari ari 323 ago 11 22:05 config.mk
-rw-r----- 1 ari ari 785 ago 22 13:06 history.c
-rw-r----- 1 ari ari 311 jul 22 00:32 history.h
-rw-r----- 1 ari ari 1078 jul 25 09:36 LICENSE
-rw-r----- 1 ari ari 1658 ago 11 16:01 Makefile
drwxr-x--- 2 ari ari 4096 ago 11 16:05 man
-rw-r----- 1 ari ari 1939 jul 23 02:14 README.md
-rw-r----- 1 ari ari 8837 ago 11 16:01 sline.c
-rw-r----- 1 ari ari 439 ago 11 16:01 sline.h
-rw-r----- 1 ari ari 818 ago 11 16:01 sline_test.c
-rw-r----- 1 ari ari 1510 jul 21 18:52 strlcpy.c
-rw-r----- 1 ari ari 115 jul 21 18:52 strlcpy.h
Do you see it? Hey, there’s a reason why I used strlcpy.c
as my example
during the Intermission! Both codebases include strlcpy.c
. In sline,
strlcpy()
is… something that might challenge your view of things…
In sline, strlcpy()
isn’t declared as static because it’s used by other
modules. So, within the sline codebase, strlcpy()
is public; it’s not
declared static. And if you remember what were the contents of the static
library’s index:
$ hexdump -C libsline.a | head -n 21
00000000 21 3c 61 72 63 68 3e 0a 2f 20 20 20 20 20 20 20 |!<arch>./ |
00000010 20 20 20 20 20 20 20 20 30 20 20 20 20 20 20 20 | 0 |
00000020 20 20 20 20 30 20 20 20 20 20 30 20 20 20 20 20 | 0 0 |
00000030 30 20 20 20 20 20 20 20 32 35 34 20 20 20 20 20 |0 254 |
00000040 20 20 60 0a 00 00 00 10 00 00 01 42 00 00 01 42 | `........B...B|
00000050 00 00 01 42 00 00 01 42 00 00 01 42 00 00 01 42 |...B...B...B...B|
00000060 00 00 01 42 00 00 01 42 00 00 0b 66 00 00 0b 66 |...B...B...f...f|
00000070 00 00 0b 66 00 00 0b 66 00 00 0b 66 00 00 0b 66 |...f...f...f...f|
00000080 00 00 0b 66 00 00 3c 1a 68 69 73 74 6f 72 79 00 |...f..<.history.|
00000090 68 69 73 74 5f 63 75 72 72 00 68 69 73 74 5f 70 |hist_curr.hist_p|
000000a0 6f 73 00 68 69 73 74 5f 65 6e 74 72 79 5f 73 69 |os.hist_entry_si|
000000b0 7a 65 00 68 69 73 74 6f 72 79 5f 67 65 74 00 68 |ze.history_get.h|
000000c0 69 73 74 6f 72 79 5f 6e 65 78 74 00 68 69 73 74 |istory_next.hist|
000000d0 6f 72 79 5f 72 6f 74 61 74 65 00 68 69 73 74 6f |ory_rotate.histo|
000000e0 72 79 5f 73 65 74 00 73 6c 69 6e 65 5f 65 72 72 |ry_set.sline_err|
000000f0 00 73 6c 69 6e 65 00 73 6c 69 6e 65 5f 65 6e 64 |.sline.sline_end|
00000100 00 73 6c 69 6e 65 5f 65 72 72 6d 73 67 00 73 6c |.sline_errmsg.sl|
00000110 69 6e 65 5f 73 65 74 75 70 00 73 6c 69 6e 65 5f |ine_setup.sline_|
00000120 73 65 74 5f 70 72 6f 6d 70 74 00 73 6c 69 6e 65 |set_prompt.sline|
00000130 5f 76 65 72 73 69 6f 6e 00 73 74 72 6c 63 70 79 |_version.strlcpy|
00000140 00 00 68 69 73 74 6f 72 79 2e 6f 2f 20 20 20 20 |..history.o/ |
There you have it, right at the end of the index: strlcpy
!
So… why does building cras even work? cras includes strlcpy.c
, and we’re
“copy-pasting” sline, which also includes that very same module… Shouldn’t it
fail? What’s going on here?
The Linker Is Your Friend
Using static libraries is the same as using dynamic libraries, but static
libraries get all the bad press, for reasons I will explain later in this
already waaay too long post (Please, don’t kill me!) You’re using precisely
the same linker in both cases: ld
for those of us still chained to the GCC
toolchain… lld
for those in LLVM-land.
When linking everything together, the linker is able to distinguish between object files and libraries. Actually, this goes even a bit further, because within object files order matters, but I won’t get into that today. The linker will prioritize symbol resolution using object files first, and then resort to the libraries you told it to look at. And it will only import the code of that symbol if needed.
Yes, this means you may shadow a library symbol by declaring it locally in your
codebase. That’s how custom malloc()
wrappers work! This is intended
design. If you want to see this in action, I’ve written a simple demo you will
find here.
So, this means that only the parts that are actually needed are copied over to your binary! That explains why schain or cras don’t becoma a massive 5M+ executable… Ring any bells?
Dynamic libraries do precisely the same. OK, the crucial difference is when linking occurs… and consequentially, in the static case it means that you insert the code into the binary… and in the dynamic case, it means that you map the code from RAM into the virtual memory space of your process… but, again, only the required symbols are mapped, not the whole library!
If you read the symbols table for the dynamically compiled version of schain,
you’ll see that, for example, that sprintf()
will be included, but
putchar()
won’t because schain doesn’t ever use it!
$ readelf -s schain | grep 'sprintf'
67: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sprintf@GLIBC_2.2.5
$ readelf -s schain | grep 'putchar'
Isn’t it a constant in the human experience that apparent deep divides are just an illusion?
An another illusion there is with respect to all of this is… how deployment actually works in a real setting. OK, dynamic libraries make users’ lives easier in a usual Linux distro: update the library and you probably don’t need to rebuild the packages that depend on it if no ABI/API changes have occured… This does help to tackle bugs and security issues with a much smaller deployment cost in most cases. In the case of a static library, the rebuild of dependent packages is always required… That means… OK, users will see a bigger update… but… that’s all.
Why the Bad Press, Though?
This is a question I’ve been trying to answer for quite some time in my head… Why static libraries are sort of vilified? OK, not vilified… I’ve never really encountered anyone explicitly talking bad things of them, except the updating issue, maybe? But they are sort of second-class citizens in the grand scheme of things, I feel.
On the other hand, I have found cat-v-ish rabbit holes of irrational ranting, conspiracy theories, Plan 9 fanboyism against dynamic libraries… Those rants are considered harmful 😉
I think binary distributions have some responsibility in dynamic libraries being favored as some sort of “obvious default…” Let’s face it, dynamic libraries made things easier for them right from the beginning. So, static libraries went hidden under the rug in the process. Also, the myth of binary size may have had some influence in this? I’m just speculating there.
I find this a bit sad. As I was developing sline, I thought not supporting a dynamic library to be a very, very good way to help development. If you really need one, it’s easy to build one… but deploy a dynamic library usually involves quite dirty hacks if you’re not willing to install it into a path the linker is aware of… and during development that can be inconvenient. A static library is much easier to manipulate outside standard paths… and your code will behave exactly the same! No worries on that part!
So, I don’t know. It’s a bit of a shame. Again, I don’t have any strong opinons on what’s better… But it’s been a lot of fun to get into the depths of how static libraries really work… and I ended up learning more about dynamic libraries as well!
I hope this post served to give some more bit of information, though… and maybe, why not?, help people to understand a component of our systems a bit better! Because that’s the deal, right? Sharing is caring!
-
However, you’re probably going to find them under filenames like this:
libbar.so.0.1.0
, which is a mess of a problem in and of itself… and I’m not getting into it at all. ↩︎ -
A quick primer on the flags
ar
takes to create an archive usable as a static library:-r
is inserting the provided files and replace them into the archive;-c
creates the archive if it doesn’t exist; and more importantly,-s
is in charge of prepending an index to the beginning of the archive. (cfr.ar(1)
) ↩︎