C Fundamentals#

C is a small language — about 30 keywords, no classes, no exceptions, no garbage collector. It compiles to machine code, gives you direct hardware access, and trusts you to manage everything yourself. This is both its power and its danger.


Hello World#

#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}
gcc -o hello hello.c
./hello

Anatomy#

PartPurpose
#include <stdio.h>Preprocessor directive — includes standard I/O declarations
int main(void)Entry point, returns int (0 = success, nonzero = error)
printf()Formatted output to stdout
return 0Exit code to the OS

Compilation Pipeline#

source.c → [Preprocessor] → source.i → [Compiler] → source.s → [Assembler] → source.o → [Linker] → executable
StageToolWhat It Does
PreprocessingcppExpands #include, #define, conditional compilation
Compilationcc1Translates C to assembly
AssemblyasTranslates assembly to machine code (object file)
LinkingldCombines object files + libraries into executable
gcc -E hello.c > hello.i    # preprocess only
gcc -S hello.c               # compile to assembly
gcc -c hello.c               # compile to object file
gcc -o hello hello.c         # full pipeline

Compiler Flags You Should Always Use#

gcc -Wall -Wextra -Werror -std=c17 -pedantic -O2 -o myapp main.c
FlagPurpose
-WallEnable most warnings
-WextraAdditional warnings
-WerrorTreat warnings as errors
-std=c17Use C17 standard
-pedanticStrict standard compliance
-O2Optimisation level 2
-gInclude debug symbols
-fsanitize=addressAddressSanitizer (catches memory bugs)

Types#

Integer Types#

TypeMinimum SizeTypical SizeRange (typical)
char1 byte1 byte-128 to 127 or 0 to 255
short2 bytes2 bytes-32,768 to 32,767
int2 bytes4 bytes-2³¹ to 2³¹-1
long4 bytes4 or 8 bytesPlatform-dependent
long long8 bytes8 bytes-2⁶³ to 2⁶³-1

Use fixed-width types from <stdint.h> for portability:

#include <stdint.h>

int8_t   a;   // exactly 8 bits, signed
uint32_t b;   // exactly 32 bits, unsigned
int64_t  c;   // exactly 64 bits, signed
size_t   len; // unsigned, big enough for any object size

Floating-Point Types#

TypeSizePrecision
float4 bytes~7 decimal digits
double8 bytes~15 decimal digits
long double8-16 bytesPlatform-dependent

Boolean#

#include <stdbool.h>

bool is_valid = true;
bool is_empty = false;

Before C99: use int with 0/nonzero convention.


Variables and Scope#

int global = 42;              // file scope (avoid)

void function(void) {
    int local = 10;           // block scope
    static int persistent = 0; // persists across calls
    persistent++;

    {
        int inner = 5;        // nested block scope
    }
    // inner is not accessible here
}

Storage Classes#

SpecifierScopeLifetimeUse
auto (default)BlockBlockLocal variables
static (local)BlockProgramPersistent local state
static (global)FileProgramFile-private globals
externGlobalProgramDeclare variable defined elsewhere
registerBlockBlockHint: keep in CPU register (obsolete)

Printf Format Specifiers#

printf("%d\n", 42);           // int
printf("%u\n", 42u);          // unsigned int
printf("%ld\n", 42L);         // long
printf("%lld\n", 42LL);       // long long
printf("%zu\n", sizeof(int)); // size_t
printf("%f\n", 3.14);         // double
printf("%.2f\n", 3.14159);   // 2 decimal places
printf("%e\n", 3.14e10);     // scientific notation
printf("%c\n", 'A');          // char
printf("%s\n", "hello");     // string
printf("%p\n", (void*)ptr);  // pointer address
printf("%x\n", 255);         // hex (ff)
printf("%o\n", 255);         // octal (377)
printf("%%\n");              // literal %

Warning: Mismatched format specifiers are undefined behaviour. Use -Wformat (included in -Wall) to catch them.


Control Flow#

If/Else#

if (x > 0) {
    printf("positive\n");
} else if (x == 0) {
    printf("zero\n");
} else {
    printf("negative\n");
}

For Loop#

for (int i = 0; i < 10; i++) {
    printf("%d\n", i);
}

While / Do-While#

while (condition) {
    // body
}

do {
    // body (executes at least once)
} while (condition);

Switch#

switch (option) {
    case 1:
        printf("one\n");
        break;          // REQUIRED — fallthrough is default
    case 2:
    case 3:
        printf("two or three\n");
        break;
    default:
        printf("other\n");
}

Functions#

// Declaration (prototype)
int add(int a, int b);

// Definition
int add(int a, int b) {
    return a + b;
}

// void = no return value
void greet(const char* name) {
    printf("Hello, %s!\n", name);
}

Pass by Value#

C passes everything by value — including pointers (the pointer is copied, not the pointed-to data):

void increment(int x) {
    x++;  // modifies the local copy only
}

int n = 5;
increment(n);
printf("%d\n", n);  // still 5

// To modify the original: pass a pointer
void increment_ptr(int *x) {
    (*x)++;
}
increment_ptr(&n);
printf("%d\n", n);  // now 6

The const Qualifier#

const int MAX = 100;        // value cannot be changed
const char* name = "Alice"; // pointer to constant data (data is immutable)
char* const ptr = buffer;   // constant pointer (pointer itself is immutable)
const char* const both = "fixed"; // both immutable

Read declarations right to left: const char* = “pointer to const char” (the char is const). char* const = “const pointer to char” (the pointer is const).


Key Takeaways#

  • C is small and explicit. There is no hidden magic — what you write is what runs.
  • Always compile with -Wall -Wextra -Werror. Warnings in C are often real bugs.
  • Use <stdint.h> for fixed-width integer types. Never rely on int being a specific size.
  • Everything is pass-by-value. To modify an argument, pass a pointer to it.
  • Printf format mismatches are undefined behaviour. The compiler warns about them if you let it.
  • const correctness communicates intent and catches accidental modification.