1.1 History and Features of C
C was designed by Dennis Ritchie at Bell Labs (1972) to rewrite the UNIX operating system. It is a general-purpose, procedural, middle-level language — low-level enough to manipulate memory directly (via pointers), yet high-level enough to write portable, structured programs.
Before C, operating systems were written in assembly language, which was bound to specific hardware architectures. Dennis Ritchie evolved C from Ken Thompson's B language (which derived from BCPL), introducing data typing and compiler optimizations that allowed UNIX to be re-implemented portably across different computer systems with near-assembly execution speed.
Key features: simplicity and small core keyword set, direct hardware/memory access through pointers, structured programming (functions, blocks), portability across platforms, rich standard library, and being the foundation on which C++, Java, C#, and many OS kernels are built.
- Fast execution — compiles to native machine code: Unlike interpreted or bytecode languages, C compiles directly to native CPU machine instructions without VM overhead, yielding near-zero runtime abstraction cost.
- Modular via functions and separate compilation: Large software systems can be partitioned into independent translation units (.c and .h files), compiled separately, and linked into cohesive binaries.
- Extensible through header files and libraries: Standard libraries (like
<stdio.h>and<stdlib.h>) and third-party libraries seamlessly integrate via function declarations and preprocessor directives. - Statically typed with manual memory management: Variable types are determined at compile time, eliminating type-check overhead at runtime, while dynamic heap allocation gives developers exact control over memory footprint.
1.2 Structure of a C Program
Every C program follows a fixed skeleton: preprocessor directives at the top, optional global declarations, and one mandatory main() function where execution begins. Curly braces {} define blocks; every statement ends with a semicolon.
A typical C translation unit is structured into clearly defined sections:
- Documentation & Preprocessor Section: Header file inclusions (
#include <stdio.h>) and macro definitions that tell the preprocessor to load standard prototypes and replace constants before compilation begins. - Global & Type Declarations: Variables, custom structures, and function prototypes declared outside any function body so they have file-level or program-wide scope.
- Main Function (Entry Point): The mandatory
int main()function where CPU execution starts. Returning0signifies successful termination to the host operating system. - Function Definitions: Implementation bodies for user-defined helper functions referenced earlier by prototypes.
#include <stdio.h> // 1. Preprocessor directive
int globalVar = 10; // 2. Global declaration
int add(int a, int b); // 3. Function prototype
int main() { // 4. Entry point
int local = 5; // 5. Local variable
printf("%d", add(local, globalVar));
return 0; // 6. Exit status
}
int add(int a, int b) { // 7. Function definition
return a + b;
}
1.3 Compilation and Execution Process
A .c file is not directly executable — it passes through four stages. The Preprocessor expands directives such as #include and #define. The Compiler translates the expanded C code into assembly. The Assembler converts assembly into machine-readable object code (.o/.obj). Finally the Linker merges your object code with library code (like printf's implementation) into one executable.
Understanding each of the four distinct build pipeline phases:
- 1. Preprocessing (
gcc -E): Strips comments, expands macros (#define), and textually includes the full headers specified by#includedirectives. Produces a pure expanded C translation unit. - 2. Compilation (
gcc -S): Parses syntax, performs semantic type checks, optimizes control flow, and translates high-level C code into assembly instructions (e.g., x86_64 or ARM assembly in a.sfile). - 3. Assembly (
gcc -c): Converts assembly mnemonics directly into binary machine instructions, creating a relocatable object file (.oor.obj) containing raw opcodes and symbol tables. - 4. Linking (
gcc -o): Resolves external function references across multiple object files and links standard runtime libraries (such as libc'sprintfimplementation) into a standalone runnable binary (a.outon Linux/macOS,.exeon Windows).