C Programming — Getting Started
C is a general-purpose, procedural programming language developed between 1969 and 1973 by Dennis Ritchie at Bell Labs. Originally designed for rewriting the UNIX operating system, C has become one of the most influential and widely used programming languages in history. It provides low-level access to memory, simple machine-level constructs, and a clean syntax that maps efficiently to machine instructions.
C is the foundation of modern computing. Operating systems (Linux, Windows, macOS kernels), databases (PostgreSQL, MySQL), compilers (GCC, Clang), interpreters (CPython, Ruby MRI), embedded systems, game engines, and countless other critical systems are written in C. Learning C gives you a deep understanding of how computers actually work — memory, pointers, data structures, and the relationship between software and hardware.
Unlike higher-level languages, C does not have garbage collection, classes, or runtime safety nets. You manage memory directly, you choose data structures explicitly, and you deal with the consequences of your choices. This is what makes C both powerful and demanding — it gives you full control with zero hand-holding.
C evolved from earlier languages in a clear lineage. Understanding this history explains why C has the features it does and why certain design decisions were made.
| Year | Milestone | Significance |
|---|---|---|
| 1972 | C created by Dennis Ritchie | Developed at Bell Labs for rewriting UNIX |
| 1978 | The C Programming Language (K&R) | First formal description, "K&R C" |
| 1989 | ANSI C (C89/C90) | First standardized version |
| 1999 | C99 | Added inline functions, long long, variable-length arrays |
| 2011 | C11 | Added threads, atomics, generic selections, static_assert |
| 2018 | C17/C18 | Minor bug fixes to C11, no major new features |
| 2023 | C23 | Added nullptr, typeof, constexpr, improved Unicode support |
note
To write and compile C programs, you need a compiler. The most common compilers are GCC (GNU Compiler Collection) and Clang. Both are free, open-source, and available on every major platform.
macOS
| 1 | # Install Xcode Command Line Tools (includes clang) |
| 2 | xcode-select --install |
| 3 | |
| 4 | # Or install GCC via Homebrew |
| 5 | brew install gcc |
| 6 | |
| 7 | # Verify installation |
| 8 | gcc --version |
| 9 | clang --version |
Linux (Ubuntu/Debian)
| 1 | # Install GCC |
| 2 | sudo apt update |
| 3 | sudo apt install build-essential |
| 4 | |
| 5 | # Verify |
| 6 | gcc --version |
| 7 | |
| 8 | # Install GDB debugger |
| 9 | sudo apt install gdb |
Windows
| 1 | # Option 1: Install MinGW-w64 (native Windows GCC) |
| 2 | # Download from https://www.mingw-w64.org/ |
| 3 | |
| 4 | # Option 2: Install WSL2 and use Linux toolchain |
| 5 | wsl --install |
| 6 | # Then follow Linux instructions inside WSL |
| 7 | |
| 8 | # Option 3: Use MSYS2 |
| 9 | # Download from https://www.msys2.org/ |
| 10 | pacman -S mingw-w64-x86_64-gcc |
Editor Setup
Any text editor works for C development. For a good experience, use VS Code with the C/C++ extension (IntelliSense, debugging), or Vim/Neovim with clangd for a terminal-based workflow.
Every C program starts executing from the main() function. This is the entry point — the operating system calls main()when your program starts. Let's write, compile, and run the classic "Hello, World!" program.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | printf("Hello, World!\n"); |
| 5 | return 0; |
| 6 | } |
Line-by-Line Breakdown
Compile and Run
| 1 | # Compile: source file → executable |
| 2 | gcc hello.c -o hello |
| 3 | |
| 4 | # Run the executable |
| 5 | ./hello |
| 6 | # Output: Hello, World! |
| 7 | |
| 8 | # Compile with warnings enabled (always do this) |
| 9 | gcc -Wall -Wextra -pedantic -std=c17 hello.c -o hello |
| 10 | |
| 11 | # Compile with debugging symbols |
| 12 | gcc -g -Wall -Wextra hello.c -o hello |
warning
C is a compiled language — your source code is translated into machine code by a compiler before execution. This is different from interpreted languages like Python or JavaScript. Understanding the compilation process helps you write better code and debug more effectively.
| 1 | # See each stage independently |
| 2 | gcc -E hello.c -o hello.i # Preprocess only |
| 3 | gcc -S hello.c -o hello.s # Compile to assembly |
| 4 | gcc -c hello.c -o hello.o # Compile to object file |
| 5 | gcc hello.o -o hello # Link into executable |
| 6 | |
| 7 | # Or all in one step |
| 8 | gcc hello.c -o hello |
C syntax is clean and minimal. Programs are made up of functions, statements, and declarations. Semicolons terminate statements, curly braces define blocks, and every variable must be declared before use.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Function declaration (prototype) |
| 4 | int add(int a, int b); |
| 5 | |
| 6 | int main(void) { |
| 7 | // Variable declaration and initialization |
| 8 | int x = 10; |
| 9 | int y = 20; |
| 10 | int sum; |
| 11 | |
| 12 | // Function call |
| 13 | sum = add(x, y); |
| 14 | |
| 15 | // printf with format specifiers |
| 16 | printf("%d + %d = %d\n", x, y, sum); |
| 17 | |
| 18 | // Comments: single-line |
| 19 | /* Multi-line |
| 20 | comment */ |
| 21 | |
| 22 | return 0; |
| 23 | } |
| 24 | |
| 25 | // Function definition |
| 26 | int add(int a, int b) { |
| 27 | return a + b; |
| 28 | } |
Key Syntax Rules
Variables in C are named storage locations with a specific type. Unlike dynamically-typed languages, C requires you to declare the type of every variable at compile time. The compiler uses this information to allocate the right amount of memory and perform type checking.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | // Integer types |
| 5 | int age = 25; |
| 6 | long population = 7900000000LL; |
| 7 | unsigned int count = 42; |
| 8 | |
| 9 | // Floating-point types |
| 10 | float pi = 3.14f; |
| 11 | double precise = 3.141592653589793; |
| 12 | |
| 13 | // Character type |
| 14 | char grade = 'A'; |
| 15 | |
| 16 | // Print sizes (in bytes) |
| 17 | printf("int: %zu bytes\n", sizeof(int)); |
| 18 | printf("long: %zu bytes\n", sizeof(long)); |
| 19 | printf("float: %zu bytes\n", sizeof(float)); |
| 20 | printf("double: %zu bytes\n", sizeof(double)); |
| 21 | printf("char: %zu byte\n", sizeof(char)); |
| 22 | |
| 23 | return 0; |
| 24 | } |
info
printf() and scanf() use format specifiers to read and write different data types. Using the wrong format specifier is undefined behavior — a common source of bugs.
| Specifier | Type | Example |
|---|---|---|
| %d | int | printf("%d", 42) |
| %ld | long | printf("%ld", 100000L) |
| %u | unsigned int | printf("%u", 42u) |
| %f | double (printf) / float (scanf) | printf("%f", 3.14) |
| %lf | double (scanf) | scanf("%lf", &val) |
| %c | char | printf("%c", 'A') |
| %s | char array (string) | printf("%s", "hi") |
| %p | pointer (void*) | printf("%p", ptr) |
| %zu | size_t | printf("%zu", sizeof(int)) |
| %x | hexadecimal | printf("%x", 255) |
| %o | octal | printf("%o", 255) |
C provides standard control flow constructs: if/else for branching, for/while/do-while for loops, and switch for multi-way branching.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | int score = 85; |
| 5 | |
| 6 | // if / else if / else |
| 7 | if (score >= 90) { |
| 8 | printf("Grade: A\n"); |
| 9 | } else if (score >= 80) { |
| 10 | printf("Grade: B\n"); |
| 11 | } else { |
| 12 | printf("Grade: C\n"); |
| 13 | } |
| 14 | |
| 15 | // for loop |
| 16 | for (int i = 0; i < 5; i++) { |
| 17 | printf("Count: %d\n", i); |
| 18 | } |
| 19 | |
| 20 | // while loop |
| 21 | int n = 3; |
| 22 | while (n > 0) { |
| 23 | printf("Down: %d\n", n); |
| 24 | n--; |
| 25 | } |
| 26 | |
| 27 | // switch |
| 28 | char op = '+'; |
| 29 | int a = 10, b = 5; |
| 30 | switch (op) { |
| 31 | case '+': printf("Result: %d\n", a + b); break; |
| 32 | case '-': printf("Result: %d\n", a - b); break; |
| 33 | case '*': printf("Result: %d\n", a * b); break; |
| 34 | default: printf("Unknown op\n"); break; |
| 35 | } |
| 36 | |
| 37 | return 0; |
| 38 | } |
Functions are the building blocks of C programs. A function is a reusable block of code that performs a specific task. C functions can accept parameters, return values, and call themselves (recursion).
| 1 | #include <stdio.h> |
| 2 | #include <math.h> |
| 3 | |
| 4 | // Function: returns the nth Fibonacci number |
| 5 | // Uses recursion — a function calling itself |
| 6 | int fibonacci(int n) { |
| 7 | if (n <= 1) return n; |
| 8 | return fibonacci(n - 1) + fibonacci(n - 2); |
| 9 | } |
| 10 | |
| 11 | // Function: checks if a number is prime |
| 12 | int is_prime(int n) { |
| 13 | if (n < 2) return 0; |
| 14 | for (int i = 2; i * i <= n; i++) { |
| 15 | if (n % i == 0) return 0; |
| 16 | } |
| 17 | return 1; |
| 18 | } |
| 19 | |
| 20 | // Function: swap two integers using pointers |
| 21 | void swap(int *a, int *b) { |
| 22 | int temp = *a; |
| 23 | *a = *b; |
| 24 | *b = temp; |
| 25 | } |
| 26 | |
| 27 | int main(void) { |
| 28 | // Call functions |
| 29 | printf("fibonacci(10) = %d\n", fibonacci(10)); |
| 30 | |
| 31 | for (int i = 1; i <= 20; i++) { |
| 32 | if (is_prime(i)) { |
| 33 | printf("%d is prime\n", i); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | int x = 5, y = 10; |
| 38 | swap(&x, &y); // Pass addresses |
| 39 | printf("x=%d, y=%d\n", x, y); // x=10, y=5 |
| 40 | |
| 41 | return 0; |
| 42 | } |
pro tip
Pointers are the most powerful and feared feature of C. A pointer is a variable that stores the memory address of another variable. Pointers enable dynamic memory allocation, efficient array traversal, and pass-by-reference semantics. They are the reason C can be both portable and low-level.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | int x = 42; |
| 5 | int *p = &x; // p stores the address of x |
| 6 | |
| 7 | printf("Value of x: %d\n", x); |
| 8 | printf("Address of x: %p\n", (void *)&x); |
| 9 | printf("Value of p: %p\n", (void *)p); |
| 10 | printf("Dereferenced p: %d\n", *p); // Follow the pointer |
| 11 | |
| 12 | *p = 100; // Modify x through the pointer |
| 13 | printf("New value of x: %d\n", x); // 100 |
| 14 | |
| 15 | return 0; |
| 16 | } |
This documentation covers C from absolute beginner to advanced systems programming. Each section builds on the previous one. Work through them in order for the best learning experience.
| Section | Level | Topics |
|---|---|---|
| Variables & Data Types | Beginner | Primitive types, sizeof, constants, type qualifiers |
| Operators | Beginner | Arithmetic, logical, bitwise, precedence |
| Control Flow | Beginner | if/else, switch, loops, break, continue, goto |
| Functions | Beginner | Declaration, scope, recursion, pass-by-value |
| Arrays | Intermediate | 1D, 2D, multi-dimensional, array operations |
| Strings | Intermediate | Char arrays, string functions, I/O, buffer safety |
| Pointers | Intermediate | Address-of, dereference, arithmetic, arrays & pointers |
| Dynamic Memory | Intermediate | malloc, calloc, realloc, free, leaks |
| Structures & Unions | Intermediate | struct, union, enum, typedef, nested structures |
| File I/O | Intermediate | fopen, fread, fprintf, binary I/O, file positioning |
| Preprocessor | Intermediate | #include, #define, macros, conditional compilation |
| Bitwise Operations | Advanced | AND, OR, XOR, shifts, bit masks, practical uses |
| Multi-file Programs | Advanced | Headers, extern, static, Makefiles, separate compilation |
| Advanced Pointers | Advanced | Function pointers, void*, pointer-to-pointer, arrays |
| Data Structures | Advanced | Linked lists, stacks, queues, trees, hash tables |
| Memory Management | Advanced | Stack vs heap, layout, buffer overflow, debugging |
| C Standard Library | Advanced | stdio, stdlib, string, math, ctype, time |
| Type System | Advanced | Casting, conversions, volatile, restrict, alignment |
| Modern C (C11/C17/C23) | Advanced | Threads, atomics, VLAs, _Generic, constexpr |
| Debugging & Testing | Advanced | GDB, Valgrind, AddressSanitizer, unit tests |
| Best Practices | Expert | Coding standards, common mistakes, security, optimization |