Programming for All: C Edition for Python Programmers
Table of contents
Getting Started
To get started with the book, simply click on the links above to access each chapter. You can read the chapters directly on GitHub or clone the repository to your local machine to read offline.
Contributing
If you find any errors or have suggestions for improvement, feel free to open an issue or submit a pull request. Contributions are welcome and encouraged!
License
This book is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. You are free to share and adapt the content for non-commercial purposes, as long as you give appropriate credit and share your adaptations under the same license.
Acknowledgments
Special thanks to all contributors who have helped make this book possible.
Introduction
Chapter 1: Introduction
1.1 Welcome
Welcome to “Programming for All: C Edition for Python Programmers”. This book helps you, a Python programmer, make the move to C. Along the way you’ll find comparisons between the two languages, practical examples, and detailed explanations of the core concepts.
1.2 Why learn C?
C is an efficient system programming language, and it has influenced many modern languages, Python among them. Learning C gives you a clearer picture of how computers work.
Benefits of learning C
- Performance: C programs are often faster and more efficient than programs written in interpreted languages like Python.
- Low-level access: you can work directly with hardware and memory, which gives you more control over system resources.
- Foundation for other languages: many languages, such as C++ and Java, are based on or influenced by C, so knowing C makes them easier to learn.
- Industry relevance: C is widely used in system programming, embedded systems, and high-performance applications.
1.3 History of C
Dennis Ritchie developed C at Bell Labs in the early 1970s. He designed it as a small, efficient language for writing operating systems, and it soon became the usual choice for system programming. The UNIX operating system, first written in assembly language, was rewritten in C, and this contributed a great deal to its success.
Milestones:
- 1972: Dennis Ritchie develops C at Bell Labs.
- 1978: Brian Kernighan and Dennis Ritchie publish “The C Programming Language”, the source of the name K&R C.
- 1989: The ANSI C standard (ANSI X3.159-1989) is established.
- 1999: The C99 standard adds features such as inline functions, variable-length arrays, and improved support for IEEE floating-point arithmetic.
- 2011: The C11 standard adds features like multi-threading and atomic operations.
1.4 How to use this book
The book introduces C gradually and draws on what you already know about Python. Each chapter builds on the one before, starting with the fundamentals and moving on to advanced topics and practical applications.
The rest of the book is laid out like this:
- Chapter 2, Building a Simple Program, shows how to write and compile a C program and how a compiler differs from an interpreter.
- Chapter 3, Core Concepts, covers data types, variables, operators, control structures, and functions, comparing each with Python.
- Chapter 4, Advanced Topics, moves on to pointers, memory management, structs, enums, and file I/O.
- Chapter 5, C Libraries, explains how to use standard library functions, write header files, and work with linkers and makefiles.
- Chapter 6, Comparison of Python and C, sets out the main differences between the languages and the common pitfalls when you move from Python to C.
- Chapter 7, Practical Applications, puts your C knowledge to work on a simple game, a small web server, and system utilities.
- The appendices hold reference sheets, further reading, and a glossary of terms.
Each chapter has hands-on examples to help you understand and apply the concepts, along with sections that compare Python and C syntax and behavior to make the move between them smoother. The appendices list further reading for when you want to go deeper.
Building a Simple Program
Chapter 2: Building a Simple Program
2.1 Compilers vs. interpreters
One major difference between C and Python is how programs run. Python is an interpreted language: the code is executed line by line. C is a compiled language. A compiler first translates the code into machine code, and then the machine code is executed.
Example
Python:
print("Hello, World!")
To run it:
python my_program.py
C:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
To compile and run it:
gcc my_program.c -o my_program
./my_program
2.2 Writing and compiling your first C program
Here is a simple C program that prints “Hello, World!”:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
To compile and run it:
- Save the code in a file named
hello.c. - Open a terminal and go to the directory that contains
hello.c. - Compile the program with GCC (the GNU Compiler Collection):
gcc hello.c -o hello - Run the compiled program:
./hello
Explanation:
#include <stdio.h>includes the standard input/output library, which you need in order to use theprintffunction.int main() { ... }is the main function. Program execution starts here.printf("Hello, World!\n");prints “Hello, World!” followed by a newline character.return 0;indicates that the program ended successfully.
2.3 Variable declarations
In C, you must declare the type of each variable before you use it. Python doesn’t ask for this, because its variables are dynamically typed.
Python:
x = 10
C:
int x = 10;
Older C (the C89 standard) required variables to be declared at the beginning of a block. Since C99 you can declare a variable anywhere before its first use, but many C programmers still put declarations at the top of a function.
Example:
#include <stdio.h>
int main() {
int x = 10;
printf("x is %d\n", x);
return 0;
}
Explanation:
int x = 10;declares an integer variablexand initializes it to 10.%dis the format specifier for printing an integer value.
2.4 Whitespace and formatting
In Python, whitespace is significant. In C it isn’t: statements end with semicolons, and blocks of code are enclosed in braces {}.
Python:
if x > 0:
print("Positive")
else:
print("Non-positive")
C:
#include <stdio.h>
int main() {
int x = 10;
if (x > 0) {
printf("Positive\n");
} else {
printf("Non-positive\n");
}
return 0;
}
Explanation:
- In C, the
ifstatement needs parentheses around the condition. Braces{}mark the block of code that runs when the condition is true, and the block that runs when it is false. - If a branch is a single statement, the braces are optional:
if (x > 0) printf("Positive\n");is valid C. Writing them anyway is a common habit, because it avoids bugs when you later add a second line to the branch.
2.5 Basic input and output with printf
C uses the printf function for output. It produces formatted output, much like Python’s print.
Python:
name = "Alice"
print(f"Hello, {name}!")
C:
#include <stdio.h>
int main() {
char name[] = "Alice";
printf("Hello, %s!\n", name);
return 0;
}
Explanation:
char name[] = "Alice";declares a character array (a string) callednameand initializes it with “Alice”.%sis the format specifier for a string.\nis a newline character.
Two more examples with printf follow.
Example 1: Printing integers and floats
#include <stdio.h>
int main() {
int a = 5;
float b = 4.5;
printf("Integer: %d\n", a);
printf("Float: %.2f\n", b);
return 0;
}
Explanation:
%d: Prints an integer.%.2f: Prints a floating-point number with two decimal places.
Example 2: Printing multiple variables
#include <stdio.h>
int main() {
int num_sol = 2;
float sol0 = 4.0;
float sol1 = 1.0;
printf("# solns: %d, solns: %.1f, %.1f\n", num_sol, sol0, sol1);
return 0;
}
Explanation:
printfcan print several variables at once: list them after the format string.
Core Concepts
Chapter 3: Core Concepts
3.1 Data types and variables
In C, every variable has a defined type, and you have to declare it explicitly. Python variables are dynamically typed and can change type while the program runs.
Common data types in C
int: Integer typefloat: Single-precision floating-point typedouble: Double-precision floating-point typechar: Single character typevoid: Represents the absence of type
Python:
x = 10 # Integer
y = 20.5 # Float
name = "Alice" # String
C:
#include <stdio.h>
int main() {
int x = 10; // Integer
float y = 20.5; // Float
double z = 30.15; // Double
char ch = 'A'; // Character
printf("Integer: %d\n", x);
printf("Float: %.2f\n", y);
printf("Double: %.2lf\n", z);
printf("Character: %c\n", ch);
return 0;
}
Explanation:
- In Python, you create a variable by assigning a value to it, and the type is inferred at runtime.
- In C, you declare a variable with a specific type before you use it, and the type is fixed at compile time.
3.2 Operators and expressions
Like Python, C has a large set of operators for working with variables, though some of them behave differently.
3.2.1 Arithmetic operators
+: Addition-: Subtraction*: Multiplication/: Division%: Modulus (remainder)
Python:
a = 10
b = 3
print(a + b) # 13
print(a - b) # 7
print(a * b) # 30
print(a / b) # 3.3333333333333335
print(a % b) # 1
C:
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("a + b = %d\n", a + b);
printf("a - b = %d\n", a - b);
printf("a * b = %d\n", a * b);
printf("a / b = %d\n", a / b); // Integer division
printf("a %% b = %d\n", a % b); // Modulus
return 0;
}
Explanation:
- In Python,
/performs floating-point division by default. - In C,
/performs integer division when both operands are integers. For floating-point division, at least one operand must be a float or double.
3.2.2 Comparison operators
==: Equal to!=: Not equal to>: Greater than<: Less than>=: Greater than or equal to<=: Less than or equal to
Python:
a = 5
b = 10
print(a == b) # False
print(a != b) # True
print(a > b) # False
print(a < b) # True
print(a >= b) # False
print(a <= b) # True
C:
#include <stdio.h>
int main() {
int a = 5, b = 10;
printf("a == b: %d\n", a == b);
printf("a != b: %d\n", a != b);
printf("a > b: %d\n", a > b);
printf("a < b: %d\n", a < b);
printf("a >= b: %d\n", a >= b);
printf("a <= b: %d\n", a <= b);
return 0;
}
Explanation:
- Python and C use similar comparison operators. In C, a comparison gives 0 (false) or 1 (true), and you can use that result directly in arithmetic expressions.
3.2.3 Logical operators
&&: Logical AND||: Logical OR!: Logical NOT
Python:
a = True
b = False
print(a and b) # False
print(a or b) # True
print(not a) # False
C:
#include <stdio.h>
int main() {
int a = 1, b = 0; // In C, 1 is true and 0 is false
printf("a && b: %d\n", a && b);
printf("a || b: %d\n", a || b);
printf("!a: %d\n", !a);
return 0;
}
Explanation:
- Python’s logical operators are the words
and,or, andnot. - C uses the symbols
&&,||, and!.
3.3 Control structures
Control structures direct the flow of execution in a program. Python and C have similar ones, written with different syntax.
3.3.1 Conditionals
ifelse if(elifin Python)else
Python:
x = 10
if x > 0:
print("Positive")
elif x == 0:
print("Zero")
else:
print("Negative")
C:
#include <stdio.h>
int main() {
int x = 10;
if (x > 0) {
printf("Positive\n");
} else if (x == 0) {
printf("Zero\n");
} else {
printf("Negative\n");
}
return 0;
}
Explanation:
- For additional conditions, Python uses
elifand C useselse if.
3.3.2 Loops
forwhiledo-while(only in C)
Python:
# For loop
for i in range(5):
print(i)
# While loop
i = 0
while i < 5:
print(i)
i += 1
C:
#include <stdio.h>
int main() {
// For loop
for (int i = 0; i < 5; i++) {
printf("i = %d\n", i);
}
// While loop
int i = 0;
while (i < 5) {
printf("i = %d\n", i);
i++;
}
// Do-while loop
i = 0;
do {
printf("i = %d\n", i);
i++;
} while (i < 5);
return 0;
}
Explanation:
- Python’s
forloop iterates over sequences. C’sforloop is more general: you write the initialization, condition, and increment statements yourself. - C also has a
do-whileloop, whose body always runs at least once.
3.4 Functions
3.4.1 Function definition
A function is a reusable block of code that performs a specific task. Python and C both have functions, but the syntax differs, and so do some details of how you use them.
Python:
def add(a, b):
return a + b
result = add(3, 4)
print("Sum:", result)
C:
#include <stdio.h>
// Function declaration
int add(int a, int b);
int main() {
int result = add(3, 4);
printf("Sum: %d\n", result);
return 0;
}
// Function definition
int add(int a, int b) {
return a + b;
}
Explanation:
- In Python, you define a function with the
defkeyword and don’t declare types for its parameters or return value. - In C, a function must be declared before it is used (with a prototype or a definition), and you must specify the types of its parameters and return value.
3.4.2 Function prototypes
A function prototype is a declaration that gives a function’s return type and parameters. Prototypes usually go at the beginning of the file, before the main function.
Python: Function definitions can appear in any order, as long as each function is defined before it is called.
C:
#include <stdio.h>
// Function prototypes
void greet();
int multiply(int a, int b);
int main() {
greet();
int result = multiply(3, 4);
printf("Product: %d\n", result);
return 0;
}
void greet() {
printf("Hello, World!\n");
}
int multiply(int a, int b) {
return a * b;
}
3.4.3 Scope and lifetime of variables
Where you declare a variable in C determines its scope and lifetime.
Python:
global_var = 10 # Global variable
def print_global():
print("Global variable:", global_var)
def main():
local_var = 20 # Local variable
print("Local variable:", local_var)
print_global()
main()
C:
#include <stdio.h>
// Global variable
int globalVar = 10;
void printGlobalVar() {
printf("Global variable: %d\n", globalVar);
}
int main() {
// Local variable
int localVar = 20;
printf("Local variable: %d\n", localVar);
printGlobalVar();
return 0;
}
Both programs print:
Local variable: 20
Global variable: 10
Explanation:
- In Python, a variable defined outside any function is global, and one defined inside a function is local.
- C follows the same rules, but a local variable’s lifetime is limited to the block in which it is defined.
Advanced Topics
Chapter 4: Advanced Topics
4.1 Pointers and memory management
Pointers let you access and change memory directly. They are one of the most powerful features of C, and you need to understand them to program in C effectively.
4.1.1 Understanding pointers
A pointer is a variable that stores the memory address of another variable.
Python:
x = 10
y = x # y references the same value as x
print(x, y) # Output: 10 10
C:
#include <stdio.h>
int main() {
int x = 10;
int *ptr = &x; // ptr stores the address of x
printf("Value of x: %d\n", x);
printf("Address of x: %p\n", &x);
printf("Value of ptr: %p\n", ptr);
printf("Value pointed to by ptr: %d\n", *ptr);
return 0;
}
Explanation:
int *ptr = &x;declares a pointerptrthat stores the address ofx.*ptrdereferences the pointer to access the value stored at the address.
4.1.2 Dynamic memory allocation
Dynamic memory allocation lets you allocate memory while the program runs, with functions such as malloc, calloc, realloc, and free.
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n = 5;
// Allocate memory for an array of 5 integers
arr = (int *)malloc(n * sizeof(int));
// Check if the memory has been successfully allocated by malloc
if (arr == NULL) {
printf("Memory not allocated.\n");
exit(0);
} else {
// Memory has been successfully allocated
for (int i = 0; i < n; ++i) {
arr[i] = i + 1;
}
// Print the elements of the array
printf("The elements of the array are: ");
for (int i = 0; i < n; ++i) {
printf("%d ", arr[i]);
}
}
// Free the allocated memory
free(arr);
return 0;
}
Explanation:
mallocallocates a block of memory of the size you ask for and returns a pointer to it.freereleases memory thatmallocallocated earlier.
4.1.3 Common pointer pitfalls
Used carelessly, pointers can cause several problems:
- A dangling pointer still points to a memory location that has been freed.
- A memory leak happens when allocated memory is never freed.
- A buffer overflow happens when a program writes more data to a buffer than it can hold.
Example of a dangling pointer:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int));
*ptr = 10;
printf("Value: %d\n", *ptr);
free(ptr);
// ptr is now a dangling pointer
// printf("Value after free: %d\n", *ptr); // This would lead to undefined behavior
return 0;
}
4.2 Structs and enums
Structs and enums let you create complex data types in C.
4.2.1 Defining and using structs
A struct is a collection of variables (of different types) under a single name.
Python:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x, p.y) # Output: 1 2
C:
#include <stdio.h>
// Define a struct
struct Point {
int x;
int y;
};
int main() {
struct Point p1;
p1.x = 1;
p1.y = 2;
printf("Point p1: (%d, %d)\n", p1.x, p1.y);
return 0;
}
Explanation:
struct Pointdefines a structure namedPoint.p1is an instance ofPoint.
4.2.2 Enumerated types
An enum gives names to integral constants, which makes a program easier to read and maintain.
Example:
#include <stdio.h>
// Define an enum
enum Weekday { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
int main() {
enum Weekday today;
today = Wednesday;
printf("Day %d\n", today); // Output: Day 3
return 0;
}
Explanation:
enum Weekdaydefines an enumerated type.todayis a variable of typeenum Weekday.
4.3 File I/O
C handles file input and output through file pointers and functions such as fopen, fclose, fprintf, and fscanf.
4.3.1 Reading from and writing to files
Example:
#include <stdio.h>
int main() {
FILE *fptr;
// Writing to a file
fptr = fopen("file.txt", "w");
if (fptr == NULL) {
printf("Error opening file!\n");
return 1;
}
fprintf(fptr, "Hello, World!\n");
fclose(fptr);
// Reading from a file
char buffer[255];
fptr = fopen("file.txt", "r");
if (fptr == NULL) {
printf("Error opening file!\n");
return 1;
}
while (fgets(buffer, 255, fptr)) {
printf("%s", buffer);
}
fclose(fptr);
return 0;
}
Explanation:
fopenopens a file. Mode"w"is for writing and mode"r"is for reading.fprintfwrites formatted output to a file.fgetsreads a line from a file.fclosecloses the file.
4.3.2 Working with binary files
Example:
#include <stdio.h>
int main() {
FILE *fptr;
int num = 1234;
// Writing to a binary file
fptr = fopen("file.bin", "wb");
if (fptr == NULL) {
printf("Error opening file!\n");
return 1;
}
fwrite(&num, sizeof(int), 1, fptr);
fclose(fptr);
// Reading from a binary file
int read_num;
fptr = fopen("file.bin", "rb");
if (fptr == NULL) {
printf("Error opening file!\n");
return 1;
}
fread(&read_num, sizeof(int), 1, fptr);
printf("Read number: %d\n", read_num);
fclose(fptr);
return 0;
}
Explanation:
fwritewrites binary data to a file.freadreads binary data from a file.- Mode
"wb"opens a binary file for writing, and mode"rb"opens one for reading.
C Libraries
Chapter 5: C Libraries
A C library is a collection of precompiled functions and definitions that you can use to add functionality to your programs. To program efficiently in C, you need to know how to use these libraries and how to create your own.
5.1 Standard library functions
The C Standard Library has functions for input/output, string manipulation, mathematical computation, and more. Some of the most commonly used ones follow.
5.1.1 String handling
The functions for handling strings are in the <string.h> library.
Common functions:
strlen: Returns the length of a string.strcpy: Copies one string to another.strcat: Concatenates two strings.strcmp: Compares two strings.
Example:
#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "Hello";
char str2[20] = "World";
char str3[20];
// Copy str1 to str3
strcpy(str3, str1);
printf("strcpy: %s\n", str3);
// Concatenate str1 and str2
strcat(str1, str2);
printf("strcat: %s\n", str1);
// Length of str1
printf("strlen: %zu\n", strlen(str1));
// Compare str1 and str2
int cmp = strcmp(str1, str2);
printf("strcmp: %d\n", cmp);
return 0;
}
5.1.2 Mathematical functions
Mathematical functions are available in the <math.h> library.
Common functions:
sqrt: Computes the square root.pow: Raises a number to a power.sin,cos,tan: Trigonometric functions.fabs: Computes the absolute value of a floating-point number.
Example:
#include <stdio.h>
#include <math.h>
#define PI 3.14159265358979323846
int main() {
double num = 16.0;
printf("Square root of %.2f: %.2f\n", num, sqrt(num));
printf("2 raised to the power 3: %.2f\n", pow(2, 3));
printf("Sine of 45 degrees: %.2f\n", sin(45 * PI / 180)); // Convert degrees to radians
return 0;
}
Standard C has no constant for π. Many systems define M_PI in <math.h>, but that comes from POSIX, not the C standard, so the example defines its own PI. On Linux you also have to link the math library by adding -lm at the end of the command, as in gcc math_example.c -o math_example -lm. On macOS the math functions are part of the standard C library, so -lm is optional there.
5.1.3 Time and date functions
For time and date functions, use the <time.h> library.
Common functions:
time: Returns the current time.ctime: Converts time to a string.difftime: Computes the difference between two times.strftime: Formats date and time.
Example:
#include <stdio.h>
#include <time.h>
int main() {
time_t current_time;
time(¤t_time);
printf("Current time: %s", ctime(¤t_time));
return 0;
}
5.2 Writing and using header files
A header file declares functions and variables so that several source files can share them. Header files usually have a .h extension.
To create a header file:
- Create a file named
myheader.h. - Declare functions and variables in it.
myheader.h:
// Function prototype
void greet();
To use the header file, include it in your source file with #include.
main.c:
#include <stdio.h>
#include "myheader.h"
// Function definition
void greet() {
printf("Hello from the header file!\n");
}
int main() {
greet();
return 0;
}
Explanation:
#include "myheader.h"includes the header file.- The
greetfunction is declared in the header file and defined in the source file.
5.3 Linkers and makefiles
Linkers and makefiles are essential for managing larger C projects. The linker combines several object files into a single executable, and a makefile automates the build process.
5.3.1 Using the linker
When you compile several source files, the linker can combine them into a single executable.
Example:
- Create two source files,
file1.candfile2.c.
file1.c:
#include <stdio.h>
void function1() {
printf("This is function1 from file1.\n");
}
file2.c:
void function1();
int main() {
function1();
return 0;
}
- Compile and link the files:
gcc -c file1.c
gcc -c file2.c
gcc file1.o file2.o -o myprogram
./myprogram
Explanation:
gcc -c file1.ccompilesfile1.cinto an object filefile1.o.gcc -c file2.ccompilesfile2.cinto an object filefile2.o.gcc file1.o file2.o -o myprogramlinks the object files into an executablemyprogram.
5.3.2 Writing a makefile
A makefile automates the build by spelling out how to compile and link the program.
Example Makefile:
# Makefile for building myprogram
# Compiler
CC = gcc
# Compiler flags
CFLAGS = -Wall
# Target executable
TARGET = myprogram
# Source files
SRCS = file1.c file2.c
# Object files
OBJS = $(SRCS:.c=.o)
# Build the target
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)
# Compile source files into object files
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Clean up object files and executable
clean:
rm -f $(OBJS) $(TARGET)
Each command line under a rule (the recipe) must start with a tab character. Make rejects lines indented with spaces, with an error such as missing separator.
Explanation:
CC = gccsets the compiler togcc.CFLAGS = -Wallturns on most of the common compiler warnings. Despite the name it doesn’t enable every warning;-Wextraadds more.TARGET = myprogramsets the name of the executable.SRCS = file1.c file2.clists the source files.OBJS = $(SRCS:.c=.o)converts source file names to object file names.$(TARGET): $(OBJS)defines the target and its dependencies.$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)links the object files to create the executable.%.o: %.ccompiles each source file into an object file.clean:removes the object files and executable.
Building with Make:
make
./myprogram
make clean
Explanation:
makecompiles and links the program../myprogramruns the executable.make cleanremoves the object files and executable.
Comparison of Python and C
Chapter 6: Comparison of Python and C
Moving from Python to C means learning where the two languages differ and where they are alike. This chapter covers the major differences in syntax, data types, and memory management, along with the common pitfalls, so that you can work with C effectively as a Python programmer.
6.1 Syntax differences
Python and C share many programming concepts, but their syntax is quite different.
6.1.1 Indentation vs. braces
Python:
if x > 0:
print("Positive")
else:
print("Non-positive")
C:
#include <stdio.h>
int main() {
int x = 10;
if (x > 0) {
printf("Positive\n");
} else {
printf("Non-positive\n");
}
return 0;
}
Explanation:
- Python marks blocks of code with indentation. C uses braces
{}.
6.1.2 Variable declarations
Python:
x = 10
y = 20.5
name = "Alice"
C:
#include <stdio.h>
int main() {
int x = 10;
float y = 20.5;
char name[] = "Alice";
return 0;
}
Explanation:
- Python uses dynamic typing, so variable types are inferred.
- C requires you to declare each variable explicitly, with its type.
6.2 Data type differences
Python has a variety of high-level data types. C’s types are more primitive, and C requires explicit memory management.
6.2.1 Lists vs. arrays
Python:
numbers = [1, 2, 3, 4, 5]
print(numbers[2]) # Output: 3
C:
#include <stdio.h>
int main() {
int numbers[] = {1, 2, 3, 4, 5};
printf("%d\n", numbers[2]); // Output: 3
return 0;
}
Explanation:
- Python lists can grow dynamically and hold elements of different types.
- C arrays have a fixed size and hold elements of the same type.
6.2.2 Strings
Python:
greeting = "Hello, World!"
print(greeting)
C:
#include <stdio.h>
int main() {
char greeting[] = "Hello, World!";
printf("%s\n", greeting);
return 0;
}
Explanation:
- Python strings are high-level objects.
- C strings are arrays of characters terminated by a null character (
\0).
6.3 Memory management
Python manages memory for you. In C you manage it manually, with functions such as malloc, calloc, realloc, and free.
6.3.1 Dynamic memory allocation
Python:
numbers = [1, 2, 3, 4, 5]
numbers.append(6)
print(numbers) # Output: [1, 2, 3, 4, 5, 6]
C:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *numbers = (int *)malloc(5 * sizeof(int));
if (numbers == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
numbers[i] = i + 1;
}
// Resize the array
int *resized = (int *)realloc(numbers, 6 * sizeof(int));
if (resized == NULL) {
printf("Memory reallocation failed\n");
free(numbers);
return 1;
}
numbers = resized;
numbers[5] = 6;
for (int i = 0; i < 6; i++) {
printf("%d ", numbers[i]);
}
free(numbers); // Free the allocated memory
return 0;
}
Explanation:
- Python manages the list’s memory automatically.
- In C, you allocate and deallocate the memory yourself.
- If
realloccan’t resize the block, it returnsNULLand leaves the original block allocated. That’s why the result goes into a separate pointer,resized. Writingnumbers = realloc(numbers, ...)would overwrite your only pointer to the old block withNULLon failure, and the block could never be freed.
6.4 Common pitfalls
Watch out for these common mistakes when you move from Python to C.
6.4.1 Buffer overflows
C gives you low-level access to memory, and if you aren’t careful, that can lead to buffer overflows.
Example of a buffer overflow:
#include <stdio.h>
#include <string.h>
int main() {
char buffer[10];
strcpy(buffer, "This is a very long string"); // Unsafe, causes buffer overflow
printf("Buffer: %s\n", buffer);
return 0;
}
Solution:
Use a safer function such as strncpy:
#include <stdio.h>
#include <string.h>
int main() {
char buffer[10];
strncpy(buffer, "This is a very long string", sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; // Ensure null-termination
printf("Buffer: %s\n", buffer);
return 0;
}
6.4.2 Dangling pointers
A dangling pointer points to a memory location that has been freed.
Example of a dangling pointer:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int));
*ptr = 10;
free(ptr);
// ptr is now a dangling pointer
// printf("Value after free: %d\n", *ptr); // This would lead to undefined behavior
return 0;
}
Solution:
Set the pointer to NULL after you free it:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int));
*ptr = 10;
free(ptr);
ptr = NULL; // Avoids dangling pointer
return 0;
}
6.4.3 Memory leaks
A memory leak occurs when allocated memory is not freed.
Example of a memory leak:
#include <stdlib.h>
void createLeak() {
int *ptr = (int *)malloc(10 * sizeof(int));
// Memory allocated is not freed
}
int main() {
createLeak();
return 0;
}
Solution: Make sure every block of memory you allocate is freed:
#include <stdlib.h>
void createLeak() {
int *ptr = (int *)malloc(10 * sizeof(int));
// Use the memory
free(ptr); // Free the allocated memory
}
int main() {
createLeak();
return 0;
}
6.5 Best practices in both languages
6.5.1 Code readability
In both languages, use meaningful variable names and write comments. Keep your indentation consistent in Python and your use of braces {} consistent in C.
6.5.2 Error handling
Python handles errors with exceptions. In C, you use return values and error codes.
Python:
try:
file = open('example.txt', 'r')
content = file.read()
print(content)
except IOError:
print("Error opening file")
C:
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}
char buffer[256];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}
fclose(file);
return 0;
}
6.5.3 Memory management
Python has automatic garbage collection. In C, you manage memory by hand with malloc/free.
Python:
data = [1, 2, 3, 4, 5]
# No need to free memory explicitly
C:
#include <stdlib.h>
int main() {
int *data = (int *)malloc(5 * sizeof(int));
if (data == NULL) {
return 1;
}
// Use the memory
free(data); // Free the allocated memory
return 0;
}
Practical Applications
Chapter 7: Practical Applications
In this chapter you apply what you know about C to three projects: writing a simple game, building a small web server, and creating system utilities. Building real programs gives you a better sense of how to use C effectively.
7.1 Writing a simple game: Guess the Number
The first project is a simple number guessing game. The program generates a random number, and the user has to guess it.
Python:
import random
def guess_the_number():
number = random.randint(1, 100)
guess = None
while guess != number:
guess = int(input("Guess the number between 1 and 100: "))
if guess < number:
print("Too low!")
elif guess > number:
print("Too high!")
else:
print("Congratulations! You guessed the number.")
guess_the_number()
C:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int number, guess;
// Initialize random number generator
srand(time(0));
number = rand() % 100 + 1;
printf("Guess the number between 1 and 100: ");
do {
scanf("%d", &guess);
if (guess < number) {
printf("Too low! Try again: ");
} else if (guess > number) {
printf("Too high! Try again: ");
} else {
printf("Congratulations! You guessed the number.\n");
}
} while (guess != number);
return 0;
}
Explanation:
srand(time(0))initializes the random number generator with the current time.- The
rand()function then generates a random number between 1 and 100. - A
do-whileloop keeps asking the user for guesses until they get the number right.
7.2 Building a small web server
Next, you’ll build a simple web server. To keep it short, it answers every request with the same fixed text response instead of serving files, and it uses only basic socket programming.
Python:
import socket
def start_server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 8080))
server_socket.listen(1)
print("Server is running on port 8080...")
while True:
client_socket, client_address = server_socket.accept()
request = client_socket.recv(1024).decode()
print("Request received:")
print(request)
response = 'HTTP/1.0 200 OK\n\nHello, World!'
client_socket.sendall(response.encode())
client_socket.close()
start_server()
C:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
socklen_t addrlen = sizeof(address);
char buffer[1024] = {0};
char *hello = "HTTP/1.0 200 OK\n\nHello, World!";
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// Bind the socket to the port 8080
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server is running on port 8080...\n");
// Accept incoming connection
while (1) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
ssize_t bytes_read = read(new_socket, buffer, sizeof(buffer) - 1);
buffer[bytes_read > 0 ? bytes_read : 0] = '\0';
printf("Request received:\n%s\n", buffer);
write(new_socket, hello, strlen(hello));
close(new_socket);
}
return 0;
}
Explanation:
- The server listens for incoming HTTP requests on port 8080.
- When a request arrives, it sends back a simple “Hello, World!” response.
- It is built from basic socket programming functions such as
socket(),bind(),listen(),accept(), andwrite(). - These functions return
-1when they fail, so the checks test for a negative result. A return value of0fromsocket()is a valid file descriptor. read()doesn’t add a terminating'\0', so the code adds one after the last byte read before printing the buffer as a string.
7.3 Creating system utilities: file copy utility
The last project is a utility that copies the contents of one file to another.
Python:
def copy_file(src, dst):
with open(src, 'r') as f_src:
with open(dst, 'w') as f_dst:
f_dst.write(f_src.read())
copy_file('source.txt', 'destination.txt')
C:
#include <stdio.h>
#include <stdlib.h>
void copyFile(const char *src, const char *dst) {
FILE *f_src = fopen(src, "r");
if (f_src == NULL) {
perror("Error opening source file");
exit(EXIT_FAILURE);
}
FILE *f_dst = fopen(dst, "w");
if (f_dst == NULL) {
perror("Error opening destination file");
fclose(f_src);
exit(EXIT_FAILURE);
}
int ch;
while ((ch = fgetc(f_src)) != EOF) {
fputc(ch, f_dst);
}
fclose(f_src);
fclose(f_dst);
}
int main() {
copyFile("source.txt", "destination.txt");
printf("File copied successfully.\n");
return 0;
}
Explanation:
- The program opens the source file for reading and the destination file for writing.
- It reads the source file character by character and writes each character to the destination file.
chis anint, not achar, becausefgetcreturns anint: either a character value orEOF, which is a separate negative value. Stored in achar, a real byte could compare equal toEOFand end the copy early, or, wherecharis unsigned, the loop would never seeEOFat all.- The program checks whether each file opened successfully and handles the error if it didn’t.
Appendices
Appendices
The appendices collect reference sheets, suggestions for further reading, and a glossary of terms to help you understand and apply C programming concepts.
Appendix A: Reference sheets
A.1 Common C functions
Standard input/output (stdio.h):
printf: Prints formatted output to the standard output.scanf: Reads formatted input from the standard input.fprintf: Prints formatted output to a file.fscanf: Reads formatted input from a file.fopen: Opens a file.fclose: Closes a file.fgets: Reads a string from a file.fputs: Writes a string to a file.
String handling (string.h):
strlen: Returns the length of a string.strcpy: Copies a string.strncpy: Copies a specified number of characters from a string.strcat: Concatenates two strings.strncat: Concatenates a specified number of characters from a string.strcmp: Compares two strings.strncmp: Compares a specified number of characters from two strings.
Memory management (stdlib.h):
malloc: Allocates memory.calloc: Allocates memory and initializes it to zero.realloc: Reallocates memory.free: Frees allocated memory.
Mathematical functions (math.h):
sqrt: Computes the square root.pow: Raises a number to a power.sin,cos,tan: Trigonometric functions.fabs: Computes the absolute value of a floating-point number.
A.2 C syntax cheat sheet
Variable declarations:
int x;
float y;
char z;
Control structures:
// If-Else
if (condition) {
// code
} else if (condition) {
// code
} else {
// code
}
// For Loop
for (initialization; condition; increment) {
// code
}
// While Loop
while (condition) {
// code
}
// Do-While Loop
do {
// code
} while (condition);
Functions:
return_type function_name(parameters) {
// code
return value;
}
Pointers:
int *ptr;
ptr = &variable;
value = *ptr;
Appendix B: Further reading and resources
These resources go deeper into C programming and its advanced topics.
Books:
- “The C Programming Language” by Brian W. Kernighan and Dennis M. Ritchie
- “C Programming: A Modern Approach” by K.N. King
- “C Primer Plus” by Stephen Prata
Online courses:
- Harvard’s CS50: Introduction to Computer Science
- Coursera’s C for Everyone: Programming Fundamentals
Websites:
Appendix C: Glossary of terms
- Array: A collection of elements of the same type stored in contiguous memory locations.
- Buffer overflow: A condition in which data exceeds a buffer’s storage capacity and overwrites adjacent memory.
- Compiler: A program that translates source code into machine code.
- Dangling pointer: A pointer that points to a memory location that has been freed.
- Dynamic memory allocation: Allocating memory storage at runtime, using functions such as
malloc,calloc, andrealloc. - Function prototype: A declaration of a function that specifies the function’s name, return type, and parameters.
- Library: A collection of precompiled functions and definitions that programs can use for added functionality.
- Pointer: A variable that stores the memory address of another variable.
- Struct: A user-defined data type in C that groups related variables of different types.
- Variable: A named storage location in memory that holds a value.