What is an Object Library?
When you write a large C program, you split the code across multiple .c source files to keep things manageable. The compiler turns each .c file into an object file (.o) — a chunk of machine code that is not yet a complete program. The linker then joins all the object files together into a final executable.
The problem: if you have utility functions used by many programs (e.g., a math helper, a string formatter), you’d have to compile those same source files again and again for each program, and you’d have to list every .o file on the link command line. This is messy and slow.
The solution: group your object files into an object library. There are two types:
- Static library (.a) — code is physically copied into each executable at link time.
- Shared library (.so) — code lives in a separate file and is loaded at run time, shared by all programs that need it.
The Compilation Pipeline
Here is how source code becomes a running program:
gcc vs ld — What is the Difference?
Many beginners think gcc is only a compiler. In reality, gcc is a driver program — it orchestrates the entire compilation process: preprocessing, compiling, assembling, and linking.
The actual linker is called ld. When you run gcc -o myprog prog.o mod1.o, gcc internally calls ld with the right flags and adds the standard C library automatically. If you called ld directly, you would have to manually specify the C runtime startup files (crt1.o, crti.o), the C library (-lc), and other details.
Rule: Always use gcc (not ld directly) to link your programs on Linux.
The -g Debug Flag
-gThe
-g flag tells gcc to include debugging information (symbol names, line numbers, variable names) in the object file. This information is used by debuggers like gdb and by tools like valgrind. Modern disks and RAM are cheap — there is almost no reason NOT to use -g during development. For final production builds, you may strip it, but keep it during development.-fomit-frame-pointer on x86-32On 32-bit x86, this option removes the frame pointer from stack frames, which makes debugging impossible. On x86-64, it is safe (enabled by default) since x86-64 has enough registers. Similarly, don’t use
strip(1) to strip debugging symbols from executables you’re still testing.Coding Example 1 — Basic Compilation Without a Library
This is the starting point. We have three source files and compile + link them manually.
/* math_utils.h - header for our utility functions */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
int multiply(int a, int b);
#endif
/* math_utils.c - utility functions */
#include "math_utils.h"
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
/* string_utils.c - more utility functions */
#include <string.h>
#include <stdio.h>
void print_greeting(const char *name) {
printf("Hello, %s!\n", name);
}
int count_chars(const char *str) {
return (int)strlen(str);
}
/* main.c - main program that uses both utilities */
#include <stdio.h>
#include "math_utils.h"
/* external declaration for string_utils */
void print_greeting(const char *name);
int count_chars(const char *str);
int main(void) {
int result = add(10, 20);
printf("10 + 20 = %d\n", result);
result = multiply(4, 5);
printf("4 * 5 = %d\n", result);
print_greeting("EmbeddedPathashala");
printf("Name length = %d\n", count_chars("EmbeddedPathashala"));
return 0;
}
## Build steps WITHOUT any library
## Step 1: Compile each .c file into a .o object file (no linking yet)
$ gcc -g -c main.c math_utils.c string_utils.c
## This produces: main.o math_utils.o string_utils.o
## Check the files:
$ ls -lh *.o
## Step 2: Link all object files into the final executable
$ gcc -g -o myprog main.o math_utils.o string_utils.o
## Run the program
$ ./myprog
# Output:
# 10 + 20 = 30
# 4 * 5 = 20
# Hello, EmbeddedPathashala!
# Name length = 18
## The -g flag embeds debug info. Use gdb to debug:
$ gdb ./myprog
(gdb) break main
(gdb) run
(gdb) next
(gdb) print result
Coding Example 2 — Understanding Object File Contents
You can inspect what’s inside an object file using tools like nm, objdump, and readelf. This helps you understand what symbols (functions, variables) an object file defines or references.
/* sensor.c - simulates reading a hardware sensor */
#include <stdio.h>
/* This function is DEFINED in this file (exported symbol) */
float read_temperature(void) {
/* In real embedded code, you'd read a register here */
return 36.5f;
}
/* This function calls printf — an EXTERNAL symbol from libc */
void display_sensor(void) {
printf("Temperature: %.1f°C\n", read_temperature());
}
## Compile to object file only
$ gcc -g -c sensor.c -o sensor.o
## Use nm to list all symbols in the object file
$ nm sensor.o
## Typical output:
## 0000000000000000 T read_temperature (T = defined in Text/code section)
## 0000000000000020 T display_sensor (T = defined here)
## U printf (U = Undefined, needs to be linked from libc)
## Use objdump to see the disassembly
$ objdump -d sensor.o
## Use objdump to see all section headers
$ objdump -h sensor.o
## Use readelf to see all symbols with full details
$ readelf -s sensor.o
## Use file command to confirm it's an object file (not executable)
$ file sensor.o
## Output: sensor.o: ELF 64-bit LSB relocatable, x86-64, ...
## Note: "relocatable" means it's an object file, not an executable
## Compare with a fully linked executable
$ gcc -g -o sensor_prog sensor.o -lc
$ file sensor_prog
## Output: sensor_prog: ELF 64-bit LSB pie executable, x86-64, ...
Interview Questions — Section 41.1
-c flag with gcc? What does it do?-c flag tells gcc to compile only, do not link. Without it, gcc would try to compile and immediately link, which would fail if you’re building a multi-file project where each file is compiled separately. With -c, you get individual .o files which can later be linked together in a separate step.ld. However, gcc acts as a driver and invokes ld internally with all the correct options, including adding the C runtime startup code and the standard C library. You should always use gcc for linking, not call ld directly, because gcc ensures all the required files are included automatically.Shared libraries (.so) — the code is NOT copied into the executable. Instead, the program records the library name, and the dynamic linker loads the library into memory at run time. Multiple programs can share the same copy in memory.
-g flag during development?-g flag embeds debugging symbols — function names, variable names, source file names, and line numbers — into the object file. Without it, tools like gdb and valgrind cannot show you meaningful information (they’ll show raw addresses instead of function names). Since disk and RAM are cheap today, there’s no good reason to omit -g during development. You can always strip the binary later for production.