14.1 Object Files and Sections
The assembler does not normally produce a finished program. It produces an object file: a structured container holding machine-code bytes, data, names, and instructions for work that must be completed later. On Unix-like systems the common format is ELF; macOS uses Mach-O, and Windows uses PE/COFF. Their encodings differ, but they solve the same problem: preserve enough structure for the linker and loader without requiring every address to be known during compilation.
An object file is therefore more than a byte dump. Its header identifies the format, target architecture, byte order, word size, and table locations. A section table describes named regions. A symbol table describes definitions and unresolved references. Relocation records mark fields whose final value depends on layout or another object. Optional metadata supports debugging, exception unwinding, security policy, and build tooling.
Sections classify bytes by meaning
A compiler and assembler place material with different lifecycle and permission needs into different sections:
.text executable machine instructions
.rodata immutable constants, strings, jump tables
.data initialized writable globals
.bss zero-initialized writable globals
.symtab full linker/debug symbol table
.strtab strings referenced by table entries
.debug_* source lines, types, scopes, variable locations
.eh_frame stack-unwinding rulesSuppose a source file defines const char banner[] = "hi", int count = 3, int cache[1024], and a function tick. The instructions for tick enter .text; the string usually enters .rodata; count needs four initialized bytes in .data; and cache belongs in .bss. The object records that .bss needs 4096 bytes in memory but stores no 4096-byte zero block on disk. ELF represents this distinction with a NOBITS section type. File size and runtime memory size are consequently different quantities.
Alignment, padding, and section flags
Every section has an alignment requirement. If .text ends at file offset 0x113 and .rodata requires 16-byte alignment, the next legal start is 0x120; the gap is padding. Alignment lets instructions, vector constants, and atomic objects start at addresses the ISA and ABI can access efficiently or legally. Excessive alignment wastes file and address space, so a backend must carry the required alignment rather than applying the largest value everywhere.
Section flags describe intended use: allocatable at runtime, writable, executable, mergeable, or composed of fixed-size strings. These are inputs to linking, not necessarily the final memory map. The linker combines compatible input sections from many objects, garbage-collects unreachable sections when enabled, resolves layout, and groups output sections into segments. The loader maps segments, because operating systems work in page-granular address ranges with permissions; it does not usually map each compiler section independently.
For example, .text and read-only metadata may become an R-X and an R-- load segment, while .data and .bss become RW-. Keeping writable data out of executable pages enforces the W^X principle. RELRO can make selected tables read-only after the dynamic loader has applied relocations. The stack policy and thread-local storage may receive their own program-header descriptions.
Sections versus segments, and relocatable versus executable files
This distinction prevents a common confusion. Sections organize information for linking and tooling; segments organize the runtime image for loading. A relocatable .o file emphasizes sections and usually has no final virtual addresses. An executable or shared object has program headers that tell the loader which file ranges map to which virtual addresses, with what memory size, alignment, and permissions.
Not every section is loaded. .debug_*, .symtab, and many note sections can remain on disk for debuggers or be removed with strip without changing normal execution. By contrast, stripping the dynamic symbol table indiscriminately would break dynamic linking. A production emitter should test this boundary using inspection tools such as readelf, objdump, llvm-readobj, otool, or dumpbin: verify section types and flags, confirm .bss does not consume file payload, compare section and segment views, and ensure no loadable page is writable and executable unless the runtime genuinely requires it.