Coding · Programming languages
C programming tutorial for beginners
Short answer
C is a small, fast, compiled language that gives direct access to memory through pointers. Write main in a .c file, compile it with gcc hello.c -o hello, and run ./hello. The core ideas are fixed-size types, functions, arrays and null-terminated strings, pointers and addresses, manual memory management with malloc and free, and error handling through return values.
What C is and what it is used for
C was created by Dennis Ritchie at Bell Labs in the early 1970s to write the Unix operating system. It is standardized by ISO (C99, C11, C17 and C23 are the main editions). C compiles to machine code with very little runtime, so it runs almost everywhere. It is used for operating system kernels (Linux), embedded firmware, databases (SQLite), language runtimes (CPython is written in C) and high-performance libraries. Learning C also teaches how memory and computers really work.
Install a compiler and run Hello, world
On Linux install gcc (for example sudo apt install build-essential); on macOS run xcode-select --install to get clang (also available as gcc); on Windows use MSYS2, WSL or Visual Studio.
#include <stdio.h>
int main(void) {
printf("Hello, world\n");
return 0;
}gcc -Wall -Wextra -std=c17 hello.c -o hello
./hello
# Hello, world#include pulls in declarations from a header file, here the standard input and output library. Returning 0 from main means success. Always compile with warnings on (-Wall -Wextra).
Variables and basic types
Every variable has a type that fixes its size and meaning. Sizes depend on the platform, except where the standard says otherwise; <stdint.h> provides exact-width types such as int32_t.
| Type | Typical size | Example | printf format |
|---|---|---|---|
char | 1 byte | char c = 'A'; | %c |
int | 4 bytes | int n = 42; | %d |
long long | 8 bytes | long long big = 1LL << 40; | %lld |
double | 8 bytes | double x = 3.14; | %f |
bool | 1 byte | bool ok = true; (C99 <stdbool.h>) | %d |
size_t | 8 bytes on 64-bit | size_t len = sizeof(arr); | %zu |
#include <stdio.h>
int main(void) {
int count = 7;
double avg = count / 2; /* 3.0: integer division first */
double avg2 = count / 2.0; /* 3.5 */
printf("%d %.1f %.1f\n", count, avg, avg2); /* 7 3.0 3.5 */
printf("%zu\n", sizeof(int)); /* 4 on most systems */
return 0;
}Control flow and functions
A function must be declared before it is used. Arguments are passed by value: the function receives a copy.
#include <stdio.h>
int square(int x) {
return x * x;
}
int main(void) {
int score = 72;
if (score >= 90) {
printf("A\n");
} else if (score >= 70) {
printf("B\n"); /* B */
} else {
printf("C\n");
}
for (int i = 0; i < 3; i++) {
printf("%d ", square(i)); /* 0 1 4 */
}
printf("\n");
int n = 10;
while (n > 1) n /= 2;
printf("%d\n", n); /* 1 */
return 0;
}C also has do/while, switch with case labels (remember break), and goto, which is mostly used for cleanup in error paths.
Arrays and strings
An array is a fixed number of elements stored next to each other. C does not check bounds and does not store the length, so you pass the length separately. A string is an array of char ending with a zero byte '\0'.
#include <stdio.h>
#include <string.h>
int sum(const int *values, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) total += values[i];
return total;
}
int main(void) {
int nums[] = {3, 1, 2, 5};
size_t n = sizeof nums / sizeof nums[0];
printf("%zu items, sum %d\n", n, sum(nums, n)); /* 4 items, sum 11 */
char name[16] = "Ada";
strncat(name, " Lovelace", sizeof name - strlen(name) - 1);
printf("%s (%zu chars)\n", name, strlen(name)); /* Ada Lovelace (12 chars) */
return 0;
}When an array is passed to a function it decays to a pointer to its first element, which is why sum takes const int * and a length.
Signature feature: pointers
A pointer holds the memory address of another value. &x gives the address of x, and *p reads or writes the value at address p. Pointers let functions modify the caller's variables, share large data without copying, and build linked data structures.
#include <stdio.h>
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 1, y = 2;
int *p = &x;
*p = 10; /* changes x */
swap(&x, &y);
printf("x=%d y=%d\n", x, y); /* x=2 y=10 */
int arr[3] = {7, 8, 9};
int *q = arr;
printf("%d %d\n", *(q + 1), q[2]); /* 8 9: pointer arithmetic */
return 0;
}Dynamic memory with malloc and free
Local variables live on the stack and disappear when the function returns. For data whose size is known only at run time, or that must outlive the function, allocate on the heap with malloc and release it with free. Every successful malloc needs exactly one free.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t n = 5;
int *squares = malloc(n * sizeof *squares);
if (squares == NULL) {
fprintf(stderr, "out of memory\n");
return 1;
}
for (size_t i = 0; i < n; i++) squares[i] = (int)(i * i);
printf("%d\n", squares[4]); /* 16 */
free(squares);
squares = NULL; /* avoid using a dangling pointer */
return 0;
}Related functions are calloc (allocates and zeroes) and realloc (resizes). Group related values with a struct, for example struct point { int x; int y; };.
Error handling
C has no exceptions. Functions report errors through return values (often -1, NULL or a status code), and many library functions also set the global errno. Check every return value that can fail.
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *f = fopen("missing.txt", "r");
if (f == NULL) {
fprintf(stderr, "cannot open: %s\n", strerror(errno));
return 1; /* cannot open: No such file or directory */
}
fclose(f);
return 0;
}A small complete program: line and word counter
This program works like a small wc: it reads a file named on the command line and prints the number of lines, words and bytes.
#include <ctype.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: %s FILE\n", argv[0]);
return 1;
}
FILE *f = fopen(argv[1], "r");
if (f == NULL) {
perror(argv[1]);
return 1;
}
long lines = 0, words = 0, bytes = 0;
int c, in_word = 0;
while ((c = fgetc(f)) != EOF) {
bytes++;
if (c == '\n') lines++;
if (isspace(c)) {
in_word = 0;
} else if (!in_word) {
in_word = 1;
words++;
}
}
fclose(f);
printf("%ld lines, %ld words, %ld bytes\n", lines, words, bytes);
return 0;
}Compile with gcc -Wall -Wextra wc.c -o wc and run ./wc notes.txt. Note that c is an int, not a char, so it can hold the special value EOF.
Common beginner mistakes
- Using
=instead of==in a condition. Warnings (-Wall) catch most cases. - Forgetting room for the terminating
'\0'when sizing a string buffer. - Using
getsor an unboundedscanf("%s", buf). Usefgets(buf, sizeof buf, stdin)instead. - Returning a pointer to a local variable, which no longer exists after the function returns.
- Mismatching
printfformats and types, such as%dfor asize_t.
Where to go next
Use cppreference.com's C section as a reference for the language and standard library, and read the ISO working group (WG14) pages for the standard itself. Practice with structs, linked lists, header files and multi-file programs built with make. A debugger (gdb or lldb) and Valgrind or AddressSanitizer will save you hours. Then explore C++ or Rust, which build on the same ideas with more safety.
FAQ
Is C still worth learning?
Yes. It is still used for operating systems, embedded devices and performance-critical libraries, and it teaches memory, pointers and how compiled code runs, which helps in every other language.
What is the difference between C and C++?
C++ started as an extension of C and adds classes, templates, references, exceptions and a large standard library. C stays small and procedural. Most C code is close to valid C++, but they are separate standards.
Why does my C program crash with a segmentation fault?
It accessed memory it does not own, usually through a null, uninitialized or freed pointer, or by indexing past the end of an array. Run it in a debugger or with AddressSanitizer to find the exact line.
Which C standard should I use?
C17 is widely supported by current compilers, and C23 is the newest standard. Pass the version explicitly, for example -std=c17, so behavior is consistent.