What is C?
C is a general-purpose, procedural programming language developed in 1972. It provides low-level access to memory, maps efficiently to machine instructions, and is the foundation for operating systems, embedded systems, and countless other languages.
Low-Level Control
Direct memory access via pointers. Manual memory management. Minimal runtime overhead.
Portable
Compiles to native code on virtually every platform. Write once, compile anywhere.
Fast
Minimal abstraction between code and hardware. Predictable performance characteristics.
Foundational
Linux, Windows, databases, interpreters — most system software is written in C.
Basics
Hello World
#include <stdio.h> int main(void) { printf("Hello, world!\n"); return 0; }
Variables
// Declaration and initialization int x = 42; int y; // uninitialized (contains garbage) y = 10; // Constants const int MAX = 100; // runtime constant #define MAX_SIZE 100 // compile-time constant // Multiple declarations int a = 1, b = 2, c = 3; // Static (persists between calls, file scope) static int counter = 0; // Volatile (prevent optimization) volatile int hardware_reg;
Functions
// Function declaration (prototype) int add(int a, int b); // Function definition int add(int a, int b) { return a + b; } // No return value void greet(const char *name) { printf("Hello, %s!\n", name); } // No parameters int get_value(void) { return 42; } // Static function (file-local) static int helper(int x) { return x * 2; }
Control Flow
// if-else if (x > 0) { printf("positive\n"); } else if (x < 0) { printf("negative\n"); } else { printf("zero\n"); } // Ternary operator int abs_x = (x >= 0) ? x : -x; // switch switch (choice) { case 1: printf("one\n"); break; case 2: case 3: printf("two or three\n"); break; default: printf("other\n"); } // while while (x > 0) { x--; } // do-while (runs at least once) do { x++; } while (x < 10); // for for (int i = 0; i < 10; i++) { printf("%d\n", i); } // break and continue for (int i = 0; i < 100; i++) { if (i == 5) continue; // skip this iteration if (i == 50) break; // exit loop }
Operators
// Arithmetic a + b a - b a * b a / b a % b // Comparison a == b a != b a < b a > b a <= b a >= b // Logical !a a && b a || b // Bitwise ~a a & b a | b a ^ b a << n a >> n // Assignment a = b a += b a -= b a *= b a /= b a %= b a &= b a |= b a ^= b a <<= n a >>= n // Increment/Decrement ++a a++ --a a-- // Pointer/Address *ptr // dereference &var // address-of // sizeof sizeof(int) // size in bytes sizeof(arr) // array size in bytes
Types
Primitive Types
| Type | Size (typical) | Range |
|---|---|---|
char |
1 byte | -128 to 127 or 0 to 255 |
short |
2 bytes | -32,768 to 32,767 |
int |
4 bytes | -2.1B to 2.1B |
long |
4-8 bytes | Platform dependent |
long long |
8 bytes | -9.2E18 to 9.2E18 |
float |
4 bytes | ~6 decimal digits |
double |
8 bytes | ~15 decimal digits |
Fixed-Width Types (stdint.h)
#include <stdint.h> // Exact width int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t // Pointer-sized intptr_t uintptr_t // Size type size_t // unsigned, for sizes/indices ptrdiff_t // signed, for pointer differences
Type Modifiers
// Signed/Unsigned signed int x; // can be negative (default for int) unsigned int y; // non-negative only unsigned char byte; // 0 to 255 // Common shortcuts unsigned x; // same as unsigned int long y; // same as long int // Boolean (stdbool.h or C23) #include <stdbool.h> bool flag = true; bool done = false;
Type Casting
int x = 10; double d = (double)x; // explicit cast int y = (int)3.14; // truncates to 3 // Pointer casts void *generic = &x; int *int_ptr = (int *)generic;
Typedef
// Create type aliases typedef unsigned int uint; typedef unsigned char byte; // Function pointer typedef typedef int (*Comparator)(const void *, const void *); // Struct typedef typedef struct { int x, y; } Point;
Pointers
A pointer stores the memory address of a variable. Pointers enable dynamic memory, data structures, and efficient function arguments.
Pointer Basics
int x = 42; int *ptr = &x; // ptr holds address of x printf("%p\n", (void *)ptr); // print address printf("%d\n", *ptr); // dereference: prints 42 *ptr = 100; // modify x through pointer printf("%d\n", x); // prints 100 // Null pointer int *null_ptr = NULL; if (ptr != NULL) { // safe to dereference }
Pointer Arithmetic
int arr[] = {10, 20, 30, 40}; int *ptr = arr; // points to arr[0] ptr++; // now points to arr[1] ptr += 2; // now points to arr[3] // Pointer subtraction int *start = arr; int *end = &arr[3]; ptrdiff_t diff = end - start; // 3 (elements, not bytes) // Array indexing is pointer arithmetic arr[2] == *(arr + 2) // equivalent
Pointers and Functions
// Pass by reference void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int x = 1, y = 2; swap(&x, &y); // x=2, y=1 // Return pointer (be careful of scope!) int *create_array(size_t size) { int *arr = malloc(size * sizeof(int)); return arr; // caller must free }
Pointers to Pointers
int x = 5; int *ptr = &x; int **pptr = &ptr; // pointer to pointer printf("%d\n", **pptr); // prints 5 // Common use: 2D arrays, modifying pointers in functions void allocate(int **ptr, size_t size) { *ptr = malloc(size * sizeof(int)); }
Function Pointers
// Declaration int (*operation)(int, int); // Functions to point to int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } // Usage operation = add; int result = operation(3, 4); // 7 operation = mul; result = operation(3, 4); // 12 // As function parameter (callback) void apply(int *arr, size_t len, int (*fn)(int)) { for (size_t i = 0; i < len; i++) { arr[i] = fn(arr[i]); } }
Arrays & Strings
Arrays
// Fixed-size array int arr[5]; // uninitialized int arr[5] = {1, 2, 3, 4, 5}; int arr[] = {1, 2, 3}; // size inferred (3) int arr[5] = {0}; // all zeros int arr[5] = {1, 2}; // {1, 2, 0, 0, 0} // Access arr[0] = 10; int x = arr[2]; // Array size size_t len = sizeof(arr) / sizeof(arr[0]); // Arrays decay to pointers when passed to functions void process(int *arr, size_t len); void process(int arr[], size_t len); // equivalent
Multidimensional Arrays
// 2D array int matrix[3][4]; // 3 rows, 4 columns int matrix[2][3] = { {1, 2, 3}, {4, 5, 6} }; // Access matrix[0][1] = 10; // Pass to function (must specify all but first dimension) void process(int mat[][4], size_t rows);
Strings
// String = char array with null terminator '\0' char str[] = "hello"; // {'h','e','l','l','o','\0'} char str[10] = "hello"; // remaining filled with '\0' char *str = "hello"; // pointer to string literal (read-only!) // String length #include <string.h> size_t len = strlen(str); // doesn't count '\0' // Common string functions strcpy(dest, src); // copy string strncpy(dest, src, n); // copy up to n chars strcat(dest, src); // concatenate strncat(dest, src, n); // concat up to n chars strcmp(s1, s2); // compare (0 if equal) strncmp(s1, s2, n); // compare up to n chars strchr(str, c); // find char, return ptr strstr(haystack, needle); // find substring
String Formatting
char buffer[100]; // Format into buffer sprintf(buffer, "Name: %s, Age: %d", "Alice", 30); // Safe version (prevents overflow) snprintf(buffer, sizeof(buffer), "Value: %d", 42); // Parse from string int num; sscanf("42", "%d", &num); // String to number #include <stdlib.h> int n = atoi("42"); long l = strtol("42", NULL, 10); double d = strtod("3.14", NULL);
strncpy/snprintf instead of strcpy/sprintf to prevent buffer overflows.
Structs & Unions
Structs
// Define struct struct Point { int x; int y; }; // Create instance struct Point p1; p1.x = 10; p1.y = 20; // Initialize struct Point p2 = {10, 20}; struct Point p3 = {.x = 10, .y = 20}; // designated initializers // Typedef for cleaner syntax typedef struct { char name[50]; int age; } Person; Person alice = {"Alice", 30};
Struct Pointers
Person *ptr = &alice; // Access via pointer (*ptr).age = 31; // dereference then access ptr->age = 31; // arrow operator (preferred) // Allocate struct on heap Person *p = malloc(sizeof(Person)); p->age = 25; strcpy(p->name, "Bob"); free(p);
Nested Structs
typedef struct { int x, y; } Point; typedef struct { Point top_left; Point bottom_right; } Rectangle; Rectangle rect = {{0, 0}, {100, 50}}; rect.top_left.x = 10;
Unions
// All members share same memory location union Data { int i; float f; char str[20]; }; union Data data; data.i = 10; // data contains int data.f = 3.14; // now contains float (i is overwritten) // Size = size of largest member printf("%zu\n", sizeof(union Data)); // 20
Tagged Unions
// Common pattern: struct with enum tag + union typedef enum { INT, FLOAT, STRING } DataType; typedef struct { DataType type; union { int i; float f; char *s; } value; } Variant; Variant v = {.type = INT, .value.i = 42};
Enums
// Define enumeration enum Color { RED, GREEN, BLUE }; // 0, 1, 2 enum Status { OK = 0, ERROR = -1 }; // explicit values enum Color c = RED; // With typedef typedef enum { MONDAY = 1, TUESDAY, // 2 WEDNESDAY, // 3 // ... } Day;
Memory Management
C requires manual memory management. You must explicitly allocate and free heap memory.
Stack vs Heap
// Stack allocation (automatic, fast, limited size) int x = 42; // freed when scope ends int arr[100]; // fixed size at compile time // Heap allocation (manual, slower, large capacity) int *ptr = malloc(sizeof(int)); *ptr = 42; free(ptr); // must free when done!
Memory Functions (stdlib.h)
#include <stdlib.h> // malloc: allocate uninitialized memory int *arr = malloc(n * sizeof(int)); if (arr == NULL) { // allocation failed! } // calloc: allocate and zero-initialize int *arr = calloc(n, sizeof(int)); // realloc: resize allocation arr = realloc(arr, new_size * sizeof(int)); // free: release memory free(arr); arr = NULL; // good practice: avoid dangling pointer
Memory Operations (string.h)
#include <string.h> // Copy memory memcpy(dest, src, n); // copy n bytes (no overlap) memmove(dest, src, n); // copy n bytes (handles overlap) // Set memory memset(ptr, 0, n); // set n bytes to 0 memset(ptr, 0xFF, n); // set n bytes to 0xFF // Compare memory int result = memcmp(a, b, n); // 0 if equal
Common Pitfalls
// Memory leak: forgetting to free void leak() { int *p = malloc(100); // forgot free(p) - memory leaked! } // Use after free free(ptr); *ptr = 10; // UNDEFINED BEHAVIOR! // Double free free(ptr); free(ptr); // UNDEFINED BEHAVIOR! // Buffer overflow char buf[10]; strcpy(buf, "this is way too long"); // overflow!
-fsanitize=address) to detect memory errors.
Preprocessor
The C preprocessor runs before compilation, handling includes, macros, and conditional compilation.
Include Directives
#include <stdio.h> // system header (search system paths) #include "myheader.h" // local header (search current dir first)
Macros
// Object-like macros (constants) #define PI 3.14159 #define MAX_SIZE 1024 // Function-like macros #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define SQUARE(x) ((x) * (x)) // Multi-line macro #define SWAP(a, b) do { \ typeof(a) _tmp = (a); \ (a) = (b); \ (b) = _tmp; \ } while(0) // Undefine #undef MAX_SIZE // Stringification #define STR(x) #x printf("%s\n", STR(hello)); // prints "hello" // Token concatenation #define CONCAT(a, b) a##b int CONCAT(my, var) = 10; // creates: int myvar = 10;
Conditional Compilation
// Check if defined #ifdef DEBUG printf("Debug mode\n"); #endif #ifndef HEADER_H #define HEADER_H // header contents (include guard) #endif // if-elif-else #if VERSION >= 2 // version 2+ code #elif VERSION == 1 // version 1 code #else // fallback #endif // Check if defined (alternative) #if defined(A) && !defined(B) // ... #endif
Predefined Macros
__FILE__ // current filename __LINE__ // current line number __DATE__ // compilation date __TIME__ // compilation time __func__ // current function name (C99) // Common debug macro #define LOG(msg) printf("[%s:%d] %s\n", __FILE__, __LINE__, msg)
Pragma
// Compiler-specific directives #pragma once // include guard (non-standard but widely supported) #pragma pack(push, 1) // pack struct with no padding #pragma pack(pop)
Input/Output
Standard I/O
#include <stdio.h> // Output printf("Hello %s, you are %d years old\n", name, age); putchar('A'); // single character puts("Hello"); // string with newline // Input int n; scanf("%d", &n); // read int char buf[100]; fgets(buf, sizeof(buf), stdin); // safe string input int c = getchar(); // single character
Format Specifiers
| Specifier | Type | Example |
|---|---|---|
%d, %i |
signed int | -42 |
%u |
unsigned int | 42 |
%ld, %lu |
long | 123456789 |
%lld, %llu |
long long | large numbers |
%f |
float/double | 3.140000 |
%e |
scientific | 3.14e+00 |
%c |
char | A |
%s |
string | hello |
%p |
pointer | 0x7fff... |
%x, %X |
hex | ff, FF |
%zu |
size_t | 100 |
%% |
literal % | % |
Format Modifiers
printf("%10d", 42); // width 10, right-aligned printf("%-10d", 42); // width 10, left-aligned printf("%010d", 42); // zero-padded printf("%.2f", 3.14159); // 2 decimal places: 3.14 printf("%8.2f", 3.14); // width 8, 2 decimals
File I/O
FILE *fp; // Open file fp = fopen("file.txt", "r"); // read fp = fopen("file.txt", "w"); // write (creates/truncates) fp = fopen("file.txt", "a"); // append fp = fopen("file.bin", "rb"); // read binary if (fp == NULL) { perror("Error opening file"); return 1; } // Read/Write char buf[256]; while (fgets(buf, sizeof(buf), fp) != NULL) { printf("%s", buf); } fprintf(fp, "Value: %d\n", 42); // formatted write fputs("Hello\n", fp); // write string fputc('A', fp); // write char // Binary I/O fread(buffer, sizeof(char), count, fp); fwrite(buffer, sizeof(char), count, fp); // Seek fseek(fp, 0, SEEK_SET); // beginning fseek(fp, 0, SEEK_END); // end long pos = ftell(fp); // current position rewind(fp); // back to start // Close fclose(fp);
Error Handling
if (ferror(fp)) { perror("File error"); clearerr(fp); } if (feof(fp)) { printf("End of file reached\n"); }
Compilation
GCC/Clang Basics
# Compile and link gcc main.c -o program ./program # Compile multiple files gcc main.c utils.c -o program # Compile only (create object file) gcc -c main.c -o main.o gcc -c utils.c -o utils.o gcc main.o utils.o -o program # With warnings (always use these!) gcc -Wall -Wextra -Werror main.c -o program # Optimization levels gcc -O0 main.c -o program # no optimization (debug) gcc -O2 main.c -o program # recommended for release gcc -O3 main.c -o program # aggressive optimization gcc -Os main.c -o program # optimize for size # Debug symbols gcc -g main.c -o program # C standard gcc -std=c99 main.c -o program gcc -std=c11 main.c -o program gcc -std=c17 main.c -o program gcc -std=c23 main.c -o program # Include paths and libraries gcc -I./include main.c -o program gcc main.c -lm -o program # link math library gcc main.c -L./lib -lmylib -o program
Header Files
// myheader.h #ifndef MYHEADER_H #define MYHEADER_H // Declarations only void my_function(int x); extern int global_var; typedef struct { int x, y; } Point; #endif // myheader.c #include "myheader.h" int global_var = 0; void my_function(int x) { // implementation }
Simple Makefile
CC = gcc CFLAGS = -Wall -Wextra -std=c17 -g LDFLAGS = -lm SRCS = main.c utils.c OBJS = $(SRCS:.c=.o) TARGET = program $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) $(LDFLAGS) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(OBJS) $(TARGET) .PHONY: clean
Useful Compiler Flags
- -Wall
- Enable common warnings
- -Wextra
- Enable extra warnings
- -Werror
- Treat warnings as errors
- -pedantic
- Strict ISO C compliance
- -fsanitize=address
- Enable AddressSanitizer
- -fsanitize=undefined
- Catch undefined behavior