00:00

C Programming Tutorial

Last updated: 14 min read

C is a general-purpose, compiled programming language that gives you direct control over memory and hardware while still being portable across almost every kind of computer. More than fifty years after its creation, it remains the language of operating system kernels, embedded devices, databases, language runtimes and countless libraries that other languages build on.

This tutorial teaches C from the ground up: how a C program is built, variables and types, operators, control flow, functions, arrays and strings, pointers, dynamic memory, structures, file handling and multi-file programs. It also explains undefined behaviour, the tools that help you write safe C, and how C relates to Java. Every section includes small programs you can compile and run.

1. What Is the C Language?

C is a procedural language: a program is a set of functions that run step by step and work on data. It is compiled ahead of time into native machine code, so there is no virtual machine or interpreter between your program and the processor, and no garbage collector managing memory for you.

C is often described as a "high-level assembly language". It is high-level enough to write readable, portable code with variables, loops and functions, yet low-level enough that you can work with memory addresses, individual bits and hardware registers. That balance explains why it is still used wherever performance, small size or hardware access matters:

  • Operating systems: the Linux kernel and large parts of other operating systems are written in C.
  • Embedded systems: microcontrollers in cars, appliances, medical devices and IoT sensors.
  • Databases and servers: SQLite, PostgreSQL, Redis and the nginx web server.
  • Language runtimes: the reference Python interpreter (CPython) and many other interpreters.
  • Libraries: compression, cryptography, image and audio libraries used from almost every other language.

Learning C also teaches you how computers really work: how memory is laid out, what a pointer is and what happens when a function is called. That knowledge makes you a better programmer in Java, Python or any other language.

2. A Short History of C

C was created by Dennis Ritchie at Bell Labs in the early 1970s. It grew out of an earlier language called B, written by Ken Thompson, which was itself based on BCPL. In 1973 the Unix operating system was largely rewritten in C, which proved that an operating system could be written in a portable high-level language.

YearMilestone
1972C developed at Bell Labs by Dennis Ritchie.
1973The Unix kernel rewritten in C.
1978Kernighan and Ritchie published The C Programming Language, the informal standard known as "K&R C".
1989 / 1990The first official standard: ANSI C (C89), adopted by ISO as C90.
1999C99 added // comments, long long, stdint.h, bool through stdbool.h, variable-length arrays and declarations anywhere in a block.
2011C11 added a memory model with threads and atomics, _Generic, _Static_assert and anonymous structures.
2018C17 fixed defects in C11 without adding new features.
2024C23 (ISO/IEC 9899:2024) made bool, true and false keywords and added nullptr, typeof, constexpr objects, binary literals, digit separators and #embed.

C's syntax strongly influenced many later languages, including C++, Java, C#, JavaScript, Go and PHP. If you know Java, much of C's syntax for expressions, loops and conditions will look familiar.

3. Key Features of C

FeatureWhat it means for you
Small and simpleThe core language has only a few dozen keywords, so you can learn all of it.
FastCompiled to native code with no runtime overhead, so programs start instantly and run close to hardware speed.
PortableStandard C code compiles on everything from 8-bit microcontrollers to supercomputers.
PointersDirect access to memory addresses for efficient data structures and hardware access.
Manual memory managementYou decide exactly when memory is allocated and freed, with no garbage-collection pauses.
Rich standard libraryFunctions for input/output, strings, maths, memory and time in headers such as stdio.h and string.h.
InteroperabilityAlmost every language, including Java, Python and Rust, can call C functions, making C the common language of libraries.

4. Setting Up a C Compiler

To write C you need a text editor and a compiler. The two most popular free compilers are GCC and Clang.

  • Linux: install GCC with your package manager, for example sudo apt install build-essential on Debian or Ubuntu.
  • macOS: run xcode-select --install to get Clang (the gcc command on macOS also runs Clang).
  • Windows: install MSYS2 and its MinGW-w64 GCC package, use Visual Studio with the MSVC compiler, or use GCC inside WSL (Windows Subsystem for Linux).

Check the installation with gcc --version or clang --version. Any editor works; Visual Studio Code with the C/C++ extension, CLion and Vim are common choices.

5. Your First C Program

hello.c
#include <stdio.h>   /* gives us printf */

int main(void)
{
    printf("Hello, C!\n");
    return 0;           /* 0 tells the operating system the program succeeded */
}
terminal
# Compile with warnings enabled, then run
gcc -std=c17 -Wall -Wextra -o hello hello.c
./hello

