Question
What Are .a and .so Files in C? Static vs Shared Libraries Explained
Question
I am porting a C application to AIX and am confused about the roles of .a and .so files. What are these file types, and how are they used when building and running a C application?
Short Answer
By the end of this page, you will understand what .a and .so files are, the difference between static and shared libraries, how the compiler and linker use them, and what happens at runtime when your program depends on one of these libraries.
Concept
In C and C-like systems programming, .a and .so files are both library files, but they are used differently.
- A
.afile is usually a static library archive. - A
.sofile is usually a shared object, also called a shared library.
.a files: static libraries
A static library is a bundle of compiled object files collected into one archive file.
For example, if you compile several source files:
math_utils.c
string_utils.c
file_utils.c
You might create a static library like this:
ar rcs libhelpers.a math_utils.o string_utils.o file_utils.o
When you link your program against libhelpers.a, the linker copies the needed machine code from the library into your final executable.
That means:
- the executable becomes larger
- the code is included at build time
- the program usually does not need that
.afile at runtime
.so files: shared libraries
A shared library is compiled code that stays outside the executable. The executable contains references to it, and the operating system loads it when the program starts, or sometimes on demand.
For example:
gcc -shared -o libhelpers.so math_utils.o string_utils.o file_utils.o
When you link against libhelpers.so:
- your executable is usually smaller
- multiple programs can share the same library in memory
- the library must usually be available on the target system at runtime
Why this matters
This matters because building and running are two different stages:
- Build time: the compiler and linker need library files to resolve function references
- Runtime: the operating system loader may need shared libraries to actually start the program
If you link statically, most code is embedded into the executable. If you link dynamically, the executable depends on external shared libraries being present.
About AIX
On AIX, library naming and behavior can be a little different from Linux. In many Unix-like systems:
.ameans static archive.someans shared library
But on AIX, shared library content is often stored in archive files too, sometimes inside a .a file rather than a standalone .so file. That is one reason porting to AIX can be confusing: the file extension alone does not always tell the whole story there.
So the core idea is:
.acommonly represents an archive library.socommonly represents a shared object- the platform decides exactly how those are packaged and loaded
Mental Model
Think of libraries like tool storage for your program.
- A static library (
.a) is like copying the exact tools you need into your own toolbox before leaving the workshop. - A shared library (
.so) is like leaving the tools in a shared cabinet that many workers can use when needed.
Static library
You pack the tools into your own box:
- self-contained
- heavier to carry
- you do not depend on the cabinet later
Shared library
You borrow tools from the shared cabinet:
- lighter personal toolbox
- many people can use the same cabinet
- you must know where the cabinet is when work begins
This is the key difference:
- static linking copies code into the program
- dynamic linking keeps code external and loads it later
Syntax and Examples
Basic naming convention
On many Unix-like systems, libraries are named like this:
libname.afor a static librarylibname.sofor a shared library
When linking with -lname, the linker looks for libname.a or libname.so.
Example: building a static library
Source file
/* greet.c */
#include <stdio.h>
void greet(void) {
printf("Hello from the library!\n");
}
Main program
/* main.c */
void greet(void);
int main(void) {
greet();
return 0;
}
Build steps
gcc -c greet.c -o greet.o
ar rcs libgreet.a greet.o
gcc main.c -L. -lgreet -o app
What happens
gcc -ccompiles source into an object filear rcscreates the archivelibgreet.agcc main.c -L. -lgreet -o applinksmain.cwith the library in the current directory
The needed code from libgreet.a gets copied into app.
Example: building a shared library
gcc -fPIC -c greet.c -o greet.o
gcc -shared -o libgreet.so greet.o
gcc main.c -L. -lgreet -o app
What changes here
-fPICcreates position-independent code, commonly used for shared libraries-sharedbuilds a shared library- the executable links to the shared library instead of embedding all code
At runtime, the system must be able to find libgreet.so.
Running a program with a shared library
On many systems, you may need to set a library search path:
export LD_LIBRARY_PATH=.
./app
This tells the loader where to look for shared libraries.
Common linker style
gcc main.c -L/path/to/libs -lmylib -o app
-L/path/to/libsadds a directory to the library search path-lmylibmeans look forlibmylib.aorlibmylib.so
The exact choice depends on platform defaults, linker options, and what files are available.
Step by Step Execution
Consider this program:
/* main.c */
void greet(void);
int main(void) {
greet();
return 0;
}
And this library code:
/* greet.c */
#include <stdio.h>
void greet(void) {
printf("Hello from the library!\n");
}
Static linking walkthrough
gcc -c greet.c -o greet.o
ar rcs libgreet.a greet.o
gcc main.c -L. -lgreet -o app
Step by step
-
greet.cis compiled intogreet.o- This contains machine code for
greet().
- This contains machine code for
-
greet.ois placed intolibgreet.a- The
.afile is just an archive of object files.
- The
-
main.cis compiled and linkedmain()callsgreet().- The linker sees that
greet()is missing. - It searches the libraries provided by
-lgreet. - It finds
greet()insidelibgreet.a. - It copies the required code into
app.
-
You run
./app- The executable already contains the library code it needs.
- The
.afile is usually not needed now.
Shared linking walkthrough
gcc -fPIC -c greet.c -o greet.o
gcc -shared -o libgreet.so greet.o
gcc main.c -L. -lgreet -o app
Step by step
-
greet.cis compiled into position-independent object code. -
libgreet.sois built from that object code. -
main.cis compiled and linked.- The linker records that
appneedslibgreet.so. - It does not copy all the library code into the executable the same way a static link does.
- The linker records that
-
You run
./app- The loader checks which shared libraries are required.
- It searches known library paths.
- If it finds
libgreet.so, the program starts. - If not, startup fails with a shared library error.
Key build-vs-run idea
- Static library: resolve now, include code now
- Shared library: record dependency now, load code later
Real World Use Cases
Static libraries (.a) are often used when
- building small self-contained command-line tools
- deploying software to systems where installing dependencies is difficult
- embedding a specific library version into a binary
- reducing runtime dependency problems
- building for restricted or embedded environments
Example:
- A maintenance utility is shipped as one executable with no external library setup.
Shared libraries (.so) are often used when
- many programs use the same library
- disk space or memory sharing matters
- libraries need to be updated independently of applications
- plugin-style systems load code dynamically
- operating system packages manage common dependencies
Example:
- Multiple applications use the same SSL or database client library.
In porting work
When moving a C program to another Unix platform such as AIX, you often need to check:
- whether the target platform prefers static or shared linking for a library
- where the runtime loader searches for libraries
- whether a
.afile contains static objects, shared objects, or both - whether the build scripts assume Linux-style
.sobehavior
Real Codebase Usage
In real projects, developers usually do not think only in terms of file extensions. They think in terms of linking strategy, deployment, and runtime loading.
Common patterns
Using system libraries
A project may link with standard libraries like:
gcc app.c -lm -lpthread -o app
The exact files chosen by the linker depend on what is installed and how the system is configured.
Using build tools
In Makefiles, CMake, or Autotools, developers usually specify library names and search paths rather than hardcoding file names.
Example Makefile style:
CFLAGS += -I./include
LDFLAGS += -L./lib
LDLIBS += -lgreet
This keeps the build more portable.
Guarding against runtime failures
When using shared libraries, teams often:
- install libraries in standard system paths
- configure runtime search paths
- package the correct library versions
- verify dependencies in CI or deployment scripts
Versioned shared libraries
Real systems often use versioned shared libraries such as:
libssl.so
libssl.so.3
This helps compatibility and upgrades.
Plugin loading
Some applications do not link a shared library at build time. Instead, they load it at runtime with platform APIs. This is common for:
- database drivers
- image codecs
- optional features
- modular server extensions
That is still part of the shared-library idea: code lives outside the executable and is loaded when needed.
Common Mistakes
1. Thinking .a and .so are source files
They are not source code files. They are already compiled binary libraries.
.a= archive of object files.so= shared library object
2. Confusing compile time with link time
Beginners often think #include is enough to use a library.
#include only gives declarations. You still need to link the compiled library.
Broken example:
#include "greet.h"
int main(void) {
greet();
return 0;
}
If you compile without linking the library:
gcc main.c -o app
You may get an error like:
undefined reference to `greet`
3. Linking succeeds, but the program fails at runtime
This often happens with shared libraries.
You linked successfully, but the loader cannot find the .so file later.
Typical cause:
- library path not configured
- library not installed on target machine
- wrong library version
4. Assuming .a always means static and .so always means shared on every Unix system
This is a useful general rule, but AIX can package shared-library content differently. Always check the platform's conventions.
5. Forgetting position-independent code for shared libraries
On many systems, shared libraries should be built with -fPIC.
Broken example:
gcc -c greet.c -o greet.o
gcc -shared -o libgreet.so greet.o
Better:
gcc -fPIC -c greet.c -o greet.o
gcc -shared -o libgreet.so greet.o
6. Hardcoding exact file names in portable builds
Less portable:
gcc main.c ./lib/libgreet.so -o app
More portable:
gcc main.c -L./lib -lgreet -o app
This lets the linker choose the appropriate file for the platform.
Comparisons
| Feature | .a static library | .so shared library |
|---|---|---|
| Full name | Archive library | Shared object |
| Linked when | Build time | Build time and loaded at runtime |
| Code copied into executable | Usually yes | Usually no |
| Executable size | Larger | Smaller |
| Runtime dependency | Usually no | Usually yes |
| Memory sharing across processes | No | Yes |
| Easier deployment | Often yes | Sometimes harder |
| Easier independent library updates | No | Yes |
Static linking vs dynamic linking
- Static linking means the program gets the needed code during the build.
- Dynamic linking means the program keeps a reference to external code that is loaded later.
.o vs .a vs .so
| File type | Meaning |
|---|---|
.o | One compiled object file |
.a | Archive containing one or more object files |
.so | Shared library loaded dynamically |
Which should you choose?
- Choose static libraries when you want a more self-contained executable.
- Choose shared libraries when many programs use the same code or when you want easier library updates.
- On AIX, confirm how the platform expects shared libraries to be packaged before deciding.
Cheat Sheet
Quick reference
.o= object file.a= static library archive.so= shared library
Build a static library
gcc -c file.c -o file.o
ar rcs libname.a file.o
gcc main.c -L. -lname -o app
Build a shared library
gcc -fPIC -c file.c -o file.o
gcc -shared -o libname.so file.o
gcc main.c -L. -lname -o app
Linker naming rule
-lname
usually means:
- search for
libname.a - or search for
libname.so
Key idea
- static library: code is copied into executable
- shared library: code stays external and must be found at runtime
Common flags
-ccompile only-Ldiradd library search directory-lnamelink library namedlibname-sharedcreate shared library-fPICcreate position-independent code
Watch out for
undefined referencemeans the linker could not find function definitions- runtime loader errors usually mean a shared library was not found
- AIX may use
.afiles in ways that differ from Linux
FAQ
What is the difference between .a and .so files?
A .a file is usually a static library archive, while a .so file is usually a shared library. Static libraries are copied into the executable during linking; shared libraries are loaded separately at runtime.
Do I need a .a file at runtime?
Usually no. If your program was statically linked with code from a .a file, that code is already inside the executable.
Do I need a .so file at runtime?
Usually yes. The program depends on the shared library being available when it starts, unless it was linked differently or bundled specially.
Why does my program build but not run?
A common reason is that the program links against a shared library successfully, but the system cannot find that library at runtime.
What does -lfoo mean in a C build command?
It tells the linker to search for a library named libfoo, typically something like libfoo.a or libfoo.so.
Are .a files always static on AIX?
Not always in the way Linux developers expect. AIX can package shared-library-related content inside archive files too, so you should check AIX-specific library conventions.
Which is better: static or shared libraries?
Neither is always better. Static libraries help create self-contained binaries. Shared libraries reduce duplication and support easier updates.
Mini Project
Description
Create a tiny C project that demonstrates both a static library and a shared library using the same function. This project helps you see the difference between build-time linking and runtime loading in a concrete, hands-on way.
Goal
Build one C program that can be linked against a custom library, then observe how behavior differs when using a static library versus a shared library.
Requirements
- Create a library function that prints a message.
- Build that function into both a
.alibrary and a.solibrary. - Write a main program that calls the library function.
- Link and run the program using the library.
- Confirm what file is needed at runtime in each case.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.