Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Programming for All: C Edition for Python Programmers

Table of contents

  1. Introduction

  2. Building a Simple Program

  3. Core Concepts

  4. Advanced Topics

  5. C Libraries

  6. Comparison of Python and C

  7. Practical Applications

  8. Appendices

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
  1. Performance: C programs are often faster and more efficient than programs written in interpreted languages like Python.
  2. Low-level access: you can work directly with hardware and memory, which gives you more control over system resources.
  3. 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.
  4. 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:

  1. Save the code in a file named hello.c.
  2. Open a terminal and go to the directory that contains hello.c.
  3. Compile the program with GCC (the GNU Compiler Collection):
    gcc hello.c -o hello
    
  4. Run the compiled program:
    ./hello
    

Explanation:

  • #include <stdio.h> includes the standard input/output library, which you need in order to use the printf function.
  • 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 variable x and initializes it to 10.
  • %d is 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 if statement 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) called name and initializes it with “Alice”.
  • %s is the format specifier for a string.
  • \n is 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:

  • printf can 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 type
  • float: Single-precision floating-point type
  • double: Double-precision floating-point type
  • char: Single character type
  • void: 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, and not.
  • 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
  • if
  • else if (elif in 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 elif and C uses else if.
3.3.2 Loops
  • for
  • while
  • do-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 for loop iterates over sequences. C’s for loop is more general: you write the initialization, condition, and increment statements yourself.
  • C also has a do-while loop, 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 def keyword 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 pointer ptr that stores the address of x.
  • *ptr dereferences 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:

  • malloc allocates a block of memory of the size you ask for and returns a pointer to it.
  • free releases memory that malloc allocated 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 Point defines a structure named Point.
  • p1 is an instance of Point.
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 Weekday defines an enumerated type.
  • today is a variable of type enum 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:

  • fopen opens a file. Mode "w" is for writing and mode "r" is for reading.
  • fprintf writes formatted output to a file.
  • fgets reads a line from a file.
  • fclose closes 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:

  • fwrite writes binary data to a file.
  • fread reads 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(&current_time);

    printf("Current time: %s", ctime(&current_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:

  1. Create a file named myheader.h.
  2. 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 greet function 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:

  1. Create two source files, file1.c and file2.c.

file1.c:

#include <stdio.h>

void function1() {
    printf("This is function1 from file1.\n");
}

file2.c:

void function1();

int main() {
    function1();
    return 0;
}
  1. 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.c compiles file1.c into an object file file1.o.
  • gcc -c file2.c compiles file2.c into an object file file2.o.
  • gcc file1.o file2.o -o myprogram links the object files into an executable myprogram.
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 = gcc sets the compiler to gcc.
  • CFLAGS = -Wall turns on most of the common compiler warnings. Despite the name it doesn’t enable every warning; -Wextra adds more.
  • TARGET = myprogram sets the name of the executable.
  • SRCS = file1.c file2.c lists 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: %.c compiles each source file into an object file.
  • clean: removes the object files and executable.

Building with Make:

make
./myprogram
make clean

Explanation:

  • make compiles and links the program.
  • ./myprogram runs the executable.
  • make clean removes 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 realloc can’t resize the block, it returns NULL and leaves the original block allocated. That’s why the result goes into a separate pointer, resized. Writing numbers = realloc(numbers, ...) would overwrite your only pointer to the old block with NULL on 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-while loop 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(), and write().
  • These functions return -1 when they fail, so the checks test for a negative result. A return value of 0 from socket() 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.
  • ch is an int, not a char, because fgetc returns an int: either a character value or EOF, which is a separate negative value. Stored in a char, a real byte could compare equal to EOF and end the copy early, or, where char is unsigned, the loop would never see EOF at 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:

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, and realloc.
  • 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.