A comprehensive introductory course covering the C programming language from absolute beginner concepts through pointers, memory management, file handling, dat…
A comprehensive introductory course covering the C programming language from absolute beginner concepts through pointers, memory management, file handling, data structures, debugging, compilation, and practical projects.
Table of Contents
1. Introduction to C
1.1 What Is C?
C is a general-purpose, procedural programming language designed for efficiency, portability, and low-level control over computer hardware.
C occupies an unusual position in programming.
It allows you to write relatively high-level algorithms using concepts such as:
- Variables
- Functions
- Loops
- Arrays
- Structures
- Modular programs
At the same time, C gives programmers direct access to concepts close to the hardware, including:
- Memory addresses
- Pointers
- Bytes
- Bits
- Memory allocation
- Object representation
This combination makes C extremely important in computer science.
A simple C program can look like this:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
Although this program is small, it introduces several fundamental ideas:
#include — incorporates a header file.
<stdio.h> — provides standard input/output facilities.
main() — the program's entry point.
printf() — displays formatted output.
return 0 — indicates successful program termination.
{} — delimits a block of code.
; — terminates a statement.
1.2 History of C
C was developed at Bell Labs during the early 1970s, primarily by Dennis Ritchie.
It evolved from earlier languages, particularly:
BCPL
↓
B
↓
C
C became closely associated with the development of Unix.
A major strength of C was that Unix could be implemented largely in C rather than being written entirely in assembly language.
This made it easier to move the operating system between different computer architectures.
Major C standards
| Standard |
Approximate era |
Major significance |
| K&R C |
1970s–1980s |
Early widely used form of C |
| C89 |
1989 |
First ANSI standardized C |
| C90 |
1990 |
ISO adoption of C89 |
| C99 |
1999 |
Major language expansion |
| C11 |
2011 |
Threads, atomics, improved language facilities |
| C17 |
2018 |
Maintenance revision |
| C23 |
2024 |
Major modern revision |
C continues to evolve through the ISO C standardization process.
1.3 Why C Matters
C is important for several reasons.
Hardware control
C provides direct access to memory through pointers.
Performance
Well-written C programs can be highly efficient.
Portability
C programs can often be compiled for many different architectures.
Small runtime footprint
C does not require a large language runtime environment.
Educational value
Learning C teaches concepts that are sometimes hidden by higher-level languages.
For example, in Python:
numbers = [10, 20, 30]
The programmer does not normally need to think about the exact memory layout of the list.
In C:
int numbers[3] = {10, 20, 30};
the programmer can reason directly about the array's representation in memory.
1.4 Where C Is Used
C is used extensively in:
- Operating systems
- Embedded systems
- Firmware
- Device drivers
- Microcontrollers
- Networking software
- Databases
- Compilers
- Interpreters
- Cryptographic libraries
- Graphics systems
- Scientific software
- High-performance applications
- System utilities
Large portions of important software infrastructure are implemented in C or depend on C libraries.
1.5 Advantages and Disadvantages
Advantages
- High performance
- Portable
- Small language core
- Mature ecosystem
- Excellent compiler support
- Direct memory access
- Suitable for embedded systems
- Large collection of existing libraries
- Excellent foundation for understanding computer systems
Disadvantages
- Manual memory management
- Few built-in safety mechanisms
- Pointer errors can be serious
- Buffer overflows are possible
- Undefined behavior can be difficult to understand
- No built-in object-oriented programming model
- No automatic garbage collection
- Relatively easy to write incorrect programs that compile successfully
Key takeaway
C gives programmers considerable control.
That control is both its greatest strength and one of its greatest sources of complexity.
2. Setting Up a C Development Environment
2.1 Compiler, Linker, and IDE
A C program normally goes through several stages before it becomes executable.
C source code
↓
Preprocessor
↓
Compiler
↓
Assembly
↓
Object file
↓
Linker
↓
Executable program
Source code
A C source file normally has the .c extension.
Example:
hello.c
Header file
A header normally has the .h extension.
Example:
stdio.h
Compiler
A compiler translates C source code into lower-level code.
Common C compilers include:
- GCC
- Clang
- Microsoft Visual C
- Intel C compilers
- Various embedded-system compilers
Linker
The linker combines object files and libraries into an executable.
2.2 GCC
GCC, the GNU Compiler Collection, is one of the most widely used C compilers.
Check whether GCC is installed:
gcc --version
Compile a program:
gcc hello.c -o hello
Run it on Linux or macOS:
./hello
On Windows using an appropriate GCC environment:
hello.exe
Recommended warning options
For learning C, compile with warnings enabled:
gcc -Wall -Wextra -Wpedantic hello.c -o hello
You can also specify a language standard:
gcc -std=c17 -Wall -Wextra -Wpedantic hello.c -o hello
2.3 Compiling Your First Program
Create:
hello.c
Put this inside:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
Compile:
gcc -Wall -Wextra -Wpedantic hello.c -o hello
Run:
./hello
Output:
Hello, world!
Common compilation problems
If you see:
gcc: command not found
the compiler is probably not installed or is not available through your system's PATH.
If you see a syntax error, inspect:
- The line reported by the compiler.
- The preceding line.
- Missing semicolons.
- Missing parentheses.
- Missing braces.
- Misspelled identifiers.
3. Your First C Program
Consider:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
Let's examine it line by line.
#include <stdio.h>
This requests the contents of the standard input/output header.
It provides the declaration of functions such as:
printf()
scanf()
fopen()
fclose()
int main(void)
This defines the program's main function.
int means the function returns an integer.
void means the function accepts no arguments.
{
Begins the function body.
printf()
Displays formatted output.
"\n"
Represents a newline character.
return 0;
Returns zero to the environment that launched the program.
Conventionally, zero indicates successful termination.
4. C Syntax and Fundamental Concepts
C is case-sensitive.
These are different identifiers:
age
Age
AGE
4.1 Statements
A statement generally ends with a semicolon:
int age = 20;
age = 21;
printf("%d\n", age);
4.2 Blocks
Blocks are enclosed in braces:
{
int x = 10;
printf("%d\n", x);
}
4.3 Comments
Single-line:
// This is a comment
Multi-line:
/*
This is a
multi-line comment.
*/
Comments are ignored by the compiler.
4.4 Identifiers
Identifiers name things such as:
- Variables
- Functions
- Structures
- Enumerations
A typical identifier can contain letters, digits, and underscores, but cannot begin with a digit.
Valid:
age
student_name
value2
Invalid:
2value
student-name
5. Variables and Data Types
A variable represents an object that can store a value.
int age = 34;
Here:
int is the type.
age is the identifier.
34 is the initial value.
5.1 Declaration
int age;
5.2 Initialization
int age = 34;
5.3 Assignment
age = 35;
Assignment changes the stored value.
5.4 Integer Types
Common integer types include:
char
short
int
long
long long
Each may be signed or unsigned.
Example:
unsigned int population = 500000;
The exact size of an integer type is implementation-dependent.
Use sizeof to determine its size on the current implementation:
printf("%zu\n", sizeof(int));
5.5 Floating-Point Types
C provides:
float
double
long double
Example:
double price = 19.99;
Floating-point numbers have finite precision.
Therefore:
double x = 0.1 + 0.2;
should not necessarily be expected to compare exactly equal to:
0.3
This is a consequence of floating-point representation.
5.6 Character Type
A character can be represented using:
char letter = 'A';
Character constants use single quotes.
Strings use double quotes:
char letter = 'A';
char word[] = "Apple";
5.7 Boolean Values
Modern C can use:
#include <stdbool.h>
bool logged_in = true;
Boolean values are:
true
false
5.8 void
void represents the absence of a value or type.
For example:
void print_message(void)
{
printf("Hello\n");
}
The function returns no value.
5.9 sizeof
sizeof determines the size of an object or type.
printf("%zu\n", sizeof(int));
For an array:
int numbers[10];
printf("%zu\n", sizeof(numbers));
The result is measured in bytes.
5.10 Integer Limits
The <limits.h> header provides information about integer ranges.
Example:
#include <limits.h>
#include <stdio.h>
int main(void)
{
printf("INT_MIN = %d\n", INT_MIN);
printf("INT_MAX = %d\n", INT_MAX);
return 0;
}
6. Constants and Literals
6.1 Integer Literals
10
-20
1000
Hexadecimal:
0xFF
Octal:
0755
C23 also introduces additional modern syntax, including binary integer literals.
6.2 Floating-Point Literals
3.14
2.5
1.0e6
6.3 Character Literals
'A'
'7'
'\n'
6.4 String Literals
"Hello"
"University"
"Swiftener"
A string literal contains a terminating null character.
Conceptually:
"CAT"
+---+---+---+----+
| C | A | T | \0 |
+---+---+---+----+
6.5 const
You can declare an object whose value should not be modified through that identifier:
const double PI = 3.141592653589793;
Attempting to modify it:
PI = 4.0;
is invalid.
6.6 #define
The preprocessor can define macros:
#define MAX_STUDENTS 100
Macros are textual substitutions and therefore require care.
For typed constants, const is often preferable when appropriate.
7. Input and Output
The standard I/O functions are declared in:
#include <stdio.h>
7.1 printf()
printf("Hello\n");
Variables:
int age = 20;
printf("Age: %d\n", age);
7.2 Format Specifiers
| Type |
Typical format |
int |
%d |
unsigned int |
%u |
long |
%ld |
long long |
%lld |
float |
%f |
double |
%f |
char |
%c |
| String |
%s |
| Pointer |
%p |
size_t |
%zu |
For printf, a float argument is promoted to double.
7.3 Reading Input With scanf
int age;
printf("Enter your age: ");
scanf("%d", &age);
The & operator supplies the address where scanf() should store the result.
7.4 Why & Matters
Consider:
int age;
scanf("%d", &age);
Conceptually:
age
+------+
| ???? |
+------+
^
|
&age
scanf() needs the location of age, not merely its current value.
7.5 Reading Strings Safely
Prefer bounded input such as:
char name[100];
fgets(name, sizeof name, stdin);
This is generally safer than:
scanf("%s", name);
because an unbounded %s conversion can overflow the destination array.
7.6 getchar()
Reads one character:
int ch = getchar();
Notice that the result is stored in an int, not necessarily a char, because getchar() must also be able to represent EOF.
7.7 putchar()
putchar('A');
8. Operators and Expressions
8.1 Arithmetic Operators
+ addition
- subtraction
* multiplication
/ division
% remainder
Example:
int remainder = 17 % 5;
Result:
2
8.2 Integer Division
This:
int result = 5 / 2;
produces:
2
not:
2.5
because both operands are integers.
Use:
double result = 5.0 / 2.0;
to obtain:
2.5
8.3 Assignment
x = 10;
Compound assignments:
x += 5;
x -= 5;
x *= 5;
x /= 5;
x %= 5;
8.4 Increment and Decrement
x++;
x--;
Prefix:
++x;
--x;
Postfix:
x++;
x--;
The distinction matters when the expression's value is used.
8.5 Relational Operators
<
>
<=
>=
Equality:
==
!=
Remember:
x = 5;
means assignment.
While:
x == 5;
tests equality.
8.6 Logical Operators
&&
||
!
Example:
if (age >= 18 && age <= 65)
{
printf("Within range\n");
}
C uses short-circuit evaluation.
For:
A && B
if A is false, B does not need to be evaluated.
For:
A || B
if A is true, B does not need to be evaluated.
8.7 Bitwise Operators
&
|
^
~
<<
>>
These operate on integer representations at the bit level.
8.8 Conditional Operator
int max = (a > b) ? a : b;
This means:
if a > b:
max = a
else:
max = b
8.9 Operator Precedence
For example:
int result = 2 + 3 * 4;
Multiplication has higher precedence.
Therefore:
2 + (3 * 4)
produces:
14
When in doubt, use parentheses:
int result = 2 + (3 * 4);
Parentheses often improve readability even when they are technically unnecessary.
9. Type Conversion and Casting
C frequently converts values between types.
9.1 Implicit Conversion
double x = 10;
The integer 10 is converted to a floating-point value.
9.2 Explicit Conversion
double result = (double)5 / 2;
The cast causes the division to occur using floating-point arithmetic.
Result:
2.5
Without the cast:
double result = 5 / 2;
the integer division occurs first, producing 2, which is then converted to 2.0.
9.3 Narrowing
Consider:
int x = 300;
char c = x;
Depending on the implementation and the value representation, information may be lost.
Conversions should therefore be deliberate.
10. Conditional Statements
10.1 if
if (age >= 18)
{
printf("Adult\n");
}
10.2 if...else
if (age >= 18)
{
printf("Adult\n");
}
else
{
printf("Minor\n");
}
10.3 else if
if (score >= 80)
{
printf("A\n");
}
else if (score >= 70)
{
printf("B\n");
}
else if (score >= 60)
{
printf("C\n");
}
else
{
printf("Below C\n");
}
10.4 switch
switch (choice)
{
case 1:
printf("Add\n");
break;
case 2:
printf("Subtract\n");
break;
default:
printf("Unknown choice\n");
break;
}
Fall-through
If break is omitted:
switch (x)
{
case 1:
printf("One\n");
case 2:
printf("Two\n");
break;
}
when x == 1, both messages can execute.
Fall-through can be intentional, but accidental fall-through is a common bug.
11. Loops and Iteration
11.1 while
int i = 1;
while (i <= 5)
{
printf("%d\n", i);
i++;
}
11.2 do...while
int choice;
do
{
printf("1. Continue\n");
printf("2. Exit\n");
scanf("%d", &choice);
}
while (choice != 2);
The body executes at least once.
11.3 for
for (int i = 0; i < 10; i++)
{
printf("%d\n", i);
}
A for loop has three major components:
initialization
condition
update
11.4 break
Terminates the nearest loop or switch.
for (int i = 0; i < 100; i++)
{
if (i == 10)
break;
}
11.5 continue
Skips the remainder of the current iteration.
for (int i = 0; i < 10; i++)
{
if (i % 2 == 0)
continue;
printf("%d\n", i);
}
Output:
1
3
5
7
9
12. Functions
Functions allow programs to be divided into reusable components.
12.1 Function Definition
int add(int a, int b)
{
return a + b;
}
12.2 Function Call
int result = add(10, 20);
12.3 Function Prototype
A declaration can appear before the function definition:
int add(int a, int b);
Then:
int main(void)
{
printf("%d\n", add(2, 3));
return 0;
}
int add(int a, int b)
{
return a + b;
}
12.4 Pass-by-Value
C passes function arguments by value.
void change(int x)
{
x = 100;
}
This does not change the caller's variable.
int value = 10;
change(value);
printf("%d\n", value);
The output remains:
10
To modify the caller's object, pass a pointer:
void change(int *x)
{
*x = 100;
}
Then:
int value = 10;
change(&value);
Now value becomes 100.
C does not have true pass-by-reference parameters in the sense used by languages that provide reference parameter types.
12.5 Recursion
A recursive function calls itself.
Example:
int factorial(int n)
{
if (n <= 1)
return 1;
return n * factorial(n - 1);
}
For:
factorial(5)
the conceptual sequence is:
5 × factorial(4)
5 × 4 × factorial(3)
5 × 4 × 3 × factorial(2)
5 × 4 × 3 × 2 × factorial(1)
5 × 4 × 3 × 2 × 1
13. Arrays
An array stores multiple objects of the same type.
int numbers[5];
Indexes begin at zero.
Index: 0 1 2 3 4
↓ ↓ ↓ ↓ ↓
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
13.1 Initialization
int numbers[5] = {10, 20, 30, 40, 50};
Access:
printf("%d\n", numbers[0]);
Output:
10
13.2 Traversing an Array
int numbers[] = {10, 20, 30, 40, 50};
size_t count = sizeof numbers / sizeof numbers[0];
for (size_t i = 0; i < count; i++)
{
printf("%d\n", numbers[i]);
}
This works because numbers is an actual array in that context.
13.3 Array Bounds
This is invalid:
int numbers[5];
numbers[5] = 100;
Valid indexes are:
0
1
2
3
4
Access outside the array is undefined behavior.
13.4 Multidimensional Arrays
int matrix[3][3] =
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
Access:
printf("%d\n", matrix[1][2]);
Output:
6
14. Strings
C does not have a built-in high-level string object.
Instead, strings are represented as arrays of characters terminated by a null character:
'\0'
Example:
char name[] = "Michael";
Conceptually:
+---+---+---+---+---+---+---+----+
| M | i | c | h | a | e | l | \0 |
+---+---+---+---+---+---+---+----+
14.1 strlen
size_t length = strlen(name);
Remember:
strlen() counts characters before the terminating null character.
14.2 Copying Strings
strcpy(destination, source);
But the destination must have enough space.
For example:
char destination[20];
strcpy(destination, "Hello");
14.3 Comparing Strings
Do not compare strings using:
if (a == b)
This compares pointer values when a and b are pointers.
Use:
if (strcmp(a, b) == 0)
{
printf("Equal\n");
}
14.4 Common String Functions
<string.h> provides functions including:
strlen()
strcpy()
strncpy()
strcat()
strncat()
strcmp()
strncmp()
strchr()
strstr()
strtok()
Each must be used with careful attention to buffer sizes and string termination.
15. Pointers
Pointers are one of the most important concepts in C.
A pointer stores an address.
Consider:
int x = 42;
Conceptually:
Memory
Address Value
0x1000 42
↑
x
A pointer can store the address:
int *p = &x;
Now:
p
↓
+--------+
| 0x1000 |
+--------+
|
v
+--------+
| 42 |
+--------+
x
15.1 Address-of Operator
&x
means:
Obtain the address of x.
15.2 Dereference Operator
*p
means:
Access the object pointed to by p.
Example:
int x = 42;
int *p = &x;
printf("%d\n", *p);
Output:
42
15.3 Modifying Through a Pointer
*p = 100;
Now x becomes:
100
15.4 NULL Pointers
A pointer can intentionally point to no object:
int *p = NULL;
Before dereferencing:
if (p != NULL)
{
printf("%d\n", *p);
}
Dereferencing NULL is invalid.
15.5 Pointer Arithmetic
Given:
int numbers[] = {10, 20, 30};
int *p = numbers;
Then:
printf("%d\n", *p);
printf("%d\n", *(p + 1));
printf("%d\n", *(p + 2));
produces:
10
20
30
Pointer arithmetic is scaled according to the pointed-to type.
If p is an int *, then:
p + 1
points to the next int, not merely the next byte.
15.6 Arrays and Pointers
In many expressions, an array expression is converted to a pointer to its first element.
Therefore:
numbers[i]
is closely related to:
*(numbers + i)
This relationship is fundamental to C.
However, arrays and pointers are not identical types.
15.7 Pointer to Pointer
int x = 10;
int *p = &x;
int **pp = &p;
Conceptually:
pp
↓
+------+
| p |
+------+
↓
+------+
| 10 |
+------+
x
Then:
**pp
produces:
10
15.8 void *
A void * can hold the address of an object of an arbitrary object type.
Example:
int x = 10;
void *p = &x;
Before dereferencing it, convert it to an appropriate pointer type:
printf("%d\n", *(int *)p);
15.9 Common Pointer Errors
Uninitialized pointer
int *p;
*p = 10;
p does not point to a valid object.
NULL dereference
int *p = NULL;
*p = 10;
Invalid.
Dangling pointer
int *p = malloc(sizeof *p);
free(p);
*p = 10;
The allocated object no longer exists.
Double free
free(p);
free(p);
This is invalid.
16. Dynamic Memory Management
Dynamic memory is allocated during program execution.
Include:
#include <stdlib.h>
16.1 malloc
int *numbers = malloc(10 * sizeof *numbers);
Always check whether allocation succeeded when failure matters:
if (numbers == NULL)
{
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
16.2 free
When finished:
free(numbers);
numbers = NULL;
16.3 calloc
int *numbers = calloc(10, sizeof *numbers);
calloc() allocates space for multiple objects and initializes the allocated bytes to zero.
16.4 realloc
int *tmp = realloc(numbers, 20 * sizeof *numbers);
if (tmp != NULL)
{
numbers = tmp;
}
Using a temporary pointer is important because a failed realloc() does not necessarily destroy the original allocation.
16.5 Memory Leaks
A memory leak occurs when dynamically allocated memory remains allocated but the program loses the ability to access it.
Example:
int *p = malloc(sizeof *p);
p = NULL;
The allocated memory is now inaccessible.
The correct approach is:
free(p);
p = NULL;
17. Structures
A structure combines multiple objects, potentially of different types.
struct Student
{
char name[100];
int age;
double grade;
};
Create an object:
struct Student student;
Initialize:
struct Student student =
{
"Alice",
20,
85.5
};
Access members:
printf("%s\n", student.name);
printf("%d\n", student.age);
printf("%.2f\n", student.grade);
17.1 typedef
Instead of repeatedly writing:
struct Student
you can define:
typedef struct
{
char name[100];
int age;
double grade;
} Student;
Then:
Student student;
17.2 Pointer to Structure
Student *p = &student;
Access through:
p->age
which is equivalent to:
(*p).age
17.3 Structure Padding
Structures can contain padding inserted by the implementation for alignment.
Therefore:
sizeof(struct Student)
may be larger than the sum of the apparent sizes of its members.
This matters in:
- Binary file formats
- Networking
- Interoperability
- Memory optimization
18. Unions
A union allows multiple members to share storage.
union Data
{
int i;
float f;
char c;
};
Unlike a structure, its members overlap in storage.
For example:
union
+-------------------+
| shared storage |
+-------------------+
↑ ↑ ↑
int float char
Only one representation should generally be treated as active at a time according to the applicable C rules.
Unions are useful for:
- Variant representations
- Hardware interfaces
- Compact data structures
- Object representation
19. Enumerations
An enumeration defines named integer constants.
enum Day
{
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY
};
By default:
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
...
You can specify values:
enum Status
{
SUCCESS = 0,
ERROR = 1,
UNKNOWN = 100
};
Enumerations improve readability when a variable has a limited conceptual set of states.
20. Scope, Storage Duration, Linkage, and Lifetime
These concepts are often confused.
Scope
Scope describes where an identifier can be referred to.
Storage duration
Storage duration describes how long the object's storage exists.
Linkage
Linkage describes whether declarations in different scopes or translation units refer to the same entity.
Lifetime
Lifetime concerns the period during which an object exists.
20.1 Local Variables
void function(void)
{
int x = 10;
}
x has block scope and automatic storage duration.
20.2 Static Local Variables
void counter(void)
{
static int count = 0;
count++;
printf("%d\n", count);
}
The variable persists between calls.
20.3 Global Variables
A file-scope definition:
int total = 0;
has static storage duration.
Global variables should generally be used deliberately because excessive global state can make programs difficult to reason about.
20.4 extern
extern can declare an object defined elsewhere:
extern int total;
This is particularly useful across source files.
20.5 static at File Scope
A file-scope declaration such as:
static int internal_value;
gives the identifier internal linkage.
This can be useful for hiding implementation details inside a source file.
21. The C Preprocessor
The preprocessor operates before the compiler processes the resulting translation unit.
21.1 #include
#include <stdio.h>
or:
#include "myheader.h"
21.2 Macros
#define PI 3.14159
Function-like macro:
#define SQUARE(x) ((x) * (x))
Parentheses are important.
A poorly written macro such as:
#define SQUARE(x) x * x
can produce surprising results:
SQUARE(2 + 3)
may expand to:
2 + 3 * 2 + 3
rather than:
25
21.3 Conditional Compilation
#ifdef DEBUG
printf("Debug information\n");
#endif
Include guards:
#ifndef MY_HEADER_H
#define MY_HEADER_H
/* declarations */
#endif
22. Header Files and Modular Programming
Large programs should not be placed in one source file.
A project might look like:
project/
├── main.c
├── calculator.c
├── calculator.h
└── Makefile
calculator.h:
#ifndef CALCULATOR_H
#define CALCULATOR_H
int add(int a, int b);
int subtract(int a, int b);
#endif
calculator.c:
#include "calculator.h"
int add(int a, int b)
{
return a + b;
}
int subtract(int a, int b)
{
return a - b;
}
main.c:
#include <stdio.h>
#include "calculator.h"
int main(void)
{
printf("%d\n", add(10, 5));
return 0;
}
Compile:
gcc -Wall -Wextra -Wpedantic main.c calculator.c -o calculator
This is an example of separate compilation.
23. File Handling
Files are accessed through FILE *.
FILE *file = fopen("data.txt", "r");
Always check:
if (file == NULL)
{
perror("data.txt");
return 1;
}
Close:
fclose(file);
23.1 File Modes
Common modes include:
"r" read
"w" write
"a" append
"r+" read/write
"w+" read/write, truncate
"a+" read/append
Binary mode can be specified using b, for example:
"rb"
"wb"
23.2 Writing Text
FILE *file = fopen("data.txt", "w");
if (file == NULL)
{
perror("data.txt");
return 1;
}
fprintf(file, "Hello, file!\n");
fclose(file);
23.3 Reading Text
char line[256];
FILE *file = fopen("data.txt", "r");
if (file == NULL)
{
perror("data.txt");
return 1;
}
while (fgets(line, sizeof line, file) != NULL)
{
printf("%s", line);
}
fclose(file);
23.4 Binary Files
Binary data can be read and written using:
fread()
fwrite()
Example:
fwrite(&value, sizeof value, 1, file);
Be careful when using raw structure representations as portable file formats because of:
- Padding
- Alignment
- Endianness
- Type sizes
- Representation differences
24. Error Handling
C commonly communicates errors using return values.
Example:
FILE *file = fopen("data.txt", "r");
if (file == NULL)
{
perror("fopen");
return 1;
}
24.1 errno
Many library functions can communicate additional error information through errno.
#include <errno.h>
24.2 perror
perror("Unable to open file");
24.3 strerror
printf("%s\n", strerror(errno));
24.4 Assertions
#include <assert.h>
assert(pointer != NULL);
Assertions are primarily intended for detecting programming assumptions that should never be violated during normal execution.
They are not a replacement for handling untrusted user input.
25. Command-Line Arguments
A program can receive arguments through:
int main(int argc, char *argv[])
Example:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Argument count: %d\n", argc);
for (int i = 0; i < argc; i++)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
Run:
./program hello world
The program receives:
argv[0] = ./program
argv[1] = hello
argv[2] = world
25.1 Converting Strings to Numbers
Prefer functions such as:
strtol()
strtoul()
strtod()
rather than relying on simplistic conversions when robust error detection is required.
26. Bit Manipulation
Computers represent information using bits.
A byte commonly contains eight bits, although C defines the relationship in terms of CHAR_BIT rather than universally assuming eight.
Suppose:
00001111
and:
00000101
Bitwise AND:
00000101
Bitwise OR:
00001111
Bitwise XOR:
00001010
26.1 Testing a Bit
Suppose:
unsigned int flags = 0;
Set bit 3:
flags |= (1u << 3);
Test bit 3:
if (flags & (1u << 3))
{
printf("Bit is set\n");
}
Clear bit 3:
flags &= ~(1u << 3);
Toggle bit 3:
flags ^= (1u << 3);
Bit manipulation is common in:
- Embedded programming
- Device drivers
- Protocols
- Compression
- Permissions
- Hardware control
27. Advanced Function Concepts
27.1 Function Pointers
A function pointer stores the address of a function.
Example:
int add(int a, int b)
{
return a + b;
}
int main(void)
{
int (*operation)(int, int) = add;
printf("%d\n", operation(2, 3));
return 0;
}
Output:
5
27.2 Callbacks
A callback is a function supplied to another function so that it can be invoked later.
Function pointers make callbacks possible in C.
A well-known example is:
qsort()
which accepts a comparison function.
28. The C Standard Library
Important standard headers include:
| Header |
Purpose |
<stdio.h> |
Input/output |
<stdlib.h> |
Memory allocation, conversions, utilities |
<string.h> |
String and memory functions |
<math.h> |
Mathematical functions |
<ctype.h> |
Character classification/conversion |
<time.h> |
Time and date facilities |
<stdbool.h> |
Boolean type support |
<stdint.h> |
Integer types with specified widths where available |
<inttypes.h> |
Integer formatting/conversion facilities |
<limits.h> |
Integer limits |
<float.h> |
Floating-point characteristics |
<assert.h> |
Assertions |
<errno.h> |
Error reporting |
<stddef.h> |
Common definitions |
<stdarg.h> |
Variadic functions |
<signal.h> |
Signals |
<setjmp.h> |
Non-local jumps |
<locale.h> |
Localization |
29. Debugging C Programs
Debugging is the systematic process of finding and correcting program defects.
29.1 Start With Compiler Warnings
Use:
gcc -Wall -Wextra -Wpedantic program.c -o program
Warnings are valuable because they often identify suspicious code before it becomes a runtime failure.
29.2 Debugging With printf
Example:
printf("DEBUG: value = %d\n", value);
Useful for simple programs, but larger programs benefit from dedicated debuggers.
29.3 GDB
Compile with debugging information:
gcc -g program.c -o program
Start GDB:
gdb ./program
Useful commands include:
break main
run
next
step
print variable
continue
backtrace
quit
30. Undefined, Unspecified, and Implementation-Defined Behavior
One of the most important concepts in C is that not everything has a single universally defined result.
Undefined behavior
The C standard imposes no requirements on what happens.
Examples can include:
int numbers[3];
numbers[10] = 5;
or using memory after it has been freed.
Undefined behavior does not simply mean "the program crashes."
The compiler may make assumptions that result in unexpected behavior.
Unspecified behavior
The implementation can choose between multiple permitted possibilities.
The program cannot rely on which permitted choice will occur unless the relevant specification guarantees it.
Implementation-defined behavior
The implementation chooses a behavior and documents the choice.
Examples can include certain properties of fundamental types.
Why this matters
Code that appears to work:
on my laptop
may behave differently:
with another compiler
on another CPU
with another optimization level
Portable C requires understanding the guarantees actually provided by the language and implementation.
31. C and Memory
A simplified process memory model might look like:
High addresses
+----------------------+
| Stack |
| ↓ |
| |
| |
| ↑ |
| Heap |
+----------------------+
| Global/static data |
+----------------------+
| Read-only data |
+----------------------+
| Program code |
+----------------------+
Low addresses
This is a conceptual model, not a universal physical layout mandated by C.
Stack
Often used for:
- Automatic local objects
- Function call state
Heap
Often used for dynamically allocated objects.
Static storage
Objects with static storage duration exist for the duration of the program.
Understanding these concepts helps explain:
- Pointers
- Recursion
- Dynamic memory
- Buffer overflows
- Dangling pointers
32. Portability
Portable software avoids relying unnecessarily on implementation-specific behavior.
Avoid assumptions such as:
sizeof(int) == 4
unless your program explicitly targets an environment where that property is guaranteed.
For fixed-width integer requirements, use types such as:
int32_t
uint32_t
when provided by:
#include <stdint.h>
Also consider:
- Endianness
- Character encoding
- Alignment
- Integer representation
- Compiler extensions
- Operating-system APIs
33. Security and Safe C Programming
C's low-level capabilities require careful programming.
33.1 Buffer Overflow
Dangerous:
char buffer[10];
scanf("%s", buffer);
An excessively long input can exceed the array.
Safer:
fgets(buffer, sizeof buffer, stdin);
33.2 Use-After-Free
Dangerous:
int *p = malloc(sizeof *p);
free(p);
printf("%d\n", *p);
After free(p), the object no longer exists.
33.3 Double Free
Dangerous:
free(p);
free(p);
Correct ownership and cleanup practices are essential.
33.4 Format String Problems
Avoid:
printf(user_input);
Prefer:
printf("%s", user_input);
The first form treats user-controlled content as a format string.
33.5 Security Principles
Good C programming should emphasize:
- Validate input.
- Bound buffer operations.
- Check allocation results.
- Check file operations.
- Check return values.
- Initialize objects appropriately.
- Release resources exactly once.
- Avoid undefined behavior.
- Enable compiler warnings.
- Use testing and static analysis.
34. Coding Style and Best Practices
Good C code should be:
- Readable
- Predictable
- Modular
- Testable
- Portable where appropriate
- Explicit about ownership
- Careful with resources
Prefer:
const int max_students = 100;
over repeated unexplained:
100
Avoid unnecessary global state.
Keep functions focused.
Use descriptive names:
calculate_average()
is generally clearer than:
calc()
for educational code.
35. Algorithms and Problem Solving
Programming is not primarily about memorizing syntax.
The essential process is:
Understand problem
↓
Break problem into parts
↓
Design algorithm
↓
Represent algorithm in code
↓
Compile
↓
Test
↓
Debug
↓
Improve
35.1 Linear Search
int find(int numbers[], int count, int target)
{
for (int i = 0; i < count; i++)
{
if (numbers[i] == target)
return i;
}
return -1;
}
Linear search has approximately:
O(n)
time complexity.
35.2 Binary Search
Binary search requires sorted data.
Its typical time complexity is:
O(log n)
This is substantially faster than linear search for large sorted collections.
35.3 Sorting
Important introductory sorting algorithms include:
- Bubble sort
- Selection sort
- Insertion sort
More advanced algorithms include:
- Merge sort
- Quicksort
- Heap sort
Understanding algorithms is more important than memorizing implementations.
36. Data Structures in C
36.1 Linked List
A simple linked-list node:
struct Node
{
int value;
struct Node *next;
};
Conceptually:
+-------+-------+ +-------+-------+
| value | next | --> | value | next | --> NULL
+-------+-------+ +-------+-------+
36.2 Stack
A stack follows:
LIFO
Last In, First Out
Operations:
push
pop
peek
36.3 Queue
A queue generally follows:
FIFO
First In, First Out
Operations include:
enqueue
dequeue
36.4 Trees
A tree consists of nodes connected in a hierarchical structure.
Binary trees are particularly important in computer science.
36.5 Hash Tables
Hash tables associate keys with values using a hash function.
They can provide efficient average-case lookup.
36.6 Graphs
Graphs consist of:
They are useful for modeling:
- Networks
- Roads
- Relationships
- Dependencies
37. Compilation and Build Systems
Consider:
program.c
|
v
Preprocessor
|
v
Expanded source
|
v
Compiler
|
v
Assembly
|
v
Assembler
|
v
program.o
|
v
Linker + libraries
|
v
Executable
This distinction is useful when debugging build problems.
37.1 Make
A simple Makefile:
CC = gcc
CFLAGS = -Wall -Wextra -Wpedantic -std=c17
program: main.o calculator.o
$(CC) $(CFLAGS) main.o calculator.o -o program
main.o: main.c calculator.h
$(CC) $(CFLAGS) -c main.c
calculator.o: calculator.c calculator.h
$(CC) $(CFLAGS) -c calculator.c
clean:
rm -f program *.o
Then:
make
and:
make clean
38. Testing C Programs
Testing asks whether software behaves correctly under expected and unexpected conditions.
Test:
- Normal input
- Empty input
- Very large input
- Very small input
- Boundary values
- Invalid input
- Missing files
- Allocation failures
- Unexpected states
For example, if a function accepts:
1–100
test:
0
1
2
99
100
101
Boundary testing is particularly important.
39. Practical Projects
Project 1: Hello World
Objective
Learn:
- Compilation
main()
printf()
- Return values
Project 2: Calculator
Requirements:
- Read two numbers.
- Read an operator.
- Perform arithmetic.
- Detect division by zero.
- Display the result.
Example:
Enter first number: 20
Enter operator: +
Enter second number: 5
Result: 25
Possible extensions:
- Modulus
- Exponentiation
- Multiple calculations
- Command-line arguments
Project 3: Temperature Converter
Support:
Celsius → Fahrenheit
Fahrenheit → Celsius
Formula:
F = C × 9/5 + 32
Project 4: Number Guessing Game
The computer chooses a number.
The user repeatedly guesses.
The program reports:
Too high
Too low
Correct
Concepts:
- Loops
- Conditional statements
- Random numbers
- Input
- Counters
Project 5: Student Grade Calculator
Store:
- Student name
- Assignment marks
- Examination marks
- Total
- Average
- Grade
This introduces structures.
Project 6: Text Statistics Analyzer
Read a text file and calculate:
- Number of characters
- Number of words
- Number of lines
- Number of digits
- Number of alphabetic characters
This combines:
- Strings
- Files
- Loops
- Functions
- Character classification
Project 7: Contact Manager
Implement:
- Add contact
- Search contact
- Edit contact
- Delete contact
- List contacts
- Save contacts
- Load contacts
Concepts:
- Structures
- Arrays
- Functions
- Strings
- File handling
Project 8: To-Do List
Implement:
Add task
List tasks
Complete task
Delete task
Save tasks
Load tasks
Project 9: Tic-Tac-Toe
Use:
- Two-dimensional arrays
- Functions
- Loops
- Input validation
- Game state
Project 10: File-Based Database
Create a small command-line database using structures and binary or text files.
Features:
Add record
Find record
Update record
Delete record
List records
Save records
This is an excellent introductory systems-programming project.
40. University-Level Practice
Beginner Questions
Question 1
What is the purpose of main()?
Question 2
What is the difference between:
=
and:
==
Question 3
What does this produce?
printf("%d\n", 10 / 3);
Question 4
Why does array indexing begin at zero?
Question 5
What does sizeof measure?
Code Tracing
Determine the output:
#include <stdio.h>
int main(void)
{
int x = 5;
if (x > 3)
printf("A\n");
else
printf("B\n");
return 0;
}
Pointer Exercise
What is the output?
#include <stdio.h>
int main(void)
{
int x = 10;
int *p = &x;
*p = 20;
printf("%d\n", x);
return 0;
}
Debugging Exercise
Find the problem:
int numbers[3];
numbers[3] = 100;
The valid indexes are:
0
1
2
Therefore numbers[3] is outside the array.
Programming Exercise
Write a program that:
- Reads ten integers.
- Calculates the sum.
- Calculates the average.
- Finds the largest value.
- Finds the smallest value.
- Counts even values.
- Counts odd values.
41. Final Review
After completing an introductory C course, a student should understand:
- C syntax
- Variables
- Data types
- Expressions
- Operators
- Input/output
- Conditional statements
- Loops
- Functions
- Arrays
- Strings
- Pointers
- Structures
- Enumerations
- Unions
- Dynamic memory
- File handling
- Error handling
- Preprocessor directives
- Header files
- Modular programming
- Compilation
- Linking
- Debugging
- Basic algorithms
- Basic data structures
- C security principles
- Undefined behavior
- Portability
The most important transition is from merely knowing syntax to understanding how C programs interact with memory and the underlying machine.
42. Final Assessment
Multiple Choice
Question 1
Which function is the conventional entry point of a C program?
A. start()
B. main()
C. begin()
D. run()
Answer:
B
Question 2
Which operator obtains the address of an object?
A. *
B. #
C. &
D. @
Answer:
C
Question 3
Which function allocates dynamic memory?
A. alloc()
B. malloc()
C. new()
D. memory()
Answer:
B
Short-Answer Questions
- Explain the difference between an array and a pointer.
- Explain pass-by-value in C.
- What is undefined behavior?
- Why should dynamically allocated memory be released?
- What is the purpose of a header file?
- Explain the difference between
struct and union.
- Explain the difference between stack and heap memory.
- What does
static mean in different contexts?
- What is a dangling pointer?
- Why should compiler warnings be enabled?
Programming Assessment
Write a command-line student-management application that supports:
Add student
List students
Search student
Update student
Delete student
Calculate average
Save to file
Load from file
Exit
Requirements:
- Use structures.
- Use functions.
- Use arrays or dynamic memory.
- Validate input.
- Handle file errors.
- Avoid memory leaks.
- Use multiple source files.
43. Capstone Project
Student Management System
Build a complete command-line student management system.
Student structure
A student could contain:
typedef struct
{
int id;
char name[100];
double marks[5];
double average;
} Student;
Required features
1. Add student
2. View students
3. Search student
4. Edit student
5. Delete student
6. Calculate average
7. Determine grade
8. Save database
9. Load database
10. Exit
Suggested architecture
student.h
student.c
database.h
database.c
main.c
Makefile
This project should demonstrate:
- Variables
- Arrays
- Strings
- Functions
- Structures
- Pointers
- Dynamic memory
- File I/O
- Error handling
- Modular programming
- Compilation
- Testing
A strong implementation should also separate the program's user interface from its data-management logic.
44. What to Learn Next
After mastering introductory C, students can progress into:
Advanced C
Study:
- Advanced pointer techniques
- Function pointers
- Variadic functions
- Advanced memory management
- Concurrency
- Atomics
- Low-level optimization
Data Structures and Algorithms
Study:
- Linked lists
- Trees
- Graphs
- Hash tables
- Sorting
- Searching
- Complexity analysis
Operating Systems
Study:
- Processes
- Threads
- Virtual memory
- System calls
- Scheduling
- File systems
- Device drivers
Computer Architecture
Study:
- CPU architecture
- Registers
- Instruction sets
- Caches
- Memory hierarchy
- Assembly language
Embedded Systems
Study:
- Microcontrollers
- GPIO
- Timers
- Interrupts
- Serial communication
- Hardware registers
Other Languages
C provides an excellent foundation for studying:
- C++
- Rust
- Go
- Objective-C
- Java
- Python internals
45. C Quick Reference
Basic Program
#include <stdio.h>
int main(void)
{
printf("Hello\n");
return 0;
}
Variable Declaration
int age = 20;
double price = 10.50;
char letter = 'A';
Condition
if (x > 10)
{
printf("Large\n");
}
Loop
for (int i = 0; i < 10; i++)
{
printf("%d\n", i);
}
Function
int add(int a, int b)
{
return a + b;
}
Array
int numbers[5] = {1, 2, 3, 4, 5};
Pointer
int x = 10;
int *p = &x;
Dynamic Memory
int *p = malloc(sizeof *p);
if (p != NULL)
{
*p = 10;
free(p);
}
Structure
struct Person
{
char name[100];
int age;
};
File
FILE *file = fopen("data.txt", "r");
if (file != NULL)
{
fclose(file);
}
46. Glossary
Algorithm — A defined procedure for solving a problem.
Array — A contiguous sequence of objects of the same type.
Argument — A value supplied to a function when it is called.
Compiler — Software that translates source code into another representation, typically involving machine or object code.
Declaration — Introduces an identifier and describes its type or other properties.
Definition — Provides the entity itself, such as the storage for an object or the body of a function.
Dereference — Accessing the object designated by a pointer.
Dynamic memory — Memory obtained during program execution, commonly through malloc, calloc, or realloc.
Expression — A combination of operands and operators that produces a value or otherwise participates in evaluation.
Function — A reusable block of program logic.
Heap — A commonly used term for dynamically allocated storage.
Identifier — A name used to identify a program entity.
Linker — Combines object files and libraries into a final executable or other linked output.
Memory leak — Allocated memory that is no longer reachable and therefore cannot be released.
Null character — The character '\0' used to terminate C strings.
Pointer — An object whose value represents the address of another object or function.
Preprocessor — The translation phase that processes directives such as #include and #define.
Recursion — A technique in which a function calls itself.
Scope — The region of program text in which an identifier can be referred to.
Source code — Human-readable program text written in a programming language.
Stack — A commonly used term for storage associated with automatic objects and function-call state.
Statement — A language construct representing an action or control operation.
String — In C, a contiguous sequence of characters terminated by a null character.
Structure — A user-defined type containing multiple members.
Undefined behavior — Behavior for which the C standard imposes no requirements.
Variable — An object whose stored value can change during execution.
47. Hashtags
#cprogramming #clanguage #learnc #programming #computerscience #softwaredevelopment #programmingcourse #cprogrammingtutorial #learnprogramming #coding #codingtutorial #universityprogramming #collegeprogramming #computersciencestudents #systemsprogramming #softwareengineering #algorithms #datastructures #pointers #memorymanagement #programmingfundamentals #swiftener
Key Takeaways
C is more than a language for writing small console programs. It provides a foundation for understanding how software interacts with memory, processors, operating systems, files, and hardware.
The most important concepts to master are:
Variables
↓
Control Flow
↓
Functions
↓
Arrays and Strings
↓
Pointers
↓
Dynamic Memory
↓
Structures
↓
Files
↓
Modular Programs
↓
Algorithms and Data Structures
↓
Systems Programming
A student who understands these concepts thoroughly has acquired a strong foundation for further study in computer science, software engineering, operating systems, embedded systems, computer architecture, and other areas of computing.