Line by line:

  • #include <stdio.h> is a preprocessor directive. It pastes in the declarations from the standard input/output header so the compiler knows what printf is.
  • int main(void) defines the main function, where every C program starts. It returns an int status code and, here, takes no arguments.
  • printf prints formatted text. \n is the newline character.
  • return 0; ends main and reports success.

The -Wall -Wextra flags turn on many useful warnings. Treat warnings as errors to fix, not noise: in C they often point to real bugs.

6. How a C Program Is Built

Typing gcc hello.c actually runs four separate stages:

The four stages of building a C program Source file hello.c is preprocessed into hello.i, compiled into assembly hello.s, assembled into object file hello.o, and linked with libraries into the executable hello. hello.c hello.i hello.s hello.o hello 1. preprocess 2. compile 3. assemble 4. link source code expanded source assembly machine code executable + standard library and other .o files
Figure 1: Each stage can be run on its own, which helps when you want to see what the compiler is doing.
  1. Preprocessing (gcc -E): handles lines starting with #. It inserts header files, expands macros and removes code excluded by #if conditions.
  2. Compilation (gcc -S): checks the code for errors and translates it into assembly language for the target processor.
  3. Assembly (gcc -c): turns the assembly into an object file of machine code.
  4. Linking: combines object files with the libraries they use (such as the C standard library) into a final executable, resolving calls like printf to real addresses.

Understanding these stages makes error messages clearer: "undefined reference to ..." is a linker error (a function was declared but its code was not found), while "expected ';'" is a compiler error.

7. Variables, Data Types and Constants

Every variable in C has a fixed type, chosen when it is declared. The basic types are:

TypeTypical size on 64-bit systemsHolds
char1 byteA single character or a small integer.
short2 bytesSmall integers.
int4 bytesThe "natural" integer for the machine.
long8 bytes on Linux and macOS, 4 bytes on WindowsLarger integers.
long long8 bytesAt least 64-bit integers.
float / double4 / 8 bytesFloating-point numbers; prefer double.
bool1 bytetrue or false (a keyword in C23; include stdbool.h in older standards).

The C standard only guarantees minimum sizes; for example, an int must be at least 16 bits. When you need an exact size, such as for file formats or network protocols, use the fixed-width types from <stdint.h>: int8_t, uint16_t, int32_t, uint64_t and so on. Integer types can also be unsigned, which doubles the positive range but cannot hold negative values.

types.c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

#define MAX_USERS 100          /* preprocessor constant: plain text substitution */

int main(void)
{
    int age = 29;
    double price = 249.50;
    char grade = 'A';
    bool active = true;
    const double TAX_RATE = 0.18;   /* typed constant: cannot be changed */
    uint32_t packet_id = 4000000000u;

    printf("age=%d price=%.2f grade=%c active=%d\n", age, price, grade, active);
    printf("price with tax = %.2f\n", price * (1 + TAX_RATE));
    printf("packet_id=%u, int is %zu bytes, max users=%d\n",
           (unsigned)packet_id, sizeof(int), MAX_USERS);
    return 0;
}
Always initialise variables. A local variable that has not been given a value contains whatever happened to be in that memory before. Reading it is undefined behaviour and a classic source of bugs that appear only sometimes.

8. Operators and Input/Output

C's operators will feel familiar if you know Java:

  • Arithmetic: + - * / %. Dividing two integers discards the fraction: 7 / 2 is 3, while 7 / 2.0 is 3.5.
  • Comparison: == != < > <= >=, which give 1 for true and 0 for false.
  • Logical: && || !. In C, any non-zero value counts as true.
  • Bitwise: & | ^ ~ << >>, used heavily for flags and hardware registers.
  • Assignment and increment: = += -= *= /= ++ --.
  • Others: the conditional operator a ? b : c, sizeof, and the pointer operators & and *.

The standard I/O functions printf and scanf use format specifiers: %d for int, %ld for long, %u for unsigned, %f for double in printf, %c for characters, %s for strings, %zu for size_t and %p for pointers.

input.c
#include <stdio.h>

int main(void)
{
    int a, b;

    printf("Enter two whole numbers: ");
    if (scanf("%d %d", &a, &b) != 2) {      /* scanf returns how many values it read */
        fprintf(stderr, "Invalid input\n");
        return 1;
    }

    printf("sum=%d  difference=%d  product=%d\n", a + b, a - b, a * b);
    if (b != 0) {
        printf("quotient=%d  remainder=%d  exact=%.3f\n", a / b, a % b, (double)a / b);
    }
    return 0;
}

Note the & before a and b in scanf: it passes the address of each variable so that scanf can store values in them. You will see why in the section on pointers.

9. Control Flow

C provides the familiar decision and loop statements: if/else, switch, while, do/while and for, along with break and continue.

control.c
#include <stdio.h>

int main(void)
{
    /* for loop: print prime numbers below 30 */
    for (int n = 2; n < 30; n++) {
        int is_prime = 1;
        for (int d = 2; d * d <= n; d++) {
            if (n % d == 0) { is_prime = 0; break; }
        }
        if (is_prime) printf("%d ", n);
    }
    printf("\n");

    /* switch: choose a branch by an integer value */
    int day = 6;
    switch (day) {
        case 6:
        case 7:  printf("Weekend\n"); break;
        case 1:  printf("Start of the week\n"); break;
        default: printf("Weekday\n"); break;
    }

    /* do-while: the body always runs at least once */
    int countdown = 3;
    do {
        printf("%d... ", countdown);
    } while (--countdown > 0);
    printf("lift-off!\n");
    return 0;
}

In a switch, execution "falls through" to the next case unless you write break. That is useful for grouping cases, as with 6 and 7 above, but forgetting a break is a common bug.

10. Functions

Functions break a program into named, reusable pieces. A function must be declared before it is called, either by defining it earlier in the file or by writing a prototype.

functions.c
#include <stdio.h>

/* Prototypes: tell the compiler the function signatures */
double average(const int values[], int count);
unsigned long long factorial(unsigned n);

int main(void)
{
    int marks[] = {72, 88, 95, 61, 79};
    int count = sizeof marks / sizeof marks[0];

    printf("average = %.2f\n", average(marks, count));
    printf("10! = %llu\n", factorial(10));
    return 0;
}

double average(const int values[], int count)
{
    long sum = 0;
    for (int i = 0; i < count; i++) {
        sum += values[i];
    }
    return count > 0 ? (double)sum / count : 0.0;
}

/* Recursive function: calls itself with a smaller problem */
unsigned long long factorial(unsigned n)
{
    return n <= 1 ? 1 : n * factorial(n - 1);
}

C passes arguments by value: the function receives a copy, so changing a parameter does not affect the caller's variable. To let a function change the caller's data, you pass a pointer to it. Arrays are the special case: an array argument is passed as a pointer to its first element, so the function can change the original array, which is why average marks its parameter const to promise it will not.

A static local variable keeps its value between calls, and a static function is visible only inside its own source file, which is C's way of making functions private.

11. Arrays and Strings

An array is a fixed-size sequence of elements of the same type stored next to each other in memory. Indexes start at 0, and C does not check whether an index is within bounds.

A string in C is simply an array of char ending with the null character '\0'. The string "cat" therefore needs 4 bytes. Functions in <string.h> work with these null-terminated strings.

strings.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main(void)
{
    int scores[5] = {10, 20, 30};          /* remaining elements become 0 */
    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    printf("scores[4]=%d grid[1][2]=%d\n", scores[4], grid[1][2]);

    char city[32] = "Pune";
    printf("'%s' has %zu characters\n", city, strlen(city));

    /* snprintf never writes past the buffer size and always adds '\0' */
    char greeting[64];
    snprintf(greeting, sizeof greeting, "Welcome to %s!", city);
    puts(greeting);

    /* Read a whole line safely; fgets keeps the newline, so remove it */
    char name[40];
    printf("Your name: ");
    if (fgets(name, sizeof name, stdin) != NULL) {
        name[strcspn(name, "\n")] = '\0';
        for (size_t i = 0; name[i] != '\0'; i++) {
            name[i] = (char)toupper((unsigned char)name[i]);
        }
        printf("Hello, %s\n", name);
    }

    if (strcmp(city, "Pune") == 0) {       /* never compare strings with == */
        printf("Same city\n");
    }
    return 0;
}
Buffer overflows: functions such as gets (removed from the language in C11), strcpy and sprintf will happily write beyond the end of an array if the input is too long, corrupting memory and creating security holes. Prefer fgets, snprintf and functions that take the buffer size.

12. Pointers

A pointer is a variable that stores a memory address. Pointers are what make C powerful, and also what make it demanding. Two operators are central:

  • &x gives the address of x.
  • *p dereferences p: it means "the value at the address stored in p".
pointers.c
#include <stdio.h>

/* Swap works because it receives the ADDRESSES of the caller's variables */
void swap(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int main(void)
{
    int x = 5;
    int *p = &x;                 /* p holds the address of x */

    printf("x=%d, address=%p, via pointer=%d\n", x, (void *)p, *p);
    *p = 42;                     /* changes x through the pointer */
    printf("x is now %d\n", x);

    int m = 1, n = 2;
    swap(&m, &n);
    printf("m=%d n=%d\n", m, n);

    /* Arrays and pointers: arr decays to a pointer to arr[0] */
    int arr[] = {3, 6, 9, 12};
    int *q = arr;
    for (int i = 0; i < 4; i++) {
        printf("%d ", *(q + i));   /* pointer arithmetic: same as arr[i] */
    }
    printf("\n");

    int *nothing = NULL;          /* NULL means "points to nothing" */
    if (nothing == NULL) {
        printf("Always check a pointer before dereferencing it\n");
    }
    return 0;
}

Pointer arithmetic is scaled by the size of the type: if q points to an int, then q + 1 points to the next int, 4 bytes further on, not the next byte. This is exactly how array indexing works: arr[i] is defined as *(arr + i).

Pointers are also used for pointers to pointers (int **), for passing large structures efficiently, for building linked data structures and for function pointers, which let you pass behaviour as an argument, as the standard qsort function does with its comparison function.

13. Memory Layout and Dynamic Allocation

A running C program's memory is divided into regions:

Memory layout of a C program From high to low addresses: the stack grows downward, free space, the heap grows upward, then uninitialised data (BSS), initialised data, and the text segment containing code. Stack free space Heap BSS (zeroed data) Initialised data Text (program code) high low Local variables, parameters and return addresses; freed automatically on return malloc / calloc / realloc; you must call free when you are done Globals and statics without an initial value Globals and statics with an initial value Compiled machine instructions (read-only)
Figure 2: The stack and the heap grow towards each other; the code and global data have fixed sizes.

When the size of data is known only at run time, allocate it on the heap with functions from <stdlib.h>:

  • malloc(size) allocates size bytes of uninitialised memory.
  • calloc(count, size) allocates memory for count elements and sets it to zero.
  • realloc(ptr, new_size) resizes an earlier allocation, possibly moving it.
  • free(ptr) releases memory so it can be reused.
dynamic_array.c
#include <stdio.h>
#include <stdlib.h>

/* A growable array of ints, similar in spirit to Java's ArrayList */
typedef struct {
    int *data;
    size_t size;
    size_t capacity;
} IntList;

int list_push(IntList *list, int value)
{
    if (list->size == list->capacity) {
        size_t new_cap = list->capacity ? list->capacity * 2 : 4;
        int *bigger = realloc(list->data, new_cap * sizeof *bigger);
        if (bigger == NULL) {
            return -1;                  /* out of memory: old data is still valid */
        }
        list->data = bigger;
        list->capacity = new_cap;
    }
    list->data[list->size++] = value;
    return 0;
}

int main(void)
{
    IntList list = {NULL, 0, 0};

    for (int i = 1; i <= 10; i++) {
        if (list_push(&list, i * i) != 0) {
            fprintf(stderr, "Out of memory\n");
            free(list.data);
            return 1;
        }
    }

    for (size_t i = 0; i < list.size; i++) {
        printf("%d ", list.data[i]);
    }
    printf("\n(size %zu, capacity %zu)\n", list.size, list.capacity);

    free(list.data);                   /* every allocation needs exactly one free */
    list.data = NULL;                  /* avoid a dangling pointer */
    return 0;
}

Manual memory management brings three classic bugs: memory leaks (forgetting to free), use-after-free (using memory after freeing it) and double free (freeing the same memory twice). A simple rule helps: for every allocation, decide which part of the code owns it and is responsible for freeing it.

14. Structures, Unions and Enums

A structure groups related values of different types into one type, much like a class with only fields. An enum gives names to a set of integer constants, and a union lets several members share the same memory, so only one is valid at a time.

structs.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef enum { DRAFT, PUBLISHED, ARCHIVED } Status;

typedef struct {
    int id;
    char title[64];
    double rating;
    Status status;
} Article;

static const char *status_name(Status s)
{
    switch (s) {
        case DRAFT:     return "draft";
        case PUBLISHED: return "published";
        case ARCHIVED:  return "archived";
    }
    return "unknown";
}

static int by_rating_desc(const void *a, const void *b)
{
    const Article *x = a, *y = b;
    return (y->rating > x->rating) - (y->rating < x->rating);
}

int main(void)
{
    Article items[] = {
        {1, "Pointers Explained", 4.6, PUBLISHED},
        {2, "Memory Layout",      4.9, PUBLISHED},
        {3, "Bit Manipulation",   4.2, DRAFT},
    };
    size_t n = sizeof items / sizeof items[0];

    /* qsort from the standard library takes a function pointer for comparison */
    qsort(items, n, sizeof items[0], by_rating_desc);

    for (size_t i = 0; i < n; i++) {
        const Article *a = &items[i];          /* use -> to reach members through a pointer */
        printf("#%d %-20s %.1f (%s)\n", a->id, a->title, a->rating, status_name(a->status));
    }

    Article copy = items[0];                    /* structs are copied by value */
    strcpy(copy.title, "Changed copy");
    printf("original title still: %s\n", items[0].title);
    return 0;
}

Use . to reach a member of a structure variable and -> to reach it through a pointer (a->id is shorthand for (*a).id). Pass large structures to functions by pointer, using const when the function should not change them, to avoid copying them.

15. File Handling

Files are accessed through a FILE * handle returned by fopen. Always check that opening succeeded, and close the file with fclose.

files.c
#include <stdio.h>

int main(void)
{
    /* Write a small CSV file: "w" creates or truncates, "a" appends */
    FILE *out = fopen("expenses.csv", "w");
    if (out == NULL) {
        perror("fopen");                 /* prints the reason, such as a permission error */
        return 1;
    }
    fprintf(out, "item,amount\n");
    fprintf(out, "books,450.00\n");
    fprintf(out, "lunch,180.50\n");
    fprintf(out, "metro,60.00\n");
    fclose(out);

    /* Read it back line by line and total the amounts */
    FILE *in = fopen("expenses.csv", "r");
    if (in == NULL) {
        perror("fopen");
        return 1;
    }
    char line[128];
    char item[64];
    double amount, total = 0;

    fgets(line, sizeof line, in);       /* skip the header row */
    while (fgets(line, sizeof line, in) != NULL) {
        if (sscanf(line, "%63[^,],%lf", item, &amount) == 2) {
            printf("%-8s %8.2f\n", item, amount);
            total += amount;
        }
    }
    fclose(in);
    printf("total    %8.2f\n", total);
    return 0;
}

Open files in binary mode ("rb", "wb") when working with non-text data, and use fread and fwrite to transfer blocks of bytes.

16. Header Files and Multi-File Programs

Real programs are split across several files. The convention is:

  • A header file (.h) contains declarations: function prototypes, type definitions and constants that other files need.
  • A source file (.c) contains the definitions: the actual function bodies.
mathutil.h
#ifndef MATHUTIL_H          /* include guard: prevents double inclusion */
#define MATHUTIL_H

int gcd(int a, int b);
int lcm(int a, int b);

#endif
mathutil.c
#include "mathutil.h"

int gcd(int a, int b)
{
    while (b != 0) {
        int t = a % b;
        a = b;
        b = t;
    }
    return a < 0 ? -a : a;
}

int lcm(int a, int b)
{
    return (a == 0 || b == 0) ? 0 : (a / gcd(a, b)) * b;
}
main.c
#include <stdio.h>
#include "mathutil.h"       /* quotes: search the project directory first */

int main(void)
{
    printf("gcd(84, 36) = %d\n", gcd(84, 36));
    printf("lcm(4, 6)   = %d\n", lcm(4, 6));
    return 0;
}
terminal
# Compile each file to an object file, then link them together
gcc -Wall -Wextra -c mathutil.c
gcc -Wall -Wextra -c main.c
gcc -o calc main.o mathutil.o
./calc

Compiling files separately means only changed files need to be recompiled. Build tools automate this: Make uses a Makefile of rules and dependencies, and CMake generates build files for many platforms and IDEs.

17. Undefined Behaviour and Safe C

The C standard leaves some operations undefined. If a program does one of them, the standard places no requirements on what happens: it might work, crash, give wrong results, or behave differently after a compiler upgrade. Optimising compilers assume undefined behaviour never happens, so its effects can be surprising. Common examples:

  • Reading or writing outside the bounds of an array.
  • Dereferencing a NULL, freed or uninitialised pointer.
  • Overflowing a signed integer (unsigned integers wrap around safely).
  • Reading an uninitialised local variable.
  • Modifying a variable twice without a sequence point, as in i = i++ + 1;.
  • Modifying a string literal, such as char *s = "hi"; s[0] = 'H';.

Modern tools catch most of these problems during development:

terminal
# Strict warnings, debug symbols and runtime checks for memory and undefined behaviour
gcc -std=c17 -Wall -Wextra -Wpedantic -g -fsanitize=address,undefined -o app app.c
./app

# Step through the program in a debugger
gdb ./app

# On Linux, Valgrind finds leaks and invalid memory use without recompiling
valgrind --leak-check=full ./app
Habit worth building: compile with warnings and sanitizers on every development build. AddressSanitizer reports the exact line of an out-of-bounds access or use-after-free, turning bugs that could take days to find into a few minutes' work.

18. C vs. C++ vs. Java

AspectCC++Java
ParadigmProceduralMulti-paradigm: procedural, object-oriented, genericObject-oriented with functional features
ExecutionCompiled to native codeCompiled to native codeCompiled to bytecode, run by the JVM with JIT compilation
Memory managementManual (malloc/free)Manual, plus RAII and smart pointersAutomatic garbage collection
PointersFull pointer arithmeticPointers and referencesReferences only; no pointer arithmetic
Safety checksFew; bounds are not checkedFew by default; safer library types availableArray bounds and null checks at run time
Standard librarySmallLarge (containers, algorithms, strings)Very large
Typical usesKernels, embedded, system librariesGames, browsers, high-performance applicationsEnterprise back ends, Android, big data

Calling C from Java: Java can use native C libraries through the classic Java Native Interface (JNI) or, more simply, the Foreign Function and Memory API (java.lang.foreign), which became a standard feature in Java 22. It lets Java code call C functions and work with native memory without writing any C glue code. Start with the Java tutorial if you want to compare the two languages side by side.

19. Best Practices and Common Mistakes

Best practices

  • Compile with warnings (-Wall -Wextra) and fix every one.
  • Initialise every variable when you declare it.
  • Check every return value that can signal failure: malloc, fopen, scanf, fgets.
  • Always pass buffer sizes and use bounded functions such as snprintf and fgets.
  • Use const for data a function should not change, and size_t for sizes and indexes.
  • Keep ownership clear: document which function frees each allocation, and set pointers to NULL after freeing.
  • Keep functions small and hide internal helpers with static.
  • Test with sanitizers and Valgrind before release.

Common mistakes to avoid

MistakeWhat goes wrongFix
Writing if (x = 5)Assigns 5 and is always true.Use ==; compiler warnings catch this.
Off-by-one loops such as i <= n on an array of nWrites one element past the end.Loop with i < n.
Forgetting room for '\0'Strings run into neighbouring memory.Allocate strlen(s) + 1 bytes.
Comparing strings with ==Compares addresses, not contents.Use strcmp.
Returning a pointer to a local variableThe memory is gone when the function returns.Return by value, use a caller-supplied buffer, or allocate on the heap.
Using sizeof on an array parameterGives the size of a pointer, not the array.Pass the length as a separate parameter.
Ignoring malloc returning NULLCrash or memory corruption when memory runs out.Check the result and handle the failure.

20. Summary

  • C is a small, fast, portable, compiled language that gives direct control over memory and hardware.
  • A C program is built in four stages: preprocessing, compilation, assembly and linking.
  • Types have minimum sizes; use <stdint.h> when you need exact widths.
  • Arrays are fixed-size and unchecked; strings are char arrays ending in '\0'.
  • Pointers store addresses; they enable pass-by-reference, dynamic data structures and function pointers.
  • Heap memory comes from malloc, calloc and realloc and must be released with free.
  • Structures, enums and unions model data; header files and separate compilation organise larger programs.
  • Undefined behaviour is C's biggest risk; warnings, sanitizers, debuggers and Valgrind are your safety net.

A great next step is to write a small project that uses everything here, such as a command-line contact book that stores records in a file, grows a dynamic array and sorts entries with qsort. Then open the FAQ tab for answers to the questions learners most often ask about C.