0% found this document useful (0 votes)
14 views791 pages

Practical C A Comprehensive Guide - Rafal Jackiewicz

This document is a comprehensive guide to the C programming language authored by Rafał Jackiewicz, covering its history, syntax, libraries, and practical applications. It includes detailed chapters on basic concepts, advanced programming techniques, safety-critical code development, and optimization strategies, along with practical projects for self-assessment. The book aims to equip both beginners and advanced learners with the knowledge and skills needed to master C programming.

Uploaded by

87jxbp765r
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views791 pages

Practical C A Comprehensive Guide - Rafal Jackiewicz

This document is a comprehensive guide to the C programming language authored by Rafał Jackiewicz, covering its history, syntax, libraries, and practical applications. It includes detailed chapters on basic concepts, advanced programming techniques, safety-critical code development, and optimization strategies, along with practical projects for self-assessment. The book aims to equip both beginners and advanced learners with the knowledge and skills needed to master C programming.

Uploaded by

87jxbp765r
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 791

OceanofPDF.

com
PRACTICAL C
OCEANOFPDF.COM
A COMPREHENSIVE
GUIDE TO THE C
PROGRAMMING
LANGUAGE
by

Rafał Jackiewicz
OceanofPDF.com
Author: Rafał Jackiewicz
email: [email protected]

Cover design: Daria Jackiewicz


email: [email protected]

Copyright © 2023 Rafał Jackiewicz


No part of this book should be reproduced, stored in
retrieval system, transmitted in any form or by any
means, electronics, mechanical, photocopy,
recording, web distribution, or likewise.
OceanofPDF.com
ABOUT AUTHOR
Rafał Jackiewicz, a software development expert with over 15 years of
experience, specializes in biometric security, automation, and innovative
solutions integration. He has held significant roles at Mastercard, Fidelity
Investments, and IDEMIA. As a Computer Software Engineering graduate
and holder of multiple security certifications, Rafał fuses his professional
and personal interests in programming, electronics, and woodworking to
create comprehensive educational resources. His mission is to equip
students to confidently and creatively delve into technology and
craftsmanship.

[email protected]

OceanofPDF.com
ABOUT BOOK
This book is a comprehensive guide on the C programming language,
offering a blend of foundational knowledge and practical applications.
Starting with a brief about the author, the book delves into the history,
importance, and workings of the C compiler, differentiating between source
files and header files. It guides readers through a comprehensive
understanding of static and dynamic libraries and their interaction with
CMake.
The second chapter dives deep into C's basic syntax, data types, operators,
control structures, and key elements such as `size_t`, `typedef`, `sizeof`, and
the `volatile` keyword. This section also provides a detailed explanation of
error handling, structures, enumerations, functions, arrays, strings, and
pointers.
The third chapter stands out due to its focus on practical C programming,
including bit manipulation, list, set, and map data structures, socket
programming, file processing, and concurrency. It covers graphic
programming with OpenGL, audio files, parallel computing with OpenCL
and CUDA, and the comparison between them. It explores data integrity,
privacy with asymmetric encryption, and securing sensitive data. This
section also features interfacing C with assembly language, Java, Python,
shell commands, and specifics about memory location access.
The fourth chapter emphasizes safety-critical code development, presenting
the Power of 10 rules. The fifth chapter elucidates code optimization
techniques, debugging, testing with GDB, and the Check framework for
unit testing, highlighting common pitfalls.
Finally, the book culminates with a chapter providing practical projects and
exercises for self-assessment, including solutions and explanations. This
book serves as an extensive resource for both beginners and advanced
learners aiming to master C programming.

OceanofPDF.com
Dear Readers,

I have put in tremendous effort to ensure that the examples presented in this
book are comprehensive, dependable, and free of errors. Each code snippet,
concept, and implementation has been meticulously reviewed, tested, and
verified for its accuracy and effectiveness.

However, like any human endeavour, this work is not immune to the
inadvertent presence of errors or omissions. If, in the course of your reading
and practical application, you come across any discrepancies,
inconsistencies, or areas that could be improved or clarified, I kindly
request you to bring them to my attention.

Your feedback is not just welcome; it's invaluable. It will serve not only to
rectify any mistakes in future editions of this book but also to enhance the
learning experience for all readers.

Please send your observations, suggestions, and error reports via email to
[email protected]. I appreciate your assistance in making this
book the most accurate and reliable resource possible for the C
programming language.

Thank you for your support and for being an integral part of this ongoing
journey of learning and improvement.

Sincerely,
Rafał Jackiewicz

OceanofPDF.com
CONTENTS AT A GLANCE
About Author
About Book
Chapter 1: Introduction to C Programming
1.1 History and Importance of C
1.2 Understanding the C Compiler
1.3 Source Files and Header Files
1.4 Compilation Switches and Parameters
1.5 C Linkage and Name Mangling
1.6 An In-Depth Look at Static and Dynamic Libraries
1.7 Static and Dynamic Libraries with CMake
Chapter 2: Getting Started with C
2.1 Basic Syntax
2.2 Data Types and Variables
2.3 Operators and Expressions
2.4 Control Structures
2.5 Understanding size_t
2.6 Understanding typedef keyword
2.7 The sizeof Operator
2.8 Understanding the `volatile` Keyword
2.9 The const volatile Combination
2.10 Error Handling in C
2.11 Structures
2.12 Enumerations (enum)
2.13 Functions
2.14 Arrays and Strings
2.15 Pointers
2.16 Understanding Pointer Arithmetic
2.17 Memory Management
2.18 Common C Libraries and Their Functions
2.19 Streams
2.20 Buffer Overflow Management and Stream Redirection
Chapter 3: Practical C
3.1 An Introduction to Numeral Systems
3.2 Bit Manipulation Techniques
3.3 Bitwise operators in certain mathematical operations
3.4 List, Set, and Map
3.5 Measuring Execution Time and Clock Ticks
3.6 Socket Programming: TCP/IP and UDP
3.7 Understanding the HTTP Protocol
3.8 Serial Port Programming
3.9 Establishing Database Connections
3.10 File Processing (I/O Operations)
3.11 Concurrency
3.12 Graphics programming with OpenGL
3.13 Image Operations and Graphic Formats
3.14 Audio Files
3.15 Parallel computing with OpenCL
3.16 Parallel computing with CUDA from NVIDIA
3.17 Cuda vs OpenCL
3.18 Checking integrity of data
3.19 Ensuring Privacy through Asymmetric Encryption
3.20 Secure Communications with SSL and TLS
3.20 Securing Sensitive Data in Memory
3.21 Regular Expressions with Oniguruma Library
3.22 Embedding Assembly Language in C
3.23 Extending Java with User-Created Libraries in C
3.24 Extending Python with User-Created Libraries in C
3.25 Executing Shell Commands in C
3.26 Detecting the Operating System in C
3.27 Accessing Specific Memory Locations in C
Chapter 4: The Power of 10: Rules for Developing Safety-
Critical Code
4.1 Introduction to Safety-Critical Code
4.2 Understanding the Power of 10 Rules
Chapter 5: Optimization Techniques, Debugging and Testing
5.1 Code Optimization Techniques
5.2 Debugging and Testing Your Code with GDB
5.3 Unit testing C code with Check framework
5.4 Common Pitfalls and How to Avoid Them
Chapter 6: Projects and Exercises
6.1 Practical Projects to Apply Your Knowledge
6.2 Exercises for Self-Assessment
6.3 Solutions and Explanations
Index
OceanofPDF.com
TABLE OF CONTENTS
About Author
About Book
Chapter 1: Introduction to C Programming
1.1 History and Importance of C
1.2 Understanding the C Compiler
1.3 Source Files and Header Files
1.4 Compilation Switches and Parameters
1.5 C Linkage and Name Mangling
1.6 An In-Depth Look at Static and Dynamic Libraries
1.6.1 Creating and Using Static Libraries
1.6.2 Using a Static Library
1.6.3 Creating and Using Dynamic Libraries
1.6.4 Using a Dynamic Library
1.7 Static and Dynamic Libraries with CMake
1.7.1 Creating a Static Library with CMake
1.7.2 Using a Static Library with CMake
1.7.3 Creating a Dynamic Library with CMake
1.7.4 Using a Dynamic Library with CMake
Chapter 2: Getting Started with C
2.1 Basic Syntax
2.2 Data Types and Variables
2.2.1 Basic Data Types
2.2.2 Variables
2.2.3 Constants
2.3 Operators and Expressions
2.3.1 Arithmetic Operators
2.3.2 Relational Operators
2.3.3 Logical Operators
2.4 Control Structures
2.4.1 Conditional Statements
2.4.2 Loops
2.4.3 The Ternary Operator
2.5 Understanding size_t
2.6 Understanding typedef keyword
2.7 The sizeof Operator
2.8 Understanding the `volatile` Keyword
2.9 The const volatile Combination
2.10 Error Handling in C
2.11 Structures
2.11.1 Nested Structures
2.11.2 Manipulating Structs in an Array with `memcpy`
2.12 Enumerations (enum)
2.13 Functions
2.14 Arrays and Strings
2.15 Pointers
2.16 Understanding Pointer Arithmetic
2.17 Memory Management
2.18 Common C Libraries and Their Functions
2.18.1 Math Library Functions
2.18.2 Time Library Functions
2.18.3 String Library Functions
2.18.4 Input/Output Functions
2.18.5 Standard Library Functions
2.18.6 CTYPE Library Functions
2.19 Streams
2.20 Buffer Overflow Management and Stream Redirection
Chapter 3: Practical C
3.1 An Introduction to Numeral Systems
3.1.1 Converting Binary Formats
3.1.2 Converting Hexadecimal Formats
3.2 Bit Manipulation Techniques
3.3 Bitwise operators in certain mathematical operations
3.3.1 Multiplication by Powers of Two
3.3.2 Division by powers of two
3.3.3 Addition and subtraction of powers of two
3.4 List, Set, and Map
3.4.1 Usage of List, Set, and Map with GLib
3.5 Measuring Execution Time and Clock Ticks
3.6 Socket Programming: TCP/IP and UDP
3.6.1 TCP/IP Protocol
3.6.1.1 TCP Server
3.6.1.2 TCP Client
3.6.2 UDP Protocol
3.6.2.1 UDP Server
3.6.2.2 UDP Client
3.7 Understanding the HTTP Protocol
3.7.1 Sending HTTP (CRUD) Methods with Sockets
3.7.2 Processing HTTP (CRUD) Methods with Sockets
3.7.3 Sending HTTP (CRUD) Methods using libcurl
3.7.4 Processing HTTP (CRUD) Methods with libevent
3.8 Serial Port Programming
3.8.1 Opening and Configuring a Serial Port
3.8.2 Reading from and Writing to the Serial Port
3.9 Establishing Database Connections
3.9.1 MySQL Database
3.9.2 PostgreSQL Database
3.9.3 SQLite Database
3.9.4 MongoDB Database
3.9.5 Cassandra Database
3.10 File Processing (I/O Operations)
3.10.1 Handling Text Strings in Files
3.10.2 Buffered File Handling
3.10.3 Writing and Reading Structures to/from Files
3.10.4 File Positioning with fseek (simple database in C)
3.10.5 Parsing XML
3.10.6 Parsing JSON
3.10.7 Parsing CSV
3.10.8 Working with ZIP files
3.10.8.1 Decompressing a ZIP File
3.10.8.2 Compressing a ZIP File
3.10.9 Working with PDF files
3.10.9.1 Basic Text Extraction from PDF page
3.10.9.2 Detecting if PDF page is text or image
3.10.9.3 Converting Specific PDF Pages to Images
3.10.9.4 Converting PDF Page to Image using Poppler's C API
3.11 Concurrency
3.11.1 Understanding pthread_create and pthread_join
3.11.2 Passing Parameters to Thread Functions
3.11.3 Locks, Mutexes, Semaphores, and Condition Variables
3.11.4 Avoiding Deadlocks and Race Conditions
3.12 Graphics programming with OpenGL
3.12.1 Drawing Simple Shapes with OpenGL
3.12.2 Creating Simple Animations with OpenGL
3.12.3 Handling Mouse Events in OpenGL
3.13 Image Operations and Graphic Formats
3.13.1 Displaying an Image with the SDL library
3.13.2 Image conversion with Leptonica library
3.13.3 Image Scaling with Leptonica library
3.13.4 Image Rotation with Leptonica library
3.13.5 Image Difference Extraction with Leptonica library
3.13.6 Contrast stretching with Leptonica library
3.13.7 Image Translation with Leptonica library
3.13.8 Image Sharpening with Leptonica library
3.13.9 Image Blurring with Leptonica library
3.13.10 Brightness adjustment with Leptonica library
3.13.11 Converting to grayscale with Leptonica library
3.13.12 Image Thresholding with Leptonica library
3.13.13 Image Blending with Leptonica library
3.13.14 Extracting text from Image with Leptonica and Tesseract
3.13.15 The SVG (Scalable Vector Graphics) Format
3.13.15.1 Creating SVG with Cairo and librsvg library
3.13.16 The Drawing Exchange Format (DXF)
3.13.16.1 Simplified example of DXF file in plain C
3.13.17 The Wavefront OBJ Format
3.13.17.1 Simplified examples of OBJ in plain C
3.13.17.2 Creating OBJ with CGLM library
3.13.18 The STL (Stereolithography) Format
3.13.18.1 Creating a 3D Cube as an STL in plain C
3.13.18.2 Creating a 3D Sphere as STL in plain C
3.14 Audio Files
3.14.1 Converting Audio Files
3.14.1.2 WAV to FLAC with libFLAC library
3.14.1.3 FLAC to WAV with libsndfile library
3.14.1.4 PCM to AAC with FAAC library
3.14.1.5 WAV to MP3 with lame library
3.14.1.6 MP3 to WAV with mpg123 and sndfile library
3.14.1.7 PCM to WAV and WAV to PCM
3.14.1.8 Getting properties of WAV file
3.14.2 Digital Sound Operations
3.14.2.1 Normalization of Audio
3.14.2.2 Amplitude Compression of Audio
3.14.2.3 Audio Reversal
3.14.2.4 Audio Mixing
3.14.2.5 Echo Effect
3.14.2.6 Reverb Effect
3.14.3 Extracting Audio Features with aubio library
3.14.4 Playing Audio with SDL library
3.15 Parallel computing with OpenCL
3.15.1 Enumerating OpenCL Devices
3.15.2 Adding Numbers with OpenCL
3.15.3 Vector Addition with OpenCL
3.15.4 Matrix Multiplication with OpenCL
3.15.5 Monte Carlo Simulation with OpenCL
3.15.6 Complete N-body Simulation with OpenCL
3.15.7 Prime Numbers with OpenCL
3.15.8 Image Processing with OpenCL
3.16 Parallel computing with CUDA from NVIDIA
3.16.1 Adding Numbers using CUDA
3.16.2 Prime Numbers using CUDA
3.16.3 Matrix Multiplication using CUDA
3.16.4 Image Processing using CUDA
3.17 Cuda vs OpenCL
3.18 Checking integrity of data
3.18.1 Cyclic Redundancy Check
3.18.1.1 Cyclic Redundancy Check (cksum) in plain C
3.18.1.2 CRC32 in plain C
3.18.2 Cryptographic Hash Functions
3.18.2.1 MD5 in plain C
3.18.2.2 SHA256 in plain C
3.18.2.3 SHA256 with OpenSSL
3.18.2.4 SHA3-256 with OpenSSL
3.18.2.5 BLAKE2 with B2
3.18.2.6 Whirlpool with OpenSSL
3.18.2.7 RIPEMD-160 with OpenSSL
3.19 Ensuring Privacy through Asymmetric Encryption
3.19.1 Unravelling the Process of Secure Messaging
3.19.2 Introduction to OpenSSL command-line tool
3.19.3 Simplifying Secure Messaging: A Focus on Core Functions in C
3.19.3.1 Keys Generation
3.19.3.1.1 Generating Asymmetric Key pairs
3.19.3.2 Verifying the key pair
3.19.3.2.1 Generating a Symmetric AES Key
3.19.3.3 Encryption
3.19.3.3.1 Symmetric Encryption with AES-256-CBC Cipher
3.19.3.3.2 Asymmetric Encryption of Symmetric Key
3.19.3.4 Decryption
3.19.3.4.1 Decryption of Symmetric Key Using RSA
3.19.3.4.2 Decrypting a Message with AES-256-CBC Cipher
3.19.3.5 Data Transfer
3.20 Secure Communications with SSL and TLS
3.20.1 Secure Handshake Implementation using OpenSSL
3.20.2 Communicating and Processing Server Response with
OpenSSL
3.20.3 Implementing a Secure Server Using OpenSSL
3.20.4 Bidirectional Communication in a Secure Server Using
OpenSSL
3.20 Securing Sensitive Data in Memory
3.21 Regular Expressions with Oniguruma Library
3.22 Embedding Assembly Language in C
3.23 Extending Java with User-Created Libraries in C
3.24 Extending Python with User-Created Libraries in C
3.25 Executing Shell Commands in C
3.26 Detecting the Operating System in C
3.27 Accessing Specific Memory Locations in C
Chapter 4: The Power of 10: Rules for Developing Safety-
Critical Code
4.1 Introduction to Safety-Critical Code
4.2 Understanding the Power of 10 Rules
Chapter 5: Optimization Techniques, Debugging and Testing
5.1 Code Optimization Techniques
5.2 Debugging and Testing Your Code with GDB
5.3 Unit testing C code with Check framework
5.3.1 Setting Up the Check Framework on Linux and Windows
5.3.2 Writing and Running Unit Tests
5.4 Common Pitfalls and How to Avoid Them
Chapter 6: Projects and Exercises
6.1 Practical Projects to Apply Your Knowledge
6.2 Exercises for Self-Assessment
6.3 Solutions and Explanations
Index
OceanofPDF.com
CHAPTER 1:
INTRODUCTION TO C
PROGRAMMING
Welcome to the fascinating world of C programming! This chapter serves as
your gateway into one of the most influential programming languages in the
history of computer science.
C is a high-level, general-purpose programming language that was
developed in the early 1970s at the Bell Labs by Dennis Ritchie. It was
designed for and first implemented on the UNIX operating system. The
language later gave birth to other popular languages such as C++, C#,
Objective-C, and many others. Despite being over five decades old, C
remains relevant and widely used today, testament to its robustness,
efficiency, and versatility.
In this chapter, we will start by exploring the history and importance of C.
Understanding the origins of C and its impact on the field of programming
provides a solid foundation and context for your learning journey.
Next, we will delve into the structure of a C program, discussing the roles
of source files and header files. Source files, typically with the .c extension,
contain the main body of your program, while header files, with the .h
extension, are used to include function declarations, macro definitions, and
other important constructs.
We will also introduce you to the C compiler, a powerful tool that
transforms your human-readable C code into machine code that can be
executed by a computer. Understanding the compilation process is crucial
as it helps you to write more efficient code and to debug your programs
effectively.
Finally, we will discuss compilation switches and parameters. These are
options that you can use to control the behaviour of the compiler. They can
be used to optimize your code, manage warnings and errors, and perform
other important tasks.
By the end of this chapter, you will have a solid understanding of the basics
of C programming and be ready to start writing your own C programs. So,
let's embark on this exciting journey together!

OceanofPDF.com
1.1 HISTORY AND
IMPORTANCE OF C
The C programming language has a rich history that dates back to the early
days of modern computing. Developed in the early 1970s at Bell Labs by
Dennis Ritchie, C was designed for and first implemented as part of the
UNIX operating system. The language was created to make the system
more portable across different machine architectures. This was a
revolutionary concept at the time, as it meant that the entire operating
system could be easily moved to a new machine with minimal changes.
C quickly gained popularity due to its efficiency, flexibility, and powerful
set of features. It allowed programmers to manipulate hardware directly,
which was a major advantage for system programming. At the same time, it
provided high-level constructs that made it more readable and easier to use
than assembly language, the predominant language for system
programming at the time.

The influence of C on the field of computer science cannot be overstated. It


has directly or indirectly shaped many of the programming languages that
came after it. Languages like C++, C#, Objective-C, and many others
borrow syntax, concepts, and features from C. Even today, C remains a
popular language for system programming, embedded systems, and other
areas where direct hardware manipulation is required.
The importance of C extends beyond its technical merits. It has had a
profound impact on the culture of programming. The development of C and
UNIX together, along with their distribution to universities around the
world, helped to foster a spirit of collaboration and sharing in the
programming community. This spirit is embodied in the open-source
movement, which has driven much of the innovation in software
development over the past few decades.
In summary, C is not just a programming language. It's a key part of the
history of computing, a major influence on many modern languages, and a
continuing tool of choice for many programmers. Learning C provides a
solid foundation for understanding the broader landscape of computer
science, and opens the door to many exciting areas of programming.

OceanofPDF.com
1.2 UNDERSTANDING THE
C COMPILER
The C compiler is a powerful tool that plays a crucial role in the process of
transforming your C cod
e into an executable program. Understanding how the compiler works can
help you write more efficient code and debug your programs more
effectively.
At a high level, the job of the C compiler is to take your C source code and
convert it into machine code that can be executed by your computer's
processor. This process involves several stages:
1. Preprocessing:
Preprocessing is the first phase of the compilation process in C. The
preprocessor manipulates the code before the actual compilation starts. It is
governed by directives, which are instructions for the preprocessor. These
directives are not part of the C language per se, but they are essential tools
for programming in C. They begin with a `#` symbol and do not end with a
semicolon. Here are some of the most common preprocessor directives:
1. #include: This directive is used to include the contents of another
file in the current file. It's typically used to include header files, which
contain function prototypes, macro definitions, and other information
that needs to be shared between different parts of the program. For
example, `#include <stdio.h>` includes the standard I/O header file,
which contains functions like `printf` and `scanf`.
2. #define: This directive is used to define a macro, which is a name
that stands for a piece of code. When the preprocessor encounters a
macro name in the code, it replaces the name with the code it stands
for. This can be used to define constants, create shorthand for complex
expressions, or even write code that generates other code. For
example, `#define PI 3.14159` defines a macro named `PI` that stands
for the value `3.14159`.
3. #ifdef, #ifndef, #if, #else, #elif, #endif: These directives are used to
conditionally compile parts of the code. This can be useful for
creating different versions of the program for different platforms,
turning debugging code on and off, or working around differences
between different compilers or versions of the language. For example,
you might have code like this:

C:

#define DEBUG

#ifdef DEBUG
printf("Debugging is on!\n");
#endif

In this example, the `printf` statement will only be included in the


program if `DEBUG` is defined. If you comment out the `#define
DEBUG` line, the `printf` statement will be ignored by the compiler.
The preprocessing stage is a powerful tool in the C programming
language, allowing for code reuse, conditional compilation, and other
features. However, it should be used judiciously, as overuse of
preprocessing can lead to code that is hard to understand and
maintain.
2. Compilation:
Compilation is the process of translating the source code written by a
programmer into a lower-level language that can be understood by the
computer. In the case of C, the compiler translates the preprocessed C code
into assembly language. This process involves several steps:
1. Lexical Analysis: The compiler first breaks down the code into
individual words or tokens. Each token represents a distinct,
indivisible entity in the C language, such as a keyword, an identifier
(like a variable name), a constant, a string literal, or a symbol (like
`+`, `-`, `*`, `/`, etc.).
2. Syntax Analysis (Parsing): The compiler then checks the tokens
against the grammatical rules of the C language. It constructs a parse
tree, a tree-like representation of the code that shows the hierarchical
relationship between different parts of the code. If the code violates
the grammatical rules of C, the compiler will report a syntax error and
stop the compilation process.
3. Semantic Analysis: After the syntax analysis, the compiler checks
the semantics, or meaning, of the code. This includes type checking
(making sure that operations are being performed on compatible
types), checking that variables and functions are declared before
they're used, and so on. If the code violates the semantic rules of C,
the compiler will report an error.
4. Optimization: The compiler then attempts to optimize the code to
make it run faster or take up less space. This can involve a wide range
of techniques, such as eliminating redundant computations, reordering
instructions to make better use of the CPU's execution units, or
replacing slow operations with faster ones. The level of optimization
depends on the options provided to the compiler.

5. Code Generation: Finally, the compiler translates the optimized


code into assembly language. Each C statement is translated into one
or more assembly instructions that perform the equivalent operation.
The output of this stage is an assembly language file.
The compilation process is complex and involves a deep
understanding of both the C language and the architecture of the target
CPU. However, it's also a crucial part of the C programming process,
transforming the human-readable C code into machine-executable
instructions.
3. Assembly:
Assembly is the process of translating the assembly language code
generated by the compiler into machine code, which can be executed
directly by the computer's processor. Assembly language is a low-level
programming language that has a strong correspondence with the machine
instructions of a specific computer architecture. It is specific to a particular
computer architecture, and different types of processors have different
assembly languages.
Each assembly instruction corresponds to a specific operation that the
processor can perform. For example, there might be assembly instructions
for adding two numbers, loading a value from memory, or jumping to a
different part of the program.
The process of translating assembly code into machine code is relatively
straightforward because of the one-to-one correspondence between
assembly instructions and machine instructions. This process is carried out
by a program called an assembler.
Here's a high-level overview of the assembly process:
1. Instruction Translation: The assembler translates each assembly
instruction into the corresponding machine instruction. For example,
an assembly instruction to add two numbers might be translated into a
binary code that tells the processor to perform an addition operation.
2. Address Calculation: The assembler calculates the addresses of
each instruction and data element in the program. These addresses are
used to resolve references to variables and functions in the code.
3. Symbol Resolution: The assembler replaces symbolic references to
memory locations (like variable and function names) with the actual
memory locations that were calculated in the previous step.
4. Object Code Generation: The assembler generates the object
code, which is the binary representation of the program that can be
executed by the processor. This includes the machine instructions as
well as information about the layout of the program in memory.
The output of the assembly process is a machine code file, which can
be loaded into memory and executed by the processor. This file is
often in a format like ELF (Executable and Linkable Format) on
Unix-like systems or PE (Portable Executable) on Windows.
While assembly language is much less human-readable than high-
level languages like C, it provides a level of control and efficiency
that high-level languages can't match. Understanding assembly
language can also be useful for debugging and optimization tasks, as it
allows you to see exactly what your program is doing at the machine
level.
4. Linking:
Linking is the final phase of the compilation process. The linker takes one
or more object files generated by the compiler and combines them into a
single executable program. The linker also includes code from libraries that
the program uses. Libraries are collections of precompiled code that provide
commonly used functionality, such as mathematical functions, file I/O,
network communication, and more.
The main tasks of the linker include:
1. Symbol Resolution: The linker resolves symbols, which are names
of global variables and functions that are used across different source
files. For example, if you define a function in one source file and call
it in another, the linker connects the call to the correct function
definition.
2. Relocation: The linker assigns final memory addresses to each
function and global variable. During the compilation of individual
files, the compiler doesn't know where these items will end up in the
final layout of the program in memory. The linker determines this
layout and adjusts addresses in the code accordingly.
3. Library Linking: The linker includes code from libraries that the
program uses. There are two types of libraries: static and dynamic.
Static libraries are included directly in the executable, while dynamic
libraries (also known as shared libraries or DLLs) are loaded at
runtime. The linker handles the inclusion of static libraries and sets up
the information needed to load dynamic libraries at runtime.
4. Entry Point Setting: The linker sets the entry point of the program,
which is the memory address at which execution will start when the
program is run. For C programs, this is typically the `main` function.
The output of the linking process is an executable file. This file contains
machine code that can be loaded into memory and executed by the
processor, along with information about how to load and run the program,
such as the entry point and the locations of dynamic libraries.
Linking is a complex process that involves a deep understanding of the
structure of programs and the runtime environment. However, it's also a
crucial part of the C programming process, as it allows you to split your
code into multiple source files and use libraries, which are key techniques
for managing complexity in large programs.
Understanding these stages can help you write better C code. For example,
knowing how the preprocessor works can help you use `#include` and
`#define` directives more effectively. Understanding the role of the linker
can help you manage dependencies between different parts of your code.
In addition, many compilers provide options to control the behaviour of
each stage. For example, you can often choose between different levels of
optimization, control how warnings and errors are reported, and specify
which libraries to link against. Understanding these options can give you
more control over the compilation process and help you diagnose problems
when they occur.
In summary, the C compiler is a key part of the C programming toolchain.
By understanding how it works, you can write more efficient code, debug
problems more effectively, and make better use of the features it provides.

OceanofPDF.com
1.3 SOURCE FILES AND
HEADER FILES
In C programming, the code is typically organized into two types of files:
source files and header files. Understanding the roles of these files and how
they interact is crucial for managing larger projects and writing reusable
code.

Source Files
Source files, typically with the `.c` extension, contain the main body of your
program. This includes function definitions, where the actual code for each
function is written, and variable definitions. Each source file is compiled
separately into an object file, which contains machine code that the
processor can execute. The object files are then linked together to create the
final executable program.

Header Files
Header files, typically with the `.h` extension, are used to share function
declarations, macro definitions, and other constructs between different
source files. When you `#include` a header file in a source file, the
preprocessor essentially copies the entire contents of the header file into the
source file.
Function declarations in a header file tell the compiler that a function exists,
even if it is not defined in the current source file. This allows you to call the
function from any source file that includes the header file. The actual
definition of the function, which includes the code that is executed when the
function is called, is typically in a separate source file.
Header files are also used to define macros and constants that you want to
use across multiple source files. For example, you might define a macro
that calculates the area of a circle, or a constant for the value of pi.
Best Practices
In C programming, source files (with the extension `.c`) and header files
(with the extension `.h`) play different roles. Source files contain the actual
code that is compiled into the program, while header files contain
declarations that are shared between different source files. Using these files
correctly is crucial for keeping your code organized, manageable, and free
from errors. Here are some best practices:
1. Code Organization: Only put code in header files that you need to share
between multiple source files. This typically includes function prototypes
and type definitions. Code that is only used in one source file should be
kept in that source file. This helps keep each file focused and easier to
understand.
2. Variable Definitions: Avoid defining variables in header files. If you
define a variable in a header file, and that header file is included in multiple
source files, you will end up with multiple definitions of the same variable,
which is an error in C. If you need to share a variable between multiple
source files, declare it as an `extern` in the header file and define it in one
source file. This tells the compiler that the variable is defined somewhere
else, without creating multiple definitions.
3. Preventing Double Inclusion: Use `#ifndef` and `#define` to prevent
double inclusion of header files. Double inclusion can happen when one
header file includes another header file that it has already included. This can
lead to errors and confusion, as it can result in the same declarations
appearing multiple times. To prevent this, you can use an
`#ifndef`/`#define` guard at the beginning of your header file, like this:

C:

#ifndef HEADER_FILE_H
#define HEADER_FILE_H

// contents of the header file go here

#endif
In this example, `HEADER_FILE_H` is an identifier that you choose. The
first time the header file is included, `HEADER_FILE_H` is not defined, so
the contents of the file are included and `HEADER_FILE_H` is defined. If
the header file is included again, `HEADER_FILE_H` is already defined,
so the contents of the file are skipped.
By following these best practices, you can use source files and header files
effectively to organize your code and prevent errors.

OceanofPDF.com
1.4 COMPILATION
SWITCHES AND
PARAMETERS
When compiling a C program, you can provide the compiler with various
switches and parameters to control its behavior. These options can affect
many aspects of the compilation process, from the level of optimization to
the handling of warnings and errors. Understanding these options can give
you more control over the compilation process and help you write more
efficient and robust code.
Here are some common types of compilation switches and parameters:
1. Optimization Level: Most compilers allow you to specify the level of
optimization they should apply to your code. For example, the `-O` switch
followed by a number in GCC specifies the optimization level, with `-O0`
meaning no optimization and `-O3` meaning the highest level of
optimization.
2. Warning Level: Compilers can often generate warnings about potential
issues in your code, such as unused variables or type mismatches. You can
control the level of warnings with switches like `-Wall` in GCC, which
enables all the standard warnings.
3. Debug Information: If you're debugging your program, you might want
the compiler to include extra information in the executable that can help the
debugger. This can be controlled with switches like `-g` in GCC.
4. Linking Libraries: If your program uses external libraries, you need to
tell the compiler to link them into your program. This is typically done with
the `-l` switch followed by the name of the library.
5. Defining Macros: You can define macros from the command line that
will be available in your code. This is done with the `-D` switch in GCC,
followed by the name of the macro (and its value, if any).
6. Including Directories: If your program includes header files from non-
standard directories, you need to tell the compiler where to find them. This
is done with the `-I` switch in GCC, followed by the path to the directory.
These are just a few examples of the many switches and parameters that can
be used when compiling a C program. The exact options available will
depend on the compiler you are using. To find out more about the options
for your compiler, you can typically use the `-h` or `--help` switch to
display a help message, or consult the compiler's documentation.

OceanofPDF.com
1.5 C LINKAGE AND
NAME MANGLING
To work effectively with C and C++, it's important to understand how these
languages handle function linkage and naming. Two key concepts here are
C linkage and name mangling.

C Linkage
In C++, the `extern` keyword is used to specify a linkage type. `extern "C"`
tells the C++ compiler to use C linkage for the following code. What does
this mean? C++ has more features than C, including function overloading,
namespaces, and classes, which affect how the compiler treats function
names. These features necessitate a different method of linking function
names compared to C, and this is where C linkage comes into play.

When we declare a function with `extern "C"`, we're telling the C++
compiler not to 'mangle' the function name (more on name mangling
below), thereby ensuring that the function's linkable name is the same as its
C-style name. This is particularly important when using a C library in a
C++ program.

Here's an example of how it's used:

C:

extern "C" {
#include "c_library.h"
}
In this case, all the function declarations within the `"c_library.h"` file will
be given C linkage, ensuring the function names are compatible with C-
style linking.

Name Mangling
Name mangling, also known as name decoration, is a technique used by
C++ compilers to support function overloading. This is a feature unique to
C++ among the C family of languages.
In C++, you can have multiple functions with the same name as long as
their parameters differ. The compiler differentiates these functions by
'mangling' their names - appending data about the function parameters to
the function name. When you call an overloaded function, the compiler
selects the correct function by matching the mangled name.
For instance, consider these overloaded functions:

CPP:

void foo(int x);


void foo(double x);

After the C++ compiler mangles these names, they might look something
like this:

CPP:

void _Z3fooi(int x); // mangled name for foo(int)


void _Z3food(double x); // mangled name for foo(double)

Using `extern "C"` to Prevent Name Mangling


By declaring a function with `extern "C"`, you can prevent the C++
compiler from mangling its name, making it compatible with C-style
linkage. This is commonly done when including C libraries in C++ code, as
it ensures the C++ code can correctly link to the C library functions.
However, remember that C does not support function overloading. If you
attempt to use `extern "C"` with overloaded functions, the compiler will
raise an error because it can't give the functions the same C-style name.
Conclusion
Understanding C linkage and name mangling is key to interoperability
between C and C++ code. By effectively using `extern "C"` to manage
linkage types, you can ensure your C++ programs correctly link to C
libraries and other C-style code.

OceanofPDF.com
1.6 AN IN-DEPTH LOOK AT
STATIC AND DYNAMIC
LIBRARIES
In the realm of programming, particularly in C, libraries play a pivotal role
in code reuse, modularity, and efficient memory usage. Libraries are
collections of precompiled pieces of code that have been packaged together
for reuse in different programs. They provide a way to share common code,
data structures, and routines among different programs, thereby reducing
the amount of code that needs to be written and tested. This not only saves
time but also enhances the reliability of the code.

Libraries in C can be broadly categorized into two types: static libraries and
dynamic libraries. Both serve the same purpose of code reuse but differ in
the way they are linked to the program and loaded into memory.
Understanding the differences between these two types of libraries, their
advantages, and their trade-offs is crucial for making informed decisions
about how to structure and distribute your software.

In this chapter, we will delve into the details of static and dynamic libraries.
We will explore how they are created, how they are used, and how they
interact with the programs that use them. We will also discuss the
implications of choosing one type of library over the other, and provide
practical examples to illustrate these concepts.

By the end of this chapter, you will have a solid understanding of both static
and dynamic libraries, and you will be equipped with the knowledge to
utilize them effectively in your C programming endeavors. Whether you are
building a small application or a large system, the knowledge of how to use
libraries can greatly enhance your productivity and the quality of your
software. So, let's embark on this journey to explore the fascinating world
of static and dynamic libraries in C.

OceanofPDF.com
1.6.1 CREATING AND
USING STATIC LIBRARIES
Static libraries, also known as archives, are a collection of object files that
are combined into a single file. When you use a function from a static
library in your program, the object code of that function is copied into the
final executable at compile time. This means that your program has a copy
of the code it needs to run, making it self-contained and able to run without
any additional files.

However, this also means that if the library is updated, you need to
recompile your program with the new version of the library to take
advantage of the changes. Additionally, if multiple programs use the same
library, they each have their own copy of the library code, which can use
more disk space and memory.

Let's walk through the process of creating and using a static library in C.

Creating a Static Library


1. Write the Code: First, you need to write the code that will go into the
library. This is typically a collection of related functions that provide some
useful functionality. For example, you might have a math library that
provides functions for complex mathematical operations. Each function
should be in its own source file.

C:

// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H
double add(double a, double b);
double subtract(double a, double b);

#endif

C:

// mathlib.c
#include "mathlib.h"

double add(double a, double b) {


return a + b;
}

double subtract(double a, double b) {


return a - b;
}

2. Compile the Code: Next, you need to compile each source file into an
object file. You can do this with the `gcc` compiler using the `-c` option,
which tells `gcc` to compile the source file into an object file without
linking.

bash:

gcc -c mathlib.c

This will produce an object file named `mathlib.o`.


3. Create the Library: Once you have all your object files, you can combine
them into a static library using the `ar` command, which stands for
"archiver". The `r` option tells `ar` to insert the object files into the library
(creating it if necessary), and the `s` option tells it to create an index to
speed up access to the library.

bash:
ar rcs libmathlib.a mathlib.o

This will create a static library named `libmathlib.a` that contains the
`mathlib.o` object file.

OceanofPDF.com
1.6.2 USING A STATIC
LIBRARY
Once you have a static library, you can use it in a program like this:

1. Include the Header File: In your source code, include the header file for
the library. This lets the compiler know about the functions in the library.

C:

#include "mathlib.h"

2. Call the Library Functions: You can then call the functions from the
library in your code, just like any other function.

C:
double result = add(1.0, 2.0);

3. Link the Library: When you compile your program, you need to tell the
compiler to link the static library. You do this by adding the library to the
command line when you run `gcc`, using the `-L` option to specify the
directory where the library is located and the `-l` option to specify the name
of the library (without the `lib` prefix or the `.a` extension).

bash:
gcc -L. -lmathlib myprogram.c -o myprogram

This command tells `gcc` to compile `myprogram.c`, link it with


`libmathlib.a` from the current directory (specified by `.`), and output the
resulting executable to `myprogram`.
And that's it! You've created a static library and used it in a program.
Remember that because the library is linked at compile time, any changes to
the library will require recompiling the programs that use it to see the
changes. Despite this, static libraries are a useful tool for code reuse,
especially when you want to distribute a self-contained program that doesn't
depend on external files.

OceanofPDF.com
1.6.3 CREATING AND
USING DYNAMIC
LIBRARIES
Dynamic libraries, also known as shared libraries, are collections of object
files that can be loaded and linked into an application at runtime. Unlike
static libraries, dynamic libraries are not included in the executable file.
Instead, they are separate files that are loaded as needed. This can save
memory when multiple programs use the same library, as they can all share
a single copy of the library code. However, it also means that the library
must be available on the system where the program is run.

Let's walk through the process of creating and using a dynamic library in C.

Creating a Dynamic Library


1. Write the Code: As with static libraries, you first need to write the code
that will go into the library. This is typically a collection of related functions
that provide some useful functionality. Each function should be in its own
source file.

C:

// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H

double add(double a, double b);


double subtract(double a, double b);

#endif
C:

// mathlib.c
#include "mathlib.h"

double add(double a, double b) {


return a + b;
}

double subtract(double a, double b) {


return a - b;
}

2. Compile the Code: Next, you need to compile each source file into an
object file. However, unlike with static libraries, you need to compile the
code with position-independent code (PIC) because the code needs to be
able to run at any memory address. You can do this with the `gcc` compiler
using the `-c` and `-fPIC` options.

bash:

gcc -c -fPIC mathlib.c

This will produce an object file named `mathlib.o`.

3. Create the Library: Once you have all your object files, you can combine
them into a dynamic library using the `gcc` command with the `-shared`
option.

bash:

gcc -shared -o libmathlib.so mathlib.o


This will create a dynamic library named `libmathlib.so` that contains the
`mathlib.o` object file.

OceanofPDF.com
1.6.4 USING A DYNAMIC
LIBRARY
Once you have a dynamic library, you can use it in a program like this:

1. Include the Header File: In your source code, include the header file for
the library. This lets the compiler know about the functions in the library.

C:

#include "mathlib.h"

2. Call the Library Functions: You can then call the functions from the
library in your code, just like any other function.

C:
double result = add(1.0, 2.0);

3. Link the Library: When you compile your program, you need to tell the
compiler to link the dynamic library. You do this by adding the library to
the command line when you run `gcc`, using the `-L` option to specify the
directory where the library is located and the `-l` option to specify the name
of the library (without the `lib` prefix or the `.so` extension).

bash:

gcc -L. -lmathlib myprogram.c -o myprogram


This command tells `gcc` to compile `myprogram.c`, link it with
`libmathlib.so` from the current directory (specified by `.`), and output the
resulting executable to `myprogram`.

At runtime, the dynamic linker will load the library and link it with the
program. The library must be in a directory listed in the
`LD_LIBRARY_PATH` environment variable, or in one of the standard
library directories.

And that's it! You've created a dynamic library and used

it in a program. Remember that because the library is linked at runtime, you


can update the library without recompiling the programs that use it, and the
changes will be picked up the next time the program is run. However, the
library must be available on the system where the program is run, and it
must be compatible with the program. Despite these considerations,
dynamic libraries are a powerful tool for code reuse and memory efficiency.
They are particularly useful for large software systems and for creating
plugins or extensions that can be loaded at runtime.

OceanofPDF.com
1.7 STATIC AND DYNAMIC
LIBRARIES WITH CMAKE
In this chapter, we will be learning how to create both static and dynamic
libraries using CMake and then incorporate these libraries into an
application. Let's dive in.

OceanofPDF.com
1.7.1 CREATING A STATIC
LIBRARY WITH CMAKE
1. Preparation: Start by preparing the source code files you would like to
compile into a static library. As an example, let's assume you have two
source files, `file1.cpp` and `file2.cpp`.
2. CmakeLists.txt: Navigate to the directory containing the source files and
create a `CMakeLists.txt` file. This file is used by CMake to manage the
build process in a compiler-independent manner.
Here is a simple example of what your `CMakeLists.txt` might look like:

cmake:

cmake_minimum_required(VERSION 3.10)
project(MyStaticLibrary)

add_library(my_static_lib STATIC file1.cpp file2.cpp)

This file tells CMake to create a static library named `my_static_lib` from
the source files `file1.cpp` and `file2.cpp`.

3. Build: Now, you can use CMake to build your library. Navigate to the
root directory of your project in a terminal and execute the following
commands:

bash:

mkdir build
cd build
cmake ..
make

You should now have a static library named `libmy_static_lib.a` in your


`build` directory.

OceanofPDF.com
1.7.2 USING A STATIC
LIBRARY WITH CMAKE
1. Preparation: Start by preparing the source files for your application.
Let's assume your application source is in a file named `main.cpp`.
2. CmakeLists.txt: Now, we need to tell CMake to link our application
with our static library. Your `CMakeLists.txt` might look something like
this:

cmake:

cmake_minimum_required(VERSION 3.10)
project(MyApplication)

# Include directories for header files


include_directories(/path/to/library/headers)

# Add the directory with the library


link_directories(/path/to/library/files)

add_executable(my_app main.cpp)

target_link_libraries(my_app my_static_lib)

Here, `target_link_libraries(my_app my_static_lib)` tells CMake to link


`my_app` with `my_static_lib`.

3. Build: Build your application with the following commands:

bash:
mkdir build
cd build
cmake ..
make

Your application, `my_app`, is now built and linked with `my_static_lib`.

OceanofPDF.com
1.7.3 CREATING A
DYNAMIC LIBRARY WITH
CMAKE
The process for creating a dynamic library is almost the same as for a static
library:
1. Preparation: As before, prepare your source files. We'll continue with
`file1.cpp` and `file2.cpp`.
2. CmakeLists.txt: Your `CMakeLists.txt` should look something like this:

cmake:

cmake_minimum_required(VERSION 3.10)
project(MyDynamicLibrary)

add_library(my_dynamic_lib SHARED file1.cpp file2.cpp)

Here, `SHARED` tells CMake to create a dynamic library, instead of a


static one.
3. Build: Build your library with the following commands:

bash:

mkdir build
cd build
cmake ..
make

You should now have a dynamic library named `libmy_dynamic_lib.so` (or


`libmy_dynamic_lib.dll` on Windows) in your `build` directory.

OceanofPDF.com
1.7.4 USING A DYNAMIC
LIBRARY WITH CMAKE
1. Preparation: As before, prepare your application source file(s). We'll
continue with `main.cpp`.
2. CmakeLists.txt: Your `CMakeLists.txt` should look something like this:

cmake:

cmake_minimum_required(VERSION 3.10)
project(MyApplication)

add_executable(my_app main.cpp)

find_library(MY_DYNAMIC_LIB my_dynamic_lib PATHS /path/to/your/library)

if(NOT MY_DYNAMIC_LIB)
message(FATAL_ERROR "my_dynamic_lib not found")
endif()

target_link_libraries(my_app ${MY_DYNAMIC_LIB})

Here, `find_library()` is used to locate the dynamic library. Then


`target_link_libraries(my_app ${MY_DYNAMIC_LIB})` tells CMake to
link `my_app` with `my_dynamic_lib`.

or another approach:
cmake:
cmake_minimum_required(VERSION 3.10)
project(MyApplication)

# Add the directory with the .h files to the include path


include_directories(/path/to/headers)

add_executable(my_app main.cpp)

# Add the directory with the .so or .dll files to the linker path
link_directories(/path/to/library/files)

target_link_libraries(my_app my_dynamic_lib)

3. Build: Build your application with the following commands:

bash:

mkdir build
cd build
cmake ..
make

Your application, `my_app`, is now built and linked with


`my_dynamic_lib`.
That's all there is to creating and using static and dynamic libraries with
CMake. Experiment with these concepts to deepen your understanding.

OceanofPDF.com
CHAPTER 2: GETTING
STARTED WITH C
Now that you have a solid understanding of the history and structure of C
programming, it's time to roll up your sleeves and dive into the practical
aspects of the language. In this chapter, we will guide you through the
fundamental concepts and techniques that form the backbone of C
programming.
We will begin by introducing you to the basic syntax of C. This includes the
structure of a C program, how to declare variables, and how to write
comments. Understanding the syntax is crucial as it forms the rules of the
language, much like grammar in spoken languages.
Next, we will explore data types and variables. In C, every variable has a
type, which determines the size and layout of the variable's memory, the
range of values that it can hold, and the set of operations that can be applied
to it. We will discuss the basic data types provided by C, such as integers,
floating-point numbers, and characters, and show you how to declare and
use variables of these types.
Following that, we will delve into operators and expressions. Operators are
symbols that tell the compiler to perform specific mathematical or logical
manipulations. C has a wide range of operators, including arithmetic
operators, relational operators, and logical operators. We will discuss how
to use these operators to form expressions, which are the building blocks of
C programs.
Finally, we will discuss control structures, which allow you to control the
flow of your program. This includes conditional statements such as if and
switch, and loops such as for and while. Understanding control structures is
key to writing programs that can make decisions and perform repetitive
tasks.
By the end of this chapter, you will have the knowledge and skills to write
simple C programs. You will understand the basic syntax of C, know how to
use various data types and operators, and be able to control the flow of your
program using control structures. So, let's get started with C!

OceanofPDF.com
2.1 BASIC SYNTAX
The syntax of a programming language is the set of rules that define the
combinations of symbols that are considered to be correctly structured
programs in that language. In this section, we will cover the basic syntax of
C programming.

Structure of a C Program
A typical C program consists of one or more functions, one of which must
be named `main`. The `main` function serves as the starting point of the
program. Here's the simplest possible C program:

C:

int main() {
return 0;
}

This program does nothing but return an exit status of 0, which generally
indicates that the program has run successfully.

Comments
Comments are used to explain the code and improve its readability. They
are ignored by the compiler. In C, there are two types of comments:
- Single-line comments, which start with `//`. Everything from the `//` to the
end of the line is a comment.
- Multi-line comments, which start with `/*` and end with `*/`. Everything
in between is a comment.
Here's an example:
C:

// This is a single-line comment

/* This is a
multi-line comment */

Variables and Data Types


In C, every variable has a type, which determines the size and layout of the
variable's memory, the range of values that it can hold, and the set of
operations that can be applied to it. Here's an example of declaring an
integer variable:

C:
int myVariable;

Statements and Blocks


A statement is a simple or compound instruction that can include
expressions and declarations. Each statement must end with a semicolon
(`;`). Here's an example of a statement:

C:
int myVariable = 5;

A block is a group of statements enclosed in curly braces (`{}`), and can be


used wherever a single statement is allowed. Here's an example of a block:

C:

{
​int myVariable = 5;
​myVariable = myVariable + 1;
}

Control Structures
Control structures determine the flow of a program. They include
conditionals like `if` and `switch`, and loops like `for` and `while`. Here's
an example of an `if` statement:

C:

if (myVariable > 5) {
// This block is executed if myVariable is greater than 5
}

This is just a brief overview of the basic syntax of C. As you continue to


learn C, you will encounter more complex syntax and more powerful
features.

OceanofPDF.com
2.2 DATA TYPES AND
VARIABLES
In C programming, every variable has a type, which determines the size and
layout of the variable's memory, the range of values that it can hold, and the
set of operations that can be applied to it. Understanding data types and
how to use them is fundamental to programming in C.

OceanofPDF.com
2.2.1 BASIC DATA TYPES
In C programming, data types are declarations for variables. This
determines the type and size of data associated with variables. The C
language provides several basic data types, including:
1. Integers: In the C language, one of the fundamental types used to
represent numerical data is the integer data type. Integer types are integral
data types that can hold whole numbers, both positive and negative,
excluding decimal or fractional parts. This chapter delves into the
understanding of integer data types in C and outlines their different
variations, including signed char, short, int, long, long long, unsigned char,
unsigned short, unsigned int, unsigned long, unsigned long long, and fixed
width integer types like int8_t, int16_t, int32_t, uint8_t, uint16_t, and
uint32_t.

Signed Integer Types


Signed Char
The 'signed char' data type in C is used to store signed character or small
integers. It has a size of 1 byte, which translates to 8 bits. This means it can
hold values from -128 to 127.

Short
The 'short' data type, or more formally 'short int', is used to store signed
integers. The size of the 'short' data type is 2 bytes, which allows it to store
values from -32,768 to 32,767.

Int
The 'int' data type is the most commonly used data type for storing signed
integers. The size of 'int' usually is 4 bytes, which allows it to store values
from -2,147,483,648 to 2,147,483,647.
Long
The 'long' or 'long int' data type is used for larger integers. The size of a
'long' is typically 4 bytes, allowing it to store values from -2,147,483,648 to
2,147,483,647. Note that on some platforms and compilers, 'long' may have
a size of 8 bytes.

Long Long
The 'long long' or 'long long int' data type is used for storing very large
integers. The size of 'long long' is 8 bytes, allowing it to store values from
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

Unsigned Integer Types


The unsigned integer types only represent non-negative numbers (zero and
positive integers).

Unsigned Char
The 'unsigned char' type is used to store unsigned small integers or
characters. It has a size of 1 byte and can hold values from 0 to 255.

Unsigned Short
The 'unsigned short' or 'unsigned short int' type is used for storing unsigned
small integers. It has a size of 2 bytes, allowing it to store values from 0 to
65,535.

Unsigned Int
The 'unsigned int' type is used for storing unsigned integers. Typically, the
size of 'unsigned int' is 4 bytes, allowing it to store values from 0 to
4,294,967,295.

Unsigned Long
The 'unsigned long' or 'unsigned long int' type is used for larger unsigned
integers. The size of 'unsigned long' is usually 4 bytes, but it can be 8 bytes
on some platforms and compilers. For a size of 4 bytes, it can store values
from 0 to 4,294,967,295.

Unsigned Long Long


The 'unsigned long long' or 'unsigned long long int' type is used for very
large unsigned integers. It has a size of 8 bytes, allowing it to store values
from 0 to 18,446,744,073,709,551,615.

Fixed Width Integer Types


C99 introduced fixed-width integer types. These types have specified
widths, making them platform-independent. They are defined in the
`<stdint.h>` library.

int8_t, int16_t, int32_t


These are signed integer types with widths of 8, 16, and 32 bits
respectively. For example, 'int8_t' can hold values from -128 to 127, while
'int32_t' can store values from -2,147,483,648 to 2,147,483,647.

uint8_t, uint16_t, uint32_t


These are unsigned integer types with widths of 8, 16, and 32 bits
respectively. For example, 'uint8_t' can hold values from 0 to 255, while
'uint32_t' can store values from 0 to 4,294,967,295.
In conclusion, when programming in C, it is essential to understand the
ranges and limitations of each integer type. Using the appropriate integer
type based on your needs can lead to more efficient memory usage and
prevent unexpected behaviours due to integer overflow or underflow.

2. float, double and long double: The C language provides three distinct
data types for representing real numbers (numbers that can have a decimal
fraction), i.e., floating-point numbers. These types are `float`, `double`, and
`long double`.

Float
A `float` type in C is used to represent real numbers, providing significant
digits of about six decimal places. The range of values can be
approximately from 1.2E-38 to 3.4E+38. The exact limits depend on the
implementation, but you can retrieve them in your program using the
`FLT_MIN` and `FLT_MAX` constants from the `<float.h>` library.
The `float` type is a good choice when your program requires fractional
precision, but not extreme accuracy. For example, it is often used in
graphics libraries because the precision provided is sufficient for screen
resolutions and the memory savings are beneficial.

Double
A `double` type in C is a double-precision floating-point type. It provides
roughly double the precision of a `float`, about 15 decimal places. The
range of values can be approximately from 2.3E-308 to 1.7E+308. You can
retrieve the exact limits in your program using the `DBL_MIN` and
`DBL_MAX` constants from the `<float.h>` library.
The `double` type is often the default choice for calculations involving real
numbers as it provides a good balance between precision and performance.
It is used in scientific calculations that require high precision and can
tolerate larger memory usage.

Long Double
A `long double` type in C provides even more precision than a `double`.
The actual size and range of a `long double` can vary between platforms
and compilers, but it is at least as large as `double`. This type is usually
used when extremely high precision is required.
The precision and range of `long double` can also be accessed using the
`LDBL_MIN` and `LDBL_MAX` constants from the `<float.h>` library.
It's important to note that while `long double` offers more precision, it does
come with a cost. The memory requirement is higher and arithmetic
operations can be slower. Therefore, it's typically used in scientific and
mathematical applications where the extra precision is critical.

In conclusion, the choice between `float`, `double`, and `long double`


should be dictated by the specific needs of your program. While a `float`
might be more efficient in terms of memory usage, `double` and `long
double` provide higher precision, which might be necessary for certain
calculations. Understanding the trade-offs between these types is key to
writing efficient and accurate C programs.

3. char: This is a character type, used to represent individual characters.


`char` variables are typically 1 byte in size, and can represent a character
using ASCII encoding. For example, the character 'A' is represented by the
number 65. `char` can be signed or unsigned, affecting its range. An
unsigned `char` type can represent values from 0 to 255, while a signed
`char` can represent values from -128 to 127.
4. void: This is a special type that represents the absence of a type. It is
typically used to indicate that a function does not return a value or does not
take any parameters. For example, a function with a `void` return type does
not return a value.
5. bool: This is a boolean type, used to represent true or false values. In C,
`bool` is not a built-in data type. Instead, it is defined in the `stdbool.h`
header file. A `bool` variable can store either `true` (which is equivalent to
integer 1) or `false` (which is equivalent to integer 0).
These basic data types form the foundation of data storage in C.
Understanding these types and how they work is crucial to writing effective
C code.

OceanofPDF.com
2.2.2 VARIABLES
In C programming, a variable is a named location in memory where a value
can be stored for use by a program. Variables are the basic units of storage
in a program. The value stored in a variable can be changed during program
execution. A variable is only readable and writable piece of memory.
Variables in C must be declared before they can be used. This is done using
a declaration statement, which specifies the type of the variable and its
name. The general syntax for declaring a variable is:

C:
type variable_name;

Here, `type` is one of C's data types, and `variable_name` is any valid
identifier. For example, to declare an integer variable named `myVariable`,
you would write:

C:
int myVariable;

In this case, `int` is the data type (representing an integer), and


`myVariable` is the name of the variable.

You can also initialize a variable at the time of declaration. Initialization


means assigning a value to the variable when you declare it. The syntax for
this is:

C:
type variable_name = value;

For example, to declare an integer variable named `myVariable` and


initialize it with the value 10, you would write:

C:
int myVariable = 10;

In this case, `myVariable` is being initialized with the value `10`. This
means that the memory location named `myVariable` now contains the
integer value `10`.
It's important to note that each variable in C has a specific type, which
determines the size and layout of the variable's memory, the range of values
that can be stored within that memory, and the set of operations that can be
applied to the variable.

OceanofPDF.com
2.2.3 CONSTANTS
In C programming, constants refer to fixed values that do not change during
the execution of a program. Constants can be of any basic data type like
`int`, `char`, `double`, etc., and can be divided into Integer Numerals,
Floating-Point Numerals, Characters, Strings, and Defined Constants.
Defined constants are created using the `#define` preprocessor directive.
This directive tells the C preprocessor to replace instances of the defined
constants with the specified value before the actual compilation process
begins.
The syntax for defining a constant using `#define` is:

C:

#define constant_name value

Here, `constant_name` is the name of the constant you want to define, and
`value` is the value of the constant.

For example, to define a constant that represents the mathematical constant


pi, you could write:

C:

#define PI 3.14159

In this example, `PI` is a constant that represents the value of pi. Anywhere
the preprocessor sees `PI` in the code, it will replace it with `3.14159`. This
means you can use `PI` in your program as if it were a variable that holds
the value `3.14159`, but unlike a variable, you cannot change the value of
`PI`.
Another way to define constants in C is using the `const` keyword. The
`const` keyword allows you to specify that a variable's value is constant and
tells the compiler to prevent the programmer from modifying it.

C:

const double pi = 3.14159;

In this case, `pi` is a constant with the value `3.14159`. Any attempt to
change the value of `pi` later in the program will result in a compile error.
Understanding data types and variables is crucial to writing effective C
code. As you continue to learn C, you will encounter more complex data
types, such as arrays, pointers, and structures.
The `const` keyword can also be used with pointers to create "constant
pointers" and "pointers to constants".

Constant Pointers
A constant pointer is a pointer that cannot change the address it is pointing
to. Here's how you can declare a constant pointer:

C:

int x = 10;
int * const p = &x;

In this example, `p` is a constant pointer to an integer. You can change the
value of `x` through `p`, but you cannot change `p` to point to a different
integer.

Pointers to Constants
A pointer to a constant is a pointer that cannot change the value it is
pointing to. Here's how you can declare a pointer to a constant:

C:

const int x = 10;


const int *p = &x;

In this example, `p` is a pointer to a constant integer. You can change `p` to
point to a different integer, but you cannot change the value of `x` through
`p`.

OceanofPDF.com
2.3 OPERATORS AND
EXPRESSIONS
Operators are symbols that tell the compiler to perform specific
mathematical, relational, or logical manipulations. C has a wide range of
operators, which can be used to form expressions. An expression is a
combination of variables, constants, and operators that yields a value.

OceanofPDF.com
2.3.1 ARITHMETIC
OPERATORS
Arithmetic operators are used to perform mathematical operations like
addition, subtraction, multiplication, and division. The following are the
arithmetic operators in C:
- Addition: `+`
- Subtraction: `-`
- Multiplication: `*`
- Division: `/`
- Modulus: `%` (returns the remainder of a division operation)
For example:

C:

int a = 10;
int b = 20;
int c = a + b; // c is now 30

OceanofPDF.com
2.3.2 RELATIONAL
OPERATORS
Relational operators are used to compare two values. They return `1` if the
comparison is true, and `0` if it is false. The following are the relational
operators in C:
- Equal to: `==`
- Not equal to: `!=`
- Greater than: `>`
- Less than: `<`
- Greater than or equal to: `>=`
- Less than or equal to: `<=`

For example:

C:

int a = 10;
int b = 20;
int result = (a > b); // result is now 0, because 10 is not greater than 20

OceanofPDF.com
2.3.3 LOGICAL
OPERATORS
Logical operators are used to combine two or more conditions. The
following are the logical operators in C:
- Logical AND: `&&` (returns true if both conditions are true)
- Logical OR: `||` (returns true if at least one condition is true)
- Logical NOT: `!` (returns true if the condition is false)
For example:

C:

int a = 10;
int b = 20;
int result = (a == 10) && (b == 20); // result is now 1, because both conditions are true

These are just a few examples of the operators available in C. There are also
bitwise operators, assignment operators, increment and decrement
operators, and others. Understanding how to use these operators is key to
writing complex expressions and algorithms in C.

OceanofPDF.com
2.4 CONTROL
STRUCTURES
Control structures in C programming allow you to control the flow of your
program's execution based on certain conditions or loops. They are essential
for enabling your program to make decisions and perform repetitive tasks.
The main control structures in C are conditional statements (like `if` and
`switch`) and loops (like `for` and `while`).

OceanofPDF.com
2.4.1 CONDITIONAL
STATEMENTS
- `if`: The `if` statement checks a condition and executes a block of code if
the condition is true.

C:

if (condition) {
// code to be executed if condition is true
}

- `if...else`: The `if...else` statement executes one block of code if a


condition is true, and another block of code if the condition is false.

C:

if (condition) {
// code to be executed if condition is true
} else {
// code to be executed if condition is false
}

- `switch`: The `switch` statement allows a variable to be tested for equality


against a list of values. Each value is called a case, and the variable being
switched on can be checked for each case.
C:

switch (variable) {
c​ ase value1:
/​/ code to be executed if variable equals value1
​break;
​case value2:
​// code to be executed if variable equals value2
​break;
​default:
​// code to be executed if variable doesn't match any cases
}

OceanofPDF.com
2.4.2 LOOPS
- `for`: The `for` loop is used when you know in advance how many times
the loop should run.

C:

for (initialization; condition; increment) {


// code to be executed for each iteration
}

- `while`: The `while` loop is used when you want the loop to continue
running as long as a condition is true.

C:

while (condition) {
// code to be executed as long as condition is true
}

- `do...while`: The `do...while` loop is similar to the `while` loop, but the
condition is checked after the loop has run. This means that the loop will
always run at least once.

C:

do {
// code to be executed
} while (condition);
Understanding these control structures is key to writing programs that can
make decisions and perform repetitive tasks. As you continue to learn C,
you will encounter more complex control structures and learn how to use
them to write more advanced programs.

OceanofPDF.com
2.4.3 THE TERNARY
OPERATOR
The ternary operator is a shorthand way of writing an `if-else` statement. It's
called "ternary" because it takes three operands: a condition, a result for
when the condition is true, and a result for when the condition is false.

The syntax for the ternary operator is as follows:

C:
condition ? result_if_true : result_if_false;

The condition is evaluated first. If it's true, then `result_if_true` is returned.


If it's false, then `result_if_false` is returned.

Example 1: Basic Usage


Here's a simple example of how to use the ternary operator:

C:

int a = 10;
int b = 20;

int max = (a > b) ? a : b;

In this example, the condition is `a > b`. If this condition is true, then `a` is
returned and assigned to `max`. If the condition is false, then `b` is returned
and assigned to `max`.

Example 2: Nested Ternary Operator


The ternary operator can also be nested, similar to `if-else` statements.
Here's an example:

C:

int a = 10;
int b = 20;
int c = 30;

int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);

In this example, we're finding the maximum of three numbers. The outer
ternary operator checks if `a > b`. If this is true, then it checks if `a > c` with
another ternary operator. If `a > c` is also true, then `a` is the maximum;
otherwise, `c` is the maximum. If `a > b` is false, then it checks if `b > c`
with another ternary operator. If `b > c` is true, then `b` is the maximum;
otherwise, `c` is the maximum.

While nested ternary operators can be useful in some cases, they can also
make your code harder to read if overused. It's often a good idea to use
regular `if-else` statements for more complex conditions.

Example 3: Ternary Operator with Functions


The ternary operator can also be used with functions. Here's an example:

C:

#include <stdio.h>

void printTrue() {
printf("Condition is true\n");
}

void printFalse() {
printf("Condition is false\n");
}
int main() {
int a = 10;
int b = 20;

(a > b) ? printTrue() : printFalse();

return 0;
}

In this example, the condition is `a > b`. If this is true, then the `printTrue()`
function is called. If it's false, then the `printFalse()` function is called.
The ternary operator is a powerful tool in C that can make your code more
concise. However, it's important to use it judiciously to ensure that your
code remains readable.

OceanofPDF.com
2.5 UNDERSTANDING
SIZE_T
In this chapter, we will explore the `size_t` type in the C programming
language. `size_t` is an unsigned integer type that is used to represent the
size of an object. It is typically used as the return type of the `sizeof`
operator, and for array indexing and loop counting.

What is size_t?
`size_t` is defined in several standard library headers, including
`<stddef.h>`, `<stdio.h>`, `<stdlib.h>`, `<string.h>`, and `<time.h>`. It is
guaranteed to be big enough to contain the size of the biggest object your
system can handle. This is typically the biggest possible array, which is also
the biggest possible memory allocation.
The actual type of `size_t` is platform-dependent; a common mistake is to
assume `size_t` is the same as `unsigned int`, which may not be true on
platforms with a 64-bit `size_t` and a 32-bit `unsigned int`.

Using size_t
Here are some examples of how `size_t` is used in C:
1. With the sizeof operator:

C:

size_t size = sizeof(int);


printf("Size of int: %zu bytes\n", size);
In this example, `sizeof(int)` returns the size of an `int` in bytes. The `%zu`
format specifier is used to print a `size_t` value.

2. For array indexing and loop counting:


C:

int arr[10];
size_t i;

for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {


​arr[i] = (int)i;
}

In this example, `size_t` is used as the loop counter and array index. This is
a common idiom for looping over an array.
3. With standard library functions:
Many standard library functions use `size_t`. For example, the `malloc`
function, which allocates dynamic memory, takes a `size_t` argument:

C:

size_t num_elements = 10;


int* arr = (int*)malloc(num_elements * sizeof(int));

In this example, `malloc` allocates enough memory to hold `num_elements`


integers.

Benefits of size_t
The main benefit of using `size_t` for sizes and counts is that it is
guaranteed to be big enough to represent the size of any object, including
any array. This makes your code more portable, as it will work correctly on
any platform, regardless of the maximum object size.
Furthermore, because `size_t` is an unsigned type, it can represent larger
values than a signed type of the same size. This can be useful if you need to
work with very large sizes or counts.
However, keep in mind that because `size_t` is unsigned, it can cause
problems if you use it in a context where negative numbers are expected.
For example, if you subtract a larger `size_t` value from a smaller one, the
result will wrap around to a very large positive number, which may not be
what you expect.

In conclusion, `size_t` is a versatile and portable type that is ideal for


representing sizes and counts in C. It is widely used in the standard library
and is an important part of writing robust, portable C code.

OceanofPDF.com
2.6 UNDERSTANDING
TYPEDEF KEYWORD
In the C programming language, `typedef` is a keyword that allows you to
create an alias for a data type. This can be particularly useful when working
with complex data types such as structures or function pointers, as it allows
you to simplify your code and make it more readable. In this chapter, we
will explore the `typedef` keyword in detail and provide examples of its
use.

Basic Usage of typedef


The basic syntax of `typedef` is as follows:

C:

typedef existing_type new_type_name;

Here's an example:

C:

typedef int Integer;

In this example, `Integer` is now an alias for the `int` type. This means that
you can use `Integer` anywhere you would normally use `int`:

C:

Integer a = 5;
Integer b = 10;
Integer sum = a + b;

typedef with structures


`typedef` is often used with structures to simplify their declaration. Without
`typedef`, you would need to use the `struct` keyword every time you
declare a variable of that structure type:

C:

struct Point {
int x;
int y;
};

struct Point p1, p2;

With `typedef`, you can create an alias for the structure type and avoid
having to use the `struct` keyword:

C:

typedef struct {
int x;
int y;
} Point;

Point p1, p2;

typedef with Function Pointers


Function pointers in C can have complex syntax, especially when they are
used as parameters or return types. `typedef` can be used to simplify this
syntax. Here's an example:

C:

typedef int (*Comparator)(int, int);

int ascending(int a, int b) {


if (a < b) {
return -1; // a should come before b
} else if (a > b) {
return 1; // a should come after b
} else {
return 0; // a and b are equal
}
}

int descending(int a, int b) {


if (a < b) {
return 1; // a should come after b
} else if (a > b) {
return -1; // a should come before b
} else {
return 0; // a and b are equal
}
}

void sort(int* array, int size, Comparator comp) {


for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
// Use the 'comp' function to compare array[j] and array[j + 1]
if (comp(array[j], array[j + 1]) > 0) {
// Swap array[j] and array[j + 1]
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}

and here you can use those functions like this:

C:

int array[] = {5, 3, 4, 1, 2};


sort(array, 5, ascending); // Sorts the array in ascending order
sort(array, 5, descending); // Sorts the array in descending order

In this example, `Comparator` is a typedef for a function pointer type. The


`sort` function takes a `Comparator` as a parameter, which makes the
function declaration much simpler and easier to read.

Conclusion
In conclusion, `typedef` is a powerful feature of C that allows you to create
aliases for existing data types. This can greatly simplify your code and
make it more readable, especially when working with complex data types
like structures or function pointers. However, like all powerful features, it
should be used judiciously. Overuse of `typedef` can lead to code that is
difficult to understand and maintain, as it can obscure the actual types being
used.

OceanofPDF.com
2.7 THE SIZEOF
OPERATOR
The `sizeof` operator in C is a compile-time unary operator that can be used
to compute the size of its operand, which can be an expression or a data
type qualifier. The `sizeof` operator returns the size of the operand in bytes.
It's a handy tool when you need to know the amount of memory a particular
variable or data type uses, which can be critical in systems with limited
memory or when working with low-level programming.
Here's the basic syntax of the `sizeof` operator:

C:

sizeof(operand)

Let's dive deeper with some examples.


1. Using sizeof with Primitive Data Types:

C:

#include <stdio.h>

int main() {
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of double: %zu bytes\n", sizeof(double));

return 0;
}
In this example, `sizeof` is used to find the size of various primitive data
types in C.

2. Using sizeof with Variables:

C:

#include <stdio.h>

int main() {
int a;
float b;
char c;
double d;

printf("Size of a: %zu bytes\n", sizeof(a));


printf("Size of b: %zu bytes\n", sizeof(b));
printf("Size of c: %zu bytes\n", sizeof(c));
printf("Size of d: %zu bytes\n", sizeof(d));

return 0;
}

In this example, `sizeof` is used to find the size of various variables. Note
that you don't need to use parentheses when the operand is a single variable.

3. Using sizeof with Arrays:

C:

#include <stdio.h>
int main() {
int arr[10];

printf("Size of arr: %zu bytes\n", sizeof(arr));

return 0;
}

In this example, `sizeof` is used to find the size of an array. Note that this
gives the total size of the array, not the number of elements. To find the
number of elements, you could divide the total size by the size of one
element, like this: `sizeof(arr) / sizeof(arr[0])`.

4. Using sizeof with User-Defined Types:

C:

#include <stdio.h>

struct MyStruct {
int a;
double b;
char c;
};

int main() {
struct MyStruct s;

printf("Size of MyStruct: %zu bytes\n", sizeof(s));

return 0;
}
In this example, `sizeof` is used to find the size of a user-defined struct
type.

5. Using sizeof with a Byte Array Storing a Struct:

C:

#include <stdio.h>
#include <string.h>

struct MyStruct {
int a;
double b;
char c;
};

int main() {
struct MyStruct s = {10, 20.5, 'x'};
unsigned char byteArray[sizeof(s)];

memcpy(byteArray, &s, sizeof(s));

printf("Size of byteArray: %zu bytes\n", sizeof(byteArray));

return 0;
}

In this example, a byte array is created with a size equal to the size of a
struct. The struct is then copied into the byte array using the `memcpy`
function. The `sizeof` operator is used to determine the correct size for the
byte array and the amount of data to copy.
The `sizeof` operator is a powerful tool in C programming, providing
critical information about data sizes that can help you manage memory
usage effectively. It's especially useful in systems programming and other
low-level tasks where precise control over memory is required. By
understanding and using `sizeof`, you can write more efficient and more
reliable C code.

OceanofPDF.com
2.8 UNDERSTANDING THE
`VOLATILE` KEYWORD
The `volatile` keyword in C is a type qualifier that is used to tell the
compiler that a variable's value may be changed in ways that cannot be
predicted by the program. This is particularly useful in systems
programming, where variables may be accessed by multiple threads,
modified by an interrupt service routine, or represent hardware registers in
embedded systems.
When the compiler encounters the `volatile` keyword, it refrains from
applying certain optimizations on the variable that could lead to incorrect
behaviour. For instance, it will not cache the value of the variable in a
register, but will always read it from memory.
Here's an example of how to declare a volatile variable in C:

C:

volatile int flag; // flag is declared as volatile

In this example, `flag` is a volatile integer. The compiler, therefore, will not
make assumptions about the value of `flag` when optimizing the code.
The `volatile` keyword can also be used with pointers, similar to the `const`
keyword. There are two ways to use `volatile` with pointers:

Volatile Pointers
A volatile pointer is a pointer that points to a volatile variable. This means
that the pointer itself can be changed as usual, but the variable it points to
will not be optimized by the compiler.
C:

volatile int x = 10; // x is declared as volatile


volatile int *p = &x; // p is a pointer to a volatile variable

In this example, `p` is a pointer to a volatile integer. The integer `x` that `p`
points to is volatile, but `p` itself is not volatile.

Pointers to Volatile
A pointer to volatile is a pointer whose address is volatile. This means that
the pointer itself is volatile and cannot be optimized by the compiler, but the
variable it points to can be changed as usual.

C:

int x = 10; // x is a regular integer


int * volatile p = &x; // p is a volatile pointer to an integer

In this example, `p` is a volatile pointer to an integer. The pointer `p` is


volatile, but the integer `x` that `p` points to is not volatile.

The `volatile` keyword is a powerful tool for systems programming and


embedded systems, where the program needs to interact closely with the
hardware or with concurrent processes. However, it should be used
sparingly, as it can make the code more difficult to understand and debug.
It's important to remember that `volatile` does not make operations atomic,
nor does it protect against concurrent access from multiple threads. For
these purposes, other synchronization mechanisms like mutexes or atomic
operations should be used.

OceanofPDF.com
2.9 THE CONST VOLATILE
COMBINATION
In C, the `const` and `volatile` keywords can be used together to declare a
variable that is both a constant and volatile. This might seem contradictory
at first, but it makes sense in certain contexts, particularly in embedded
systems programming.
A `const volatile` variable is a variable that cannot be changed by your
program (because it's `const`), but it can change for reasons beyond the
control of the program (because it's `volatile`). This is typically used for
hardware registers in microcontrollers and other embedded systems, where
the register is read-only and its value can change due to hardware events.
Here's how you can declare a `const volatile` variable in C:

C:

const volatile int registerValue;

In this example, `registerValue` is a `const volatile` integer. The program


cannot change its value, but the value can change due to external factors,
and the compiler will not optimize away reads from `registerValue`
assuming that it cannot change.
Similarly, you can have pointers to `const volatile` variables or `const
volatile` pointers:
- Pointers to const volatile: A pointer to a `const volatile` variable is a
pointer that points to a variable that is both `const` and `volatile`.

C:
const volatile int x = 10;
const volatile int *p = &x;

In this example, `p` is a pointer to a `const volatile` integer.


- Const volatile Pointers: A `const volatile` pointer is a pointer that is both
`const` (its value, or the address it holds, cannot be changed) and `volatile`
(it can be changed by factors beyond the control of the program).

C:

int x = 10;
int * const volatile p = &x;

In this example, `p` is a `const volatile` pointer to an integer.


The `const volatile` combination is a powerful tool for systems
programming and embedded systems, where the program needs to interact
closely with the hardware. However, it should be used sparingly, as it can
make the code more difficult to understand and debug.

OceanofPDF.com
2.10 ERROR HANDLING IN
C
Error handling is a crucial aspect of programming in C. Unlike some
higher-level languages, C does not have built-in exception handling.
Instead, you must check for errors manually and handle them appropriately.
Here are some common techniques for error handling in C:

Return Values
Many C functions indicate errors through their return values. For example,
the `fopen` function returns `NULL` if the file cannot be opened. It's
important to always check the return values of functions and handle any
errors that are indicated.

C:

FILE *file = fopen("file.txt", "r");


if (file == NULL) {
​ rintf("Error: Unable to open file\n");
p
r​eturn 1;
}

In this example, if `fopen` returns `NULL`, an error message is printed and


the program exits with a non-zero status code.

Errno
The `errno` variable is a global variable that is set by many C library
functions when they encounter an error. You can check the value of `errno`
after calling a function to see if an error occurred. The `errno.h` header file
defines constants for various error codes, and the `strerror` function can be
used to get a human-readable string for an error code.

C:

FILE *file = fopen("file.txt", "r");


if (file == NULL) {
printf("Error: %s\n", strerror(errno));
return 1;
}

In this example, if `fopen` fails, the `strerror` function is used to print a


human-readable error message.

Error Handling Functions


You can write your own error handling functions to encapsulate common
error handling tasks. For example, you might write a function that takes an
error message and an error code, prints the message, and exits the program
with the error code.

C:

void handleError(const char *message, int errorCode) {


printf("Error: %s\n", message);
exit(errorCode);
}

You can then call this function whenever an error occurs:

C:

FILE *file = fopen("file.txt", "r");


if (file == NULL) {
handleError("Unable to open file", 1);
}

Error handling is a critical part of C programming. By checking return


values, using the `errno` variable, and writing your own error handling
functions, you can ensure that your program behaves correctly in the face of
errors.

OceanofPDF.com
2.11 STRUCTURES
A structure (or struct) in C is a user-defined data type that allows you to
combine data items of different kinds. Structures are used to represent a
record, which allows more complex data structures to be built.
Defining Structures
A structure is defined using the `struct` keyword, followed by an optional
tag, followed by a list of fields enclosed in curly braces. Each field has a
type and a name. Here's an example:

C:

struct Student {
char name[50];
int age;
float grade;
};

In this example, `Student` is a structure with three fields: a string `name`, an


integer `age`, and a float `grade`.

Creating Structure Variables


Once a structure type is defined, you can create variables of that type.
Here's how you can create a variable of type `Student`:

C:
struct Student john;

In this example, `john` is a variable of type `Student`.

Accessing Structure Members


You can access the fields of a structure using the dot operator (`.`). Here's
how you can set the fields of `john` and then access them:

C:

strcpy(john.name, "John Doe");


john.age = 20;
john.grade = 4.0;

printf("Name: %s\n", john.name);


printf("Age: %d\n", john.age);
printf("Grade: %.2f\n", john.grade);

In this example, `john.name`, `john.age`, and `john.grade` refer to the fields


of the `john` structure.

Structures and Functions


You can pass structures to functions and return structures from functions.
When you pass a structure to a function, it is passed by value, which means
that a copy of the structure is made for the function. If you want the
function to modify the original structure, you need to pass a pointer to the
structure.
Structures are a powerful feature of C that allow you to create complex data
types. They are particularly useful for representing records and building
more complex data structures.

Structures and Pointers


Pointers can be used to refer to a structure. This is particularly useful when
dealing with large structures, as it avoids the overhead of copying the entire
structure. To declare a pointer to a structure, we use the following syntax:

C:

struct Student *p;

In this example, `p` is a pointer to a structure of type `Student`. We can


assign the address of a `Student` structure to `p` like this:

C:

struct Student john;


p = &john;

Now, `p` points to `john`. To access the fields of the structure that `p` points
to, we use the arrow operator (`->`):

C:

strcpy(p->name, "John Doe");


p->age = 20;
p->grade = 4.0;

In this example, `p->name`, `p->age`, and `p->grade` refer to the fields of


the structure that `p` points to.

OceanofPDF.com
2.11.1 NESTED
STRUCTURES
Structures can be nested, which means that a structure can have fields that
are themselves structures. This is useful for creating more complex data
types. Here's an example:

C:

struct Date {
int day;
int month;
int year;
};

struct Student {
char name[50];
int age;
struct Date birthday;
};

In this example, `Student` is a structure with a `Date` structure as one of its


fields.
Structures are a powerful feature of C that allow you to group related
variables together. They are particularly useful for representing complex
data types and building data structures like lists and trees.

OceanofPDF.com
2.11.2 MANIPULATING
STRUCTS IN AN ARRAY
WITH `MEMCPY`
In C programming, you may often encounter scenarios where you need to
manipulate data within complex data structures, like arrays of structs. The
`memcpy` function proves to be a powerful tool for such operations. Let's
delve into an illustrative example of a C program that reads and updates
struct objects in an array using `memcpy`.

Defining the Struct


Our example revolves around a `Student` struct, which consists of two
attributes: an integer `id` and a character array `name`.

C:

typedef struct {
int id;
char name[50];
} Student;

Here, `typedef` allows us to use `Student` as a type name for our struct,
facilitating a more readable and maintainable code.

Creating an Array of Structs


Next, we create an array `students`, comprising four `Student` objects.

C:
Student students[4] = {
{1, "John Doe"},
{2, "Jane Doe"},
{3, "Jim Doe"},
{4, "Jill Doe"}
};

Each student in the array is initialized with an `id` and a `name`.

Using `memcpy` to Read and Update Structs


Our central task involves reading the second `Student` object and updating
the third one using the `memcpy` function.
First, we read the second `Student` by copying its data into a new `Student`
object, `secondStudent`.

C:

Student secondStudent;
memcpy(&secondStudent, &students[1], sizeof(Student));

In this context, `memcpy` takes three arguments: the destination address


(`&secondStudent`), the source address (`&students[1]`), and the number of
bytes to copy (`sizeof(Student)`). Consequently, the data of the second
student in the array is copied to `secondStudent`.
Next, we update the third `Student` object in the array by copying the
details of `secondStudent` onto it.

C:

memcpy(&students[2], &secondStudent, sizeof(Student));

Similar to our previous usage, the destination is now the address of the third
student in the array (`&students[2]`), and the source is `secondStudent`.
Upon completion of these steps, the third student's data now mirrors the
second student's data, as a result of our operations with `memcpy`.
Complete code:

C:
#include <stdio.h>
#include <string.h>

// Define the Student struct


typedef struct {
int id;
char name[50];
} Student;

int main() {
// Create an array of Student objects
Student students[4] = {
{1, "John Doe"},
{2, "Jane Doe"},
{3, "Jim Doe"},
{4, "Jill Doe"}
};

// Print the original students


printf("Original Students:\n");
for (int i = 0; i < 4; i++) {
printf("ID: %d, Name: %s\n", students[i].id, students[i].name);
}

// Read the second student


Student secondStudent;
memcpy(&secondStudent, &students[1], sizeof(Student));
printf("\nSecond Student:\n");
printf("ID: %d, Name: %s\n", secondStudent.id, secondStudent.name);
// Update the third student by copying the details of the second student
memcpy(&students[2], &secondStudent, sizeof(Student));

// Print the updated students


printf("\nUpdated Students:\n");
for (int i = 0; i < 4; i++) {
printf("ID: %d, Name: %s\n", students[i].id, students[i].name);
}

return 0;
}

OceanofPDF.com
2.12 ENUMERATIONS
(ENUM)
Enumerations, or `enum`, are a feature of C that allow you to define a type
that can have one of a few specific values. An `enum` is a user-defined data
type that consists of integral constants. To define an `enum`, you use the
`enum` keyword, followed by the `enum` name (optional), and a list of
enumerators (the possible values the `enum` can have) enclosed in curly
braces.
Here's an example of an `enum`:

C:

enum Day {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};

In this example, `Day` is an `enum` that can have one of seven values,
representing the days of the week. By default, the first enumerator has the
value `0`, and each subsequent enumerator value is one greater than the
value of its predecessor. So in this example, `Sunday` is `0`, `Monday` is
`1`, `Tuesday` is `2`, and so on.
You can also specify the values of the enumerators yourself:

C:

enum Month {January = 1, February, March, April, May, June, July, August, September, October,
November, December};

In this example, `January` is `1`, `February` is `2`, `March` is `3`, and so


on. If you don't specify a value for an enumerator, it is given the value one
more than the previous enumerator.
To create a variable of an `enum` type, you use the `enum` name, followed
by the variable name:

C:

enum Day today;


today = Wednesday;

In this example, `today` is a variable of type `Day`, and it is assigned the


value `Wednesday`.
Enumerations are a useful feature of C that allow you to create a type that
can have one of a specific set of values. This can make your code more
readable and self-documenting.

OceanofPDF.com
2.13 FUNCTIONS
Functions in C are the fundamental building blocks of a program. They are
self-contained modules of code that accomplish a specific task. Functions
are invoked by a call from another part of the program and the control is
returned back to the part that called it upon completion.

Defining a Function
A function definition in C programming language consists of a function
header and a function body. Here are all the parts of a function:
- Return Type: This is the type of data that the function is expected to return
to the point from where it was called. If the function is not returning any
values, then the return type is `void`.
- Function Name: This is the actual name of the function. It is the identifier
by which a function is called.
- Parameters: These are input values or arguments passed to the function at
the time of function call, from the point where function is invoked.
- Function Body: This part contains a collection of statements that define
what the function does.
Here is the general form of a function in C:

C:

return_type function_name( parameter list ) {


body of the function
}

For example, here's a simple function that adds two integers:


C:

int add(int num1, int num2) {


int sum = num1 + num2;
return sum;
}

Calling a Function
To use a function, you will have to call or invoke that function. When a
program calls a function, the program control is transferred to the called
function. The called function performs a defined task and when its return
statement is executed or when its function-ending closing brace is reached,
it returns the program control back to the main program.
To call a function, you simply need to pass the required parameters along
with the function name, and if the function returns a value, then you can
store the returned value. For example:

C:
int result = add(10, 20); // result is now 30

Function Prototypes
In C programming, a function prototype is a declaration of a function that
omits the function body but does specify the function's name, arity,
argument types and return type. Function prototypes provide a form of
forward declaration, allowing functions to be used before they are defined.
The function prototype informs the compiler about the number of
arguments and the type of arguments that the function is going to take, as
well as the type of the return value. This information helps the compiler in
performing the appropriate type checking.
The general syntax of a function prototype is as follows:

C:
return_type function_name( parameter list );

For instance, the function prototype for the `add` function would be:

C:
int add(int num1, int num2);

In this case, the function prototype tells the compiler that there's a function
named `add` that takes two integers as arguments and returns an integer.
Function prototypes are essential for ensuring that functions are used
correctly. If a function is used before it's defined, the compiler won't know
anything about the function's return type or its parameters. By providing a
function prototype, you give the compiler enough information to catch any
usage errors before the function is actually defined.
Moreover, function prototypes contribute to the modularity and readability
of your code, making it easier to understand and reuse. They are a
fundamental aspect of structured programming, where the focus is on the
data and the operations that are applied to it. As you continue to learn and
grow in C programming, you'll encounter more complex uses of functions
and will be able to utilize this powerful feature to write more efficient and
effective code.

OceanofPDF.com
2.14 ARRAYS AND
STRINGS
Arrays and strings are fundamental concepts in C programming that allow
you to store and manipulate groups of data of the same type.

Arrays
An array in C is a collection of elements of the same type stored in
contiguous memory locations. This means that if you have a set of integers
that are logically related, you can store them in an array. Here's how you
can declare an array in C:

C:
type arrayName[arraySize];

For example, to declare an array of integers named `numbers` with a size of


5, you would write:

C:
int numbers[5];

You can also initialize an array at the time of declaration:

C:
int numbers[5] = {1, 2, 3, 4, 5};
To access an element in the array, you use the array name followed by the
index of the element in square brackets. Note that array indices in C start at
0, so the first element is at index 0, the second element is at index 1, and so
on.

C:
int firstNumber = numbers[0]; // firstNumber is now 1

Strings
In C, strings are actually arrays of characters. A string is a sequence of
characters ending with the null character `\0`. Here's how you can declare
and initialize a string in C:

C:
char name[] = "John";

In this example, `name` is a string (or an array of characters) that contains


the characters 'J', 'o', 'h', 'n', and '\0'. The null character marks the end of the
string.
To access individual characters in the string, you can use array indexing:

C:
char firstLetter = name[0]; // firstLetter is now 'J'

Understanding arrays and strings is crucial for handling data in C. Arrays


allow you to store and manipulate collections of data, while strings enable
you to work with text. As you continue to learn C, you will encounter more
complex uses of arrays and strings and learn how to use them to write more
advanced programs.

OceanofPDF.com
2.15 POINTERS
A pointer in C is a variable that stores the memory address of another
variable. Pointers are a powerful feature of C that allow you to directly
manipulate memory, which can lead to more efficient and compact code.
However, they can also be a source of confusion and bugs, so it's important
to understand them well.

Declaring Pointers
A pointer is declared in the following way:

C:
type *pointerName;

Here, `type` is the data type of the variable the pointer is going to point to,
and `pointerName` is the name of the pointer variable. For example, to
declare a pointer to an integer, you would write:

C:
int *p;

Assigning Addresses to Pointers


To assign the address of a variable to a pointer, you use the address-of
operator (`&`):

C:

int x = 10;
int *p;
p = &x;

In this example, `p` now points to `x`, i.e., it contains the memory address
of `x`.

Accessing Values Through Pointers


To get the value of the variable that a pointer is pointing to, you use the
dereference operator (`*`):

C:

int x = 10;
int *p = &x;
int y = *p; // y is now 10

In this example, `*p` gives you the value of `x`, which is then assigned to
`y`.

Pointers and Arrays


Pointers and arrays in C are closely related. In fact, an array name is a
constant pointer to the first element of the array. Therefore, you can use
pointers to traverse and access elements of an array.

C:

int numbers[5] = {1, 2, 3, 4, 5};


int *p = numbers;
int firstNumber = *p; // firstNumber is now 1

In this example, `p` points to the first element of the `numbers` array, and
`*p` gives you the value of that element.
Pointers are a complex but powerful feature of C. They allow you to
directly manipulate memory and can lead to more efficient code. However,
they should be used carefully, as incorrect use of pointers can lead to bugs
that are hard to find.

OceanofPDF.com
2.16 UNDERSTANDING
POINTER ARITHMETIC
In C, a pointer is a variable that holds the memory address of another
variable. Pointer arithmetic is a set of operations that C allows on pointers
which includes addition, subtraction, comparison, and assignment.

Pointer Addition
When you add an integer to a pointer, the pointer moves a certain number of
elements forward. For example, if you have a pointer to an integer (which
takes 4 bytes on most systems), adding one to the pointer will advance the
pointer by 4 bytes.

C:

int arr[] = {10, 20, 30, 40, 50};


int *p = arr; // p points to the first element of arr

p = p + 1; // p now points to the second element of arr

Pointer Subtraction
Subtraction is the opposite of addition. When you subtract an integer from a
pointer, the pointer moves a certain number of elements backward.

C:

int arr[] = {10, 20, 30, 40, 50};


int *p = arr + 4; // p points to the last element of arr
p = p - 1; // p now points to the second last element of arr

Difference of Two Pointers


You can also subtract one pointer from another. The result is the number of
elements between the two pointers.

C:

int arr[] = {10, 20, 30, 40, 50};


int *p1 = arr; // p1 points to the first element of arr
int *p2 = arr + 4; // p2 points to the last element of arr

int diff = p2 - p1; // diff is 4

Pointer Comparison
Pointers can be compared using relational operators like `==`, `!=`, `<`, `>`,
`<=`, `>=`. This can be useful to check if two pointers point to the same
element or to compare the positions of two pointers.

C:

int arr[] = {10, 20, 30, 40, 50};


int *p1 = arr; // p1 points to the first element of arr
int *p2 = arr; // p2 also points to the first element of arr

if (p1 == p2) {
printf("p1 and p2 point to the same element.\n");
}

Pointer Assignment
You can assign the value of one pointer to another. After the assignment,
both pointers will point to the same element.

C:
int arr[] = {10, 20, 30, 40, 50};
int *p1 = arr; // p1 points to the first element of arr
int *p2;

p2 = p1; // p2 now points to the first element of arr

Pointer arithmetic is a powerful feature of C, but it should be used carefully.


Incorrect use of pointer arithmetic can lead to bugs that are difficult to find
and fix. Always make sure that your pointers point to valid memory
locations.

OceanofPDF.com
2.17 MEMORY
MANAGEMENT
Memory management is a critical aspect of programming in C. Unlike some
higher-level languages, C does not have a built-in garbage collector, so it's
up to the programmer to allocate and deallocate memory as needed. This
gives you a great deal of control, but also places a lot of responsibility on
your shoulders to manage memory correctly.

Dynamic Memory Allocation


In C, you can allocate memory at runtime using the `malloc`, `calloc`,
`realloc`, and `free` functions, which are part of the C standard library.
These functions allow you to request memory from the heap, which is a
region of memory used for dynamic memory allocation.
- `malloc`: This function allocates a specified amount of memory and
returns a pointer to the first byte of the allocated space, or NULL if there is
an error.

C:

int *p = malloc(4 * sizeof(int)); // allocate memory for 4 integers

- `calloc`: This function works like `malloc`, but it also initializes the
allocated memory to zero.

C:

int *p = calloc(4, sizeof(int)); // allocate memory for 4 integers and set them to zero
- `realloc`: This function changes the size of a block of memory that was
previously allocated with `malloc` or `calloc`.

C:

p = realloc(p, 8 * sizeof(int)); // resize the memory block to hold 8 integers

- `free`: This function deallocates a block of memory that was previously


allocated with `malloc`, `calloc`, or `realloc`.

C:
free(p); // deallocate the memory block

Memory Leaks
A memory leak occurs when a program allocates memory but fails to free it
when it's no longer needed. Over time, memory leaks can consume a lot of
memory and cause a program to run out of memory or perform poorly. To
avoid memory leaks, you should always free any memory that you allocate
as soon as you're done with it.

Pointers and Memory


Pointers play a crucial role in memory management. When you allocate
memory, you get a pointer to the first byte of the allocated space. You can
then use this pointer to read from or write to the allocated memory. When
you're done with the memory, you use the pointer to free it.
Memory management is a complex but important part of C programming.
By understanding how to allocate and deallocate memory, and by using
pointers correctly, you can write efficient and performant C programs.

OceanofPDF.com
2.18 COMMON C
LIBRARIES AND THEIR
FUNCTIONS
In C programming, a wide range of libraries exist, providing pre-defined
functions that can be used to perform different operations and tasks. Let's
take a closer look at some of the most commonly used C libraries and the
functions they offer.

Math library (math.h)


The math library in C offers a wide array of functions designed to perform
various mathematical operations:
- `sin()`, `cos()`, `tan()`: These functions are used to calculate trigonometric
ratios.
- `exp()`: This function is used to calculate the exponent of a number.
- `log()`, `log10()`: These functions are utilized to compute logarithms.
- `sqrt()`: This function is used to find the square root of a number.
- `ceil()`, `floor()`: These functions are designed to round up and down,
respectively.
- `pow()`: This function is used to calculate the power of a number.

Time library (time.h)


The time library in C provides various functions to work with date and
time:
- `time()`: This function fetches the current calendar time.
- `clock()`: This function computes the processor time.
- `difftime()`: This function calculates the difference between two time
points.
- `asctime()`: This function converts a time structure to a string.
String library (string.h)
The string library provides a suite of functions to manipulate and handle
strings in C:
- `strchr()`, `strrchr()`: These functions find the first/last occurrence of a
character in a string.
- `strstr()`: This function is used to search for a substring in a string.
- `strtok()`: This function splits a string into tokens.
- `strdup()`: This function duplicates a string.
- `strlen()`: This function calculates the length of a string.
- `strcpy()`, `strncpy()`: These functions are used to copy strings.
- `strcat()`, `strncat()`: These functions are used to concatenate strings.
- `strcmp()`, `strncmp()`: These functions compare two strings.

Standard Input/Output library (stdio.h)


The Standard Input/Output library offers a variety of functions for file and
console I/O operations:
- `fopen()`, `fclose()`: These functions are used to open and close files,
respectively.
- `fread()`, `fwrite()`: These functions read from and write to files.
- `fgetc()`, `fputc()`, `fgets()`, `fputs()`: These functions get and put
characters and strings from and into files.
- `fprintf()`, `fscanf()`: These functions read from and write to files with a
specific format.
- `printf()`: This function prints a specified message on the screen.
- `scanf()`: This function reads input data like integer, character, float, etc.
from the keyboard.
- `gets()`, `fgets()`: These functions read a line of text from the user.
- `puts()`, `fputs()`: These functions write a line of text to the console.

Standard library (stdlib.h)


The standard library in C provides an assortment of general purpose
functions:
- `rand()`, `srand()`: These functions generate and seed a pseudo-random
number.
- `atoi()`, `atol()`, `atof()`: These functions convert a string to an integer,
long, and double, respectively.
- `qsort()`: This function sorts an array using the quicksort algorithm.
- `bsearch()`: This function conducts a binary search in a sorted array.
- `malloc()`, `calloc()`: These functions dynamically allocate memory.
- `free()`: This function frees dynamically allocated memory.
- `exit()`: This function terminates the program.
- `abs()`: This function finds the absolute value of an integer.

ctype library (ctype.h)


The ctype library contains functions that classify characters into several
categories (digit, alpha, upper, lower, etc.):

- `isdigit()`, `isalpha()`, `isalnum()`: These functions check if a character is


a digit, an alphabet letter, or an alphanumeric character, respectively.
- `isupper()`, `islower()`: These functions check if a character is uppercase
or lowercase.
- `toupper()`, `tolower()`: These functions convert characters to uppercase
or lowercase.

In conclusion, understanding these libraries and their associated functions


can significantly improve your proficiency and efficiency in C
programming. They offer pre-built solutions that can simplify your code
and make your programs more efficient.

OceanofPDF.com
2.18.1 MATH LIBRARY
FUNCTIONS
The math library in C offers a multitude of functions for carrying out various mathematical
operations. In this chapter, we will explore several of these functions in detail. They are `sin()`,
`cos()`, `tan()`, `exp()`, `log()`, `log10()`, `sqrt()`, `ceil()`, `floor()`, and `pow()`. Each of these
functions can be utilized once the math library (`math.h`) is included in your program.

Trigonometric Functions: `sin()`, `cos()`, `tan()`


The `sin()`, `cos()`, and `tan()` functions are used to compute the sine, cosine, and tangent of an angle
(in radians), respectively.
- Input: A `double` value representing an angle in radians.
- Output: A `double` value that represents the sine, cosine, or tangent of the given angle.

C:

#include <stdio.h>
#include <math.h>

int main() {
double angle = M_PI / 4; // 45 degrees in radians

printf("sin(%f) = %f\n", angle, sin(angle));


printf("cos(%f) = %f\n", angle, cos(angle));
printf("tan(%f) = %f\n", angle, tan(angle));

return 0;
}

Exponential Function: `exp()`


The `exp()` function computes the exponential function `e^x`, where `e` is Euler's number
(approximately equal to 2.71828).

- Input: A `double` value representing the power to which `e` is raised.


- Output: A `double` value that is the result of `e` raised to the power of the input value.

C:
#include <stdio.h>
#include <math.h>

int main() {
double num = 2.0;

printf("exp(%f) = %f\n", num, exp(num));

return 0;
}

Logarithmic Functions: `log()`, `log10()`


The `log()` function computes the natural logarithm (base `e`) of a number, while `log10()` computes
the common logarithm (base 10) of a number.
- Input: A `double` value of which to compute the logarithm.
- Output: A `double` value that represents the natural or common logarithm of the input value.

C:

#include <stdio.h>
#include <math.h>

int main() {
double num = 10.0;

printf("log(%f) = %f\n", num, log(num));


printf("log10(%f) = %f\n", num, log10(num));

return 0;
}

Square Root Function: `sqrt()`


The `sqrt()` function computes the square root of a number.
- Input: A `double` value to compute the square root of.
- Output: A `double` value that is the square root of the input value.

C:
#include <stdio.h>
#include <math.h>

int main() {
double num = 16.0;

printf("sqrt(%f) = %f\n", num, sqrt(num));

return 0;
}

Rounding Functions: `ceil()`, `floor()`


The `ceil()` function rounds up a number to the smallest integer that is not less than the input, while
the `floor()` function rounds down a number to the largest integer that is not greater than the input.
- Input: A `double` value to round up or down.

- Output: A `double` value that is the rounded up or down integer.

C:

#include <stdio.h>
#include <math.h>

int main() {
double num = 10.7;

printf("ceil(%f) = %f\n", num, ceil(num));


printf("floor(%f) = %f\n", num, floor(num));

return 0;
}

Power Function: `pow()`


The `pow()` function computes a number raised to the power of an exponent.
- Input: Two `double` values. The first is the base and the second is the exponent.
- Output: A `double` value that is the result of raising the base to the power of the exponent.

C:

#include <stdio.h>
#include <math.h>

int main() {
double base = 2.0, exponent = 3.0;

printf("pow(%f, %f) = %f\n", base, exponent, pow(base, exponent));

return 0;
}

These functions provide a solid foundation for mathematical operations in the C language. Knowing
how to use them effectively can greatly enhance your problem-solving capabilities in various
domains like physics, engineering, and more.

OceanofPDF.com
2.18.2 TIME LIBRARY
FUNCTIONS
The C programming language offers several functions related to time, which are provided in the time
library (`time.h`). In this chapter, we'll delve into four such functions: `time()`, `clock()`, `difftime()`,
and `asctime()`. These functions are crucial for managing and manipulating time information in your
C programs.

Current Calendar Time: `time()`


The `time()` function retrieves the current calendar time.
- Input: A pointer to a `time_t` variable. If this parameter is not `NULL`, the function will store the
result at the specified location. If it is `NULL`, the function will not store the result.

- Output: A `time_t` value representing the current calendar time. If the function fails, it will return
`-1`.

C:

#include <stdio.h>
#include <time.h>

int main() {
time_t current_time = time(NULL);

printf("Current time is %ld\n", current_time);

return 0;
}

Processor Time: `clock()`


The `clock()` function calculates the processor time that has been used since the beginning of the
program.
- Input: None
- Output: The `clock_t` value that represents the processor time used by the program, or `-1` if the
time is not available.
C:

#include <stdio.h>
#include <time.h>

int main() {
clock_t start = clock();
// Execute some code here
clock_t end = clock();

double cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;

printf("CPU time used is %f seconds\n", cpu_time_used);

return 0;
}

Difference Between Two Time Points: `difftime()`


The `difftime()` function calculates the difference between two time points.
- Input: Two `time_t` values representing two points in time.
- Output: A `double` value that is the number of seconds between the two time points.

C:

#include <stdio.h>
#include <time.h>

int main() {
time_t start = time(NULL);
// Execute some code here
time_t end = time(NULL);

double difference = difftime(end, start);

printf("Time elapsed is %f seconds\n", difference);


return 0;
}

Convert Time Structure to String: `asctime()`


The `asctime()` function converts a `struct tm` (time structure) to a string in the format: "Day Mon
DD HH:MM:SS YYYY\n".
- Input: A pointer to a `struct tm`.
- Output: A string that represents the time and date information contained in the structure. It returns
`NULL` if the structure cannot be converted.

C:

#include <stdio.h>
#include <time.h>

int main() {
time_t current_time = time(NULL);
struct tm *time_info = localtime(&current_time);

printf("Current time is %s", asctime(time_info));

return 0;
}

The understanding of these functions provides a powerful toolset for dealing with time in your C
programs. They can be useful in a variety of applications, such as logging, performance testing, and
more.

OceanofPDF.com
2.18.3 STRING LIBRARY
FUNCTIONS
The C programming language includes a set of functions specifically for handling strings, which are
provided in the string library (`string.h`). These functions provide capabilities to perform a variety of
operations on strings such as finding a character or a substring, splitting a string into tokens,
duplicating a string, and more.

Finding a Character in a String: `strchr()`, `strrchr()`


The `strchr()` function returns a pointer to the first occurrence of a character in a string.
The `strrchr()` function, on the other hand, returns a pointer to the last occurrence of a character in a
string.

- Input: A string and a character to find in the string.


- Output: A pointer to the first (or last) occurrence of the character in the string or `NULL` if the
character is not found.

C:

#include <stdio.h>
#include <string.h>

int main() {
char str[] = "Hello, World!";
char *p;

p = strchr(str, 'o');
if(p) {
printf("First occurrence of 'o' is at position: %ld\n", p-str+1);
}

p = strrchr(str, 'o');
if(p) {
printf("Last occurrence of 'o' is at position: %ld\n", p-str+1);
}
return 0;
}

Finding a Substring in a String: `strstr()`


The `strstr()` function finds a substring in a string.
- Input: Two strings - the main string and the substring to find.
- Output: A pointer to the first occurrence of the substring in the string, or `NULL` if the substring is
not found.

C:

#include <stdio.h>
#include <string.h>

int main() {
char str[] = "Hello, World!";
char *p;

p = strstr(str, "World");
if(p) {
printf("Substring 'World' found at position: %ld\n", p-str+1);
}

return 0;
}

Splitting a String into Tokens: `strtok()`


The `strtok()` function splits a string into tokens, which are sequences of characters separated by
delimiters.
- Input: A string to split and a string containing delimiter characters.
- Output: A pointer to the next token, or `NULL` if there are no more tokens.

C:
#include <stdio.h>
#include <string.h>

int main() {
char str[] = "Hello, World!";
char *token = strtok(str, " ,!");

while(token) {
printf("Token: %s\n", token);
token = strtok(NULL, " ,!");
}

return 0;
}

Duplicating a String: `strdup()`


The `strdup()` function creates a new copy of a string.
- Input: A string to duplicate.
- Output: A pointer to the new string, or `NULL` if the string cannot be duplicated (for example, if
memory cannot be allocated).

C:

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

int main() {
char str[] = "Hello, World!";
char *copy = strdup(str);

if(copy) {
printf("Copied string: %s\n", copy);
free(copy);
}
return 0;
}

Calculating the Length of a String: `strlen()`


The `strlen()` function calculates the length of a string.
- Input: A string whose length to calculate.
- Output:
The length of the string (excluding the null-terminating character).

C:

#include <stdio.h>
#include <string.h>

int main() {
char str[] = "Hello, World!";
size_t length = strlen(str);

printf("Length of string is %zu\n", length);

return 0;
}

Copying Strings: `strcpy()`, `strncpy()`


The `strcpy()` function copies the contents of one string into another.
The `strncpy()` function copies up to n characters from the source string into the destination string.
- Input: Two strings - the destination string and the source string. For `strncpy()`, an additional size
parameter is also required.
- Output: A pointer to the destination string.

C:

#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20];

strcpy(dest, src);
printf("Copied string: %s\n", dest);

strncpy(dest, "Hello", 5);


dest[5] = '\0'; // add null-terminating char
printf("Copied string with strncpy: %s\n", dest);

return 0;
}

Concatenating Strings: `strcat()`, `strncat()`


The `strcat()` function appends the source string to the destination string.
The `strncat()` function appends up to n characters from the source string to the destination string.

- Input: Two strings - the destination string and the source string. For `strncat()`, an additional size
parameter is also required.
- Output: A pointer to the destination string.

C:

#include <stdio.h>
#include <string.h>

int main() {
char dest[20] = "Hello";
char src[] = ", World!";

strcat(dest, src);
printf("Concatenated string: %s\n", dest);

strcpy(dest, "Hello"); // reset dest


strncat(dest, src, 6); // append only 6 characters
printf("Concatenated string with strncat: %s\n", dest);
return 0;
}

Comparing Strings: `strcmp()`, `strncmp()`


The `strcmp()` function compares two strings lexicographically.

The `strncmp()` function compares up to n characters from two strings.


- Input: Two strings to compare. For `strncmp()`, an additional size parameter is also required.
- Output: An integer less than, equal to, or greater than zero if the first string is found, respectively, to
be less than, to match, or be greater than the second string.

C:

#include <stdio.h>
#include <string.h>

int main() {
char str1[] = "Hello";
char str2[] = "World";
int result;

result = strcmp(str1, str2);


if(result < 0) {
printf("str1 is less than str2\n");
} else if(result == 0) {
printf("str1 is equal to str2\n");
} else {
printf("str1 is greater than str2\n");
}

result = strncmp(str1, str2, 4); // compare only first 4 characters


if(result < 0) {
printf("First four characters of str1 are less than those of str2\n");
} else if(result == 0) {
printf("First four characters of str1 are equal to those of str2\n");
} else {
printf("First four characters of str1 are greater than those of str2\n");
}

return 0;
}

Memory Manipulation Functions: memcpy(), memmove(), memset()


The `memcpy()` function copies n bytes from the source memory area to the destination memory
area, returning a pointer to the destination. It does not handle overlapping areas.
-Input: Two memory areas (destination and source), and a size_t n.
-Output: A pointer to the destination memory area.
The `memmove()` function also copies n bytes from source to destination, but it handles overlapping
memory areas safely. It returns a pointer to the destination memory area.

-Input: Two memory areas (destination and source), and a size_t n.


-Output: A pointer to the destination memory area.
The `memset()` function fills the first n bytes of the memory area pointed to by s with the constant
byte c, returning a pointer to the memory area.

-Input: A memory area, a constant byte, and a size_t n.


-Output: A pointer to the memory area.

C:

#include <stdio.h>
#include <string.h>

int main() {
char src[] = "Hello, world!";
char dest1[20];
char dest2[20];
char dest3[20];

memcpy(dest1, src, strlen(src) + 1);


memmove(dest2, src, strlen(src) + 1);
memset(dest3, '$', 5);

printf("After memcpy: %s\n", dest1);


printf("After memmove: %s\n", dest2);
printf("After memset: %s\n", dest3);

return 0;
}

By understanding how to use these functions, you can manipulate strings and memory areas
effectively in your C programs. Remember to check and handle any errors that these functions may
produce, to ensure the robustness of your programs.

With this understanding of string functions in C, you are equipped to perform a variety of operations
on strings to meet your program's requirements.

OceanofPDF.com
2.18.4 INPUT/OUTPUT
FUNCTIONS
The I/O functions can be utilized once the library (`stdio.h`) is included in your program.
Opening and Closing Files: `fopen()`, `fclose()`
The `fopen()` function opens a file and returns a pointer to it. The `fclose()` function closes an
opened file.

- Input: For `fopen()`, a filename and a mode. The mode can be "r" for reading, "w" for writing, "a"
for appending, and more. For `fclose()`, a pointer to a file.
- Output: For `fopen()`, a pointer to the opened file. For `fclose()`, zero on success, EOF on error.

C:

#include <stdio.h>

int main() {
FILE* fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("Failed to open file\n");
return 1;
}
// file operations go here
fclose(fp);
return 0;
}

Reading from and Writing to Files: `fread()`, `fwrite()`


The `fread()` function reads data from a file into a buffer. The `fwrite()` function writes data from a
buffer to a file.

- Input: For both functions, a pointer to a buffer, a size of an element, a count of elements, and a
pointer to a file.
- Output: For both functions, the count of elements successfully read or written.
C:

#include <stdio.h>

int main() {
FILE* fp = fopen("test.bin", "wb");
int data[5] = {1, 2, 3, 4, 5};
fwrite(data, sizeof(int), 5, fp);
fclose(fp);

fp = fopen("test.bin", "rb");
int buffer[5];
fread(buffer, sizeof(int), 5, fp);
for (int i = 0; i < 5; i++) {
printf("%d ", buffer[i]);
}
fclose(fp);

return 0;
}

Reading from and Writing to Files Character by Character: `fgetc()`, `fputc()`


The `fgetc()` function reads a character from a file. The `fputc()` function writes a character to a file.

- Input: For `fgetc()`, a pointer to a file. For `fputc()`, a character and a pointer to a file.
- Output: For `fgetc()`, the character read on success, EOF on end of file or error. For `fputc()`, the
character written on success, EOF on error.

C:

#include <stdio.h>

int main() {
FILE* fp = fopen("test.txt", "w");
fputc('A', fp);
fclose(fp);
fp = fopen("test.txt", "r");
char ch = fgetc(fp);
printf("%c\n", ch);
fclose(fp);

return 0;
}

Reading from and Writing to Files Line by Line: `fgets()`, `fputs()`


The `fgets()` function reads a line from a file. The `fputs()` function writes a line to a file.
- Input: For `fgets()`, a pointer to a buffer, a maximum count of characters to read, and a pointer to a
file. For `fputs()`, a pointer to a string and a pointer to a file.
- Output: For `fgets()`, a pointer to the buffer on success, NULL on end of file or error. For `fputs()`,
a nonnegative value on success, EOF on error.

C:

#include <stdio.h>

int main() {
FILE* fp = fopen("test.txt", "w");
fputs("Hello, world!\n", fp);
fclose(fp);

fp = fopen("test.txt", "r");
char buffer[256];
fgets(buffer, sizeof(buffer), fp);
printf("%s", buffer);
fclose(fp);

return 0;
}

Formatted Input and Output: `fprintf()`, `fscanf()`


The `fprintf()` function writes formatted output to a file. The `fscanf()` function reads formatted input
from a file.
- Input: For both functions, a pointer to a file, a format string, and an optional list of arguments.
- Output: For both functions, the count of items successfully read or written.

C:

#include <stdio.h>

int main() {
FILE* fp = fopen("test.txt", "w");
fprintf(fp, "%s %d\n", "Hello, world!", 123);
fclose(fp);

fp = fopen("test.txt", "r");
char buffer[256];
int number;
fscanf(fp, "%s %d", buffer, &number);
printf("%s %d\n", buffer, number);
fclose(fp);

return 0;
}

Standard Input and Output: `printf()`, `scanf()`


The `printf()` function prints formatted output to the console. The `scanf()` function reads formatted
input from the keyboard.

- Input: For both functions, a format string, and an optional list of arguments.
- Output: For both functions, the count of items successfully read or written.

C:

#include <stdio.h>

int main() {
printf("%s %d\n", "Hello, world!", 123);
int number;
printf("Enter a number: ");
scanf("%d", &number);
printf("You entered: %d\n", number);

return 0;
}

Getting and Putting Strings: `gets()`, `puts()`


The `gets()` function reads a line of text from the user. The `puts()` function writes a line of text to
the console.
- Input: For `gets()`, a pointer to a buffer. For `puts()`, a pointer to a string.
- Output: For `gets()`, a pointer to the buffer on success, NULL on end of file or error. For `puts()`, a
nonnegative value on success, EOF on error.

C:

#include <stdio.h>

int main() {
char buffer[256];
printf("Enter a string: ");
gets(buffer); // Note: this function is dangerous because it doesn't check the buffer size
printf("You entered: ");
puts(buffer);

return 0;
}

With these functions, you can handle a wide variety of input and output operations in C.

OceanofPDF.com
2.18.5 STANDARD
LIBRARY FUNCTIONS
The standard functions can be utilized once the library (`stdlib.h`) is
included in your program.
Pseudo-random Number Generation: `rand()`, `srand()`
The `rand()` function generates a pseudo-random number between 0 and
RAND_MAX. The `srand()` function seeds the random number generator
with a starting value.

- Input: For `srand()`, an unsigned integer seed.


- Output: For `rand()`, a pseudo-random integer.

C:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
srand(time(NULL)); // seed the random number generator with the current time
printf("Random number: %d\n", rand());
return 0;
}

String to Number Conversion: `atoi()`, `atol()`, `atof()`


These functions convert strings to numbers. `atoi()` converts to an integer,
`atol()` converts to a long integer, and `atof()` converts to a double floating-
point number.

- Input: A string representing a number.


- Output: The converted number.

C:

#include <stdio.h>
#include <stdlib.h>

int main() {
char *str = "12345";
printf("Integer: %d\n", atoi(str));
printf("Long: %ld\n", atol(str));
printf("Double: %f\n", atof(str));
return 0;
}

Quick Sort: `qsort()`


This function sorts an array using the quicksort algorithm.

- Input: A pointer to the array, the number of elements in the array, the size
of each element, and a comparison function.
- Output: None. The array is sorted in-place.

C:

#include <stdio.h>
#include <stdlib.h>
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}

int main() {
int arr[] = {4, 2, 5, 1, 3};
int n = sizeof(arr) / sizeof(arr[0]);

qsort(arr, n, sizeof(int), compare);

for (int i = 0; i < n; i++) {


printf("%d ", arr[i]);
}

return 0;
}

Binary Search: `bsearch()`


This function performs a binary search on a sorted array.

- Input: A key to search for, a pointer to the array, the number of elements in
the array, the size of each element, and a comparison function.
- Output: A pointer to the element if found, or NULL if not found.

C:

#include <stdio.h>
#include <stdlib.h>

int compare(const void *a, const void *b) {


return (*(int*)a - *(int*)b);
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int key = 3;

int *item = (int*) bsearch(&key, arr, 5, sizeof(int), compare);


if (item != NULL) {
printf("Found item: %d\n", *item);
} else {
printf("Item not found\n");
}

return 0;
}

Dynamic Memory Allocation: `malloc()`, `calloc()`


The `malloc()` function allocates a block of uninitialized memory. The
`calloc()` function allocates a block of memory and initializes it to zero.

- Input: The size of the memory to allocate.


- Output: A pointer to the allocated memory.

C:

#include <stdio.h>
#include <stdlib.h>

int main() {
int *p = (int*) malloc(4 * sizeof(int)); // allocates space for 4 integers
if (p == NULL) {
printf("Failed to allocate memory\n");
return 1;
}
for (int i = 0; i < 4; i++) {
p[i] = i;
}

for (int i = 0; i < 4; i++) {


printf("%d ", p[i]);
}

free(p); // free the allocated memory

return 0;
}

Program Termination: `exit()`


This function terminates the program immediately.

- Input: An integer status code.


- Output: None. The program ends immediately.

C:

#include <stdio.h>
#include <stdlib.h>

int main() {
printf("About to exit...\n");
exit(0);
}
Absolute Value: `abs()`
This function returns the absolute value of an integer.

- Input: An integer.
- Output: The absolute value of the integer.

C:

#include <stdio.h>
#include <stdlib.h>

int main() {
printf("Absolute value: %d\n", abs(-123));
return 0;
}

These functions are all fundamental parts of the C programming language


and provide a variety of important features for your programs.

OceanofPDF.com
2.18.6 CTYPE LIBRARY
FUNCTIONS
The ctype functions can be utilized once the library (`ctype.h`) is included
in your program.
Checking Character Types: `isdigit()`, `isalpha()`, `isalnum()`
The `isdigit()` function checks if a character is a decimal digit (0-9).
`isalpha()` checks if a character is an alphabet letter (a-z, A-Z), and
`isalnum()` checks if a character is either a digit or an alphabet letter.

- Input: A character.
- Output: Non-zero integer if true, 0 if false.

C:

#include <stdio.h>
#include <ctype.h>

int main() {
char ch = 'A';

if (isdigit(ch)) {
printf("%c is a digit\n", ch);
}

if (isalpha(ch)) {
printf("%c is an alphabet letter\n", ch);
}

if (isalnum(ch)) {
printf("%c is an alphanumeric character\n", ch);
}

return 0;
}

Checking Case of Characters: `isupper()`, `islower()`


The `isupper()` function checks if a character is an uppercase alphabet letter
(A-Z). `islower()` checks if a character is a lowercase alphabet letter (a-z).

- Input: A character.
- Output: Non-zero integer if true, 0 if false.

C:

#include <stdio.h>
#include <ctype.h>

int main() {
char ch = 'a';

if (isupper(ch)) {
printf("%c is uppercase\n", ch);
}

if (islower(ch)) {
printf("%c is lowercase\n", ch);
}

return 0;
}
Changing Case of Characters: `toupper()`, `tolower()`
The `toupper()` function converts a character to uppercase if it is lowercase,
and `tolower()` converts a character to lowercase if it is uppercase.

- Input: A character.
- Output: The converted character.

C:

#include <stdio.h>
#include <ctype.h>

int main() {
char ch = 'a';

printf("Uppercase: %c\n", toupper(ch));


printf("Lowercase: %c\n", tolower('A'));

return 0;
}

These functions are part of the ctype library in C and provide ways to
inspect and modify the case of characters and to classify characters as
digits, alphabetic, or alphanumeric.

OceanofPDF.com
2.19 STREAMS
Streams are abstract interfaces for data transfer in the C programming
language. In this chapter, we'll dive into the details of streams, discuss their
types, and learn how to use them in C programming.

Standard Streams in C
In C, there are three pre-defined file pointers as part of the standard library
`stdio.h`. They represent standard input (`stdin`), standard output (`stdout`),
and standard error (`stderr`).
- `stdin`: This is the standard input stream, which is usually the keyboard.
Functions such as `scanf()`, `getchar()`, `fgets()`, and others use `stdin` to
get input data from the keyboard. Here is an example:

C:

#include <stdio.h>

int main() {
int number;
printf("Enter a number: ");
scanf("%d", &number);
printf("You entered: %d\n", number);
return 0;
}

In this program, `scanf()` reads an integer from the standard input (`stdin`)
and stores it in the variable `number`.
- `stdout`: This represents the standard output stream, which typically
displays on the computer screen. Functions like `printf()`, `putchar()`,
`puts()`, and others output data to `stdout`. Here is an example:

C:

#include <stdio.h>

int main() {
printf("Hello, World!\n");
return 0;
}

In this program, `printf()` writes the string "Hello, World!" followed by a


newline to the standard output (`stdout`).
- `stderr`: This is the standard error stream, which is the default output for
error messages, typically displayed on the computer screen. Here is an
example:

C:

#include <stdio.h>

int main() {
int dividend = 5;
int divisor = 0;
if (divisor == 0) {
fprintf(stderr, "Error: Division by zero is not allowed.\n");
return 1;
}
printf("The result is %d\n", dividend / divisor);
return 0;
}
In this program, `fprintf()` writes an error message to `stderr` when trying
to divide by zero.

File Streams in C
Beyond `stdin`, `stdout`, and `stderr`, C allows the definition of other
streams. These additional streams are typically associated with files or other
input/output devices and need to be explicitly opened and closed within the
program using C standard I/O library functions.
The `fopen()` function can be used to open a file for reading, writing, or
both. The function returns a `FILE *` pointer, which can be used with other
standard I/O functions.
Here's an example where a file is opened, written to, and then closed:

C:

#include <stdio.h>

int main() {
FILE *fp;

fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("Error opening file!\n");
return 1;
}

fprintf(fp, "This is some text.\n");

fclose(fp);

return 0;
}
In this example, `fp` is a file stream. The `fprintf()` function writes to the
stream, and `fclose()` is used to close it after use. It's crucial to always close
any streams that you open to prevent resource leaks.

Redirecting Streams and Using File Functions with stdin and stdout
In C, you can also redirect streams and use `stdin` and `stdout` with file
functions. For instance, you can use `stdin` with the `fscanf()` function to
read input from the user:

C:

#include <stdio.h>

int main() {
int number;
printf("Enter a number: ");
fscanf(stdin, "%d", &number);
printf("You entered: %d\n", number);
return 0;
}

In the above example, `fscanf()` reads an integer from the standard input
(`stdin`) and stores it in the variable `number`.
Similarly, you can use `stdout` with the `fprintf()` function to write output
to the screen:

C:

#include <stdio.h>

int main() {
int number = 10;
fprintf(stdout, "The number is: %d\n", number);
return 0;
}

In this example, `fprintf()` writes the string "The number is: " followed by
the value of `number` and a newline to the standard output (`stdout`).
Overall, understanding streams is crucial in C programming as they offer a
convenient way to handle input and output operations. Whether reading
from the keyboard or writing to a file, streams make the process seamless
and efficient.

OceanofPDF.com
2.20 BUFFER OVERFLOW
MANAGEMENT AND
STREAM REDIRECTION
When working with streams in C programming, it is essential to pay
attention to buffer management. Improper handling can lead to issues such
as buffer overflow, which could potentially result in errors or security
vulnerabilities. In this chapter, we discuss methods of effective buffer
management, the risks of improper handling, and how stream redirection
can be leveraged for more versatile and robust coding.

Buffer Management in C
Buffer management is a critical aspect of programming in C. Improper
handling can lead to buffer overflow, where the program writes more data
to a buffer than it can handle, potentially causing an application crash or,
even worse, a security vulnerability.
Consider the use of the `fgets()` function. It reads data from an input stream
and stores it into a character array, or buffer. The advantage of using
`fgets()` over certain other functions, like `gets()`, is that `fgets()` allows
specifying a maximum size of data to be read, thus preventing buffer
overflow.
Here's an example where `fgets()` reads data from a file without risking
buffer overflow:

C:

#include <stdio.h>
int main() {
FILE *fp;
char buffer[100];

fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("Error opening file!\n");
return 1;
}

while (fgets(buffer, sizeof(buffer), fp) != NULL) {


printf("%s", buffer);
}

fclose(fp);

return 0;
}

In this code, `fgets(buffer, sizeof(buffer), fp)` reads a line from `fp` but
never reads more characters than can fit into `buffer`, thereby preventing
buffer overflow.

Risks of Buffer Overflow and Usage of fgets() Over gets()


Consider an alternative function, `gets()`, which reads a line from `stdin`
into a buffer. The `gets()` function does not have a way to limit input size,
which makes it vulnerable to buffer overflow. This is why its use is
discouraged, and it has been removed from the latest versions of the C
standard.
Let's observe an example of using `gets()`:

C:

#include <stdio.h>
int main() {
char buffer[5];

printf("Enter some text: ");


gets(buffer);
printf("You entered: %s\n", buffer);

return 0;
}

In this code, if a user enters more than 4 characters (leaving room for the
null character), the `gets(buffer)` function call will lead to buffer overflow,
which could cause a crash or allow a malicious user to execute arbitrary
code.
To prevent this, `fgets()` can be used to read from `stdin`:

C:

#include <stdio.h>

int main() {
char buffer[5];

printf("Enter some text: ");


fgets(buffer, sizeof(buffer), stdin);
printf("You entered: %s\n", buffer);

return 0;
}

With `fgets(buffer, sizeof(buffer), stdin)`, you are now controlling the


maximum amount of input read to prevent buffer overflow.
Conclusion
Stream redirection, combined with safe data handling practices, plays an
important role in creating robust and efficient C applications. These
practices provide a reliable way to handle input and output operations,
preventing buffer overflow and enabling the building of secure and versatile
code. Thus, understanding and using functions like `fgets()` instead of
`gets()` are vital steps in becoming a proficient C programmer.

OceanofPDF.com
CHAPTER 3: PRACTICAL
C
As we journey further into the realm of C programming, we encounter
concepts and techniques that offer even greater control and flexibility. This
chapter is dedicated to these advanced topics, which will enable you to
harness the full power of C and write more sophisticated and efficient
programs.
We will begin by exploring structures in C. Structures are a way of
grouping related variables together, allowing you to manage complex data
in a more organized and intuitive way. We will discuss how to declare and
use structures, and how to work with arrays of structures and structures
containing pointers.
Next, we will delve into the topic of file handling in C, with a particular
focus on how to write and read structures to and from files. This is a crucial
skill for many real-world applications, as it allows your programs to persist
data between runs and to exchange data with other programs.
Following that, we will explore bit manipulation techniques. Bit
manipulation is a powerful tool that allows you to directly manipulate the
individual bits of data in memory. While it can be a challenging concept to
grasp, understanding bit manipulation can lead to more efficient and
compact code, and is essential for certain types of programming such as
device drivers and embedded systems.
Finally, we will discuss the keywords enum, const, volatile, and const
volatile. These keywords allow you to further control the behavior of your
code and the way it interacts with memory. Understanding these keywords
will give you a deeper understanding of C and enable you to write more
robust and efficient code.
By the end of this chapter, you will have a solid grasp of advanced C
concepts and be well-equipped to tackle complex programming challenges.
So, let's dive in and unlock the full potential of C programming!

OceanofPDF.com
3.1 AN INTRODUCTION
TO NUMERAL SYSTEMS
Numeral systems, also known as number systems, are methods of
representing numbers that allow us to express quantities and perform
computations. These systems have been instrumental to human civilizations
for thousands of years, enabling scientific, economic, and technological
advancements.
In the simplest terms, a numeral system is a way to express numbers using a
consistent set of symbols. The exact symbols and how they are arranged
vary from one system to another, but the underlying concept remains the
same.
There are several types of numeral systems, each defined by its "base" or
"radix" - the number of unique digits, including zero, used to represent
numbers. Some commonly used numeral systems include:
1. Decimal (Base-10): This is the system most commonly used in everyday
life. It employs ten symbols, from 0-9.
2. Binary (Base-2): This is the fundamental language of digital electronics
and computer systems, using only two symbols: 0 and 1.
3. Octal (Base-8): This system uses eight symbols, from 0-7. While less
common today, it was widely used in early computing systems.
4. Hexadecimal (Base-16): This system uses sixteen symbols: 0-9 and A-F.
It is frequently used in computing and digital systems because it represents
large binary numbers in a more human-readable format.
These numeral systems, while different in their representation, serve the
same purpose: to express and manipulate quantities. Understanding the
relationships and conversions between these systems is crucial for various
scientific and computing domains. In the subsequent chapters, we will delve
into each of these systems and their interconversion in detail, enabling you
to interact with and understand the digital world more effectively.
OceanofPDF.com
3.1.1 CONVERTING
BINARY FORMATS
Binary, as the term suggests, is a base-2 numeral system, commonly used in
digital systems including computers, where only two types of symbols or
digits are used: 0 and 1. In contrast, the decimal system that we typically
use in everyday life is base-10, composed of ten digits (0-9).
In the realm of digital electronics and computing, the binary system is
paramount. Each binary digit, also known as a "bit," represents the
fundamental unit of data. A combination of these bits creates larger units,
such as a byte (8 bits), kilobyte (1024 bytes), and so forth.

Converting Decimal to Binary


To convert from decimal to binary, one can use the method of repeated
division by 2. The process includes the following steps:
1. Divide the decimal number by 2.
2. Record the remainder.
3. Replace the original decimal number with the quotient from step 1.
4. Repeat the process until the quotient is 0.
The binary equivalent of the decimal number is the string of remainders
read from bottom to top (i.e., in reverse order of generation).

Example
Let's convert the decimal number 13 to binary:
1. 13 divided by 2 equals 6 remainder 1.
2. 6 divided by 2 equals 3 remainder 0.
3. 3 divided by 2 equals 1 remainder 1.
4. 1 divided by 2 equals 0 remainder 1.
Reading the remainders from the bottom up gives the binary representation
of 13 as 1101.

Converting Binary to Decimal


The process of converting from binary to decimal involves multiplying each
binary digit by 2 raised to an appropriate power, then summing these
products.
Here's the step-by-step process:
1. Identify the rightmost bit in the binary number. This is the least
significant bit. The next bit to the left is two times more significant, and so
on. Assign each bit a power of 2 starting from 0 for the rightmost bit and
incrementing by 1 as you move left.
2. Multiply each binary digit by the corresponding power of 2.
3. Sum up all the values obtained from step 2. The result is the decimal
equivalent of the binary number.

Example
Let's convert the binary number 1011 to decimal:
1. Assign powers of 2 to each digit:
1 (2^3), 0 (2^2), 1 (2^1), 1 (2^0).
2. Multiply each binary digit by the corresponding power of 2:
1*2^3, 0*2^2, 1*2^1, 1*2^0 = 8, 0, 2, 1.
3. Sum up all the values obtained:
8 + 0 + 2 + 1 = 11.
Therefore, the binary 1011 is equivalent to the decimal number 11.
In summary, understanding binary format and conversion between decimal
and binary is fundamental to grasping how data is represented and
manipulated within digital systems. These skills provide the foundation for
more advanced concepts such as binary arithmetic, logical operations, and
computer programming.

OceanofPDF.com
3.1.2 CONVERTING
HEXADECIMAL FORMATS
The hexadecimal numeral system, often shortened to hex, is a base-16
system, meaning it uses sixteen distinct symbols. It includes the digits 0-9
to represent values zero to nine, and the letters A-F (or alternatively a-f) to
represent values ten to fifteen. Hexadecimal is particularly useful in
computing and digital systems because it's a human-friendly representation
of binary-coded values.

Converting Decimal to Hexadecimal


The process of converting decimal numbers to hexadecimal is similar to the
method used for binary conversion. Instead of dividing by 2, we divide by
16. Here are the steps:
1. Divide the decimal number by 16.
2. Record the remainder in hexadecimal (i.e., if the remainder is 10-15,
write down A-F).
3. Replace the original decimal number with the quotient.
4. Repeat the process until the quotient is 0.
The hexadecimal equivalent of the decimal number is the sequence of
remainders read from bottom to top.

Example
Let's convert the decimal number 255 to hexadecimal:
1. 255 divided by 16 equals 15 remainder 15.
2. 15 divided by 16 equals 0 remainder 15.
Writing down the remainders in hexadecimal (15 is F), from bottom to top,
gives us FF. So, 255 in decimal is FF in hexadecimal.
Converting Hexadecimal to Decimal
Converting a hexadecimal number to decimal involves a similar method to
that used in binary-to-decimal conversion. The process is as follows:
1. Identify the rightmost digit in the hexadecimal number. This is the least
significant digit. The next digit to the left is sixteen times more significant,
and so forth. Assign each digit a power of 16, starting from 0 for the
rightmost digit and incrementing by 1 as you move left.
2. Multiply each hexadecimal digit (converted to decimal) by the
corresponding power of 16.
3. Sum up all the values obtained from step 2. The result is the decimal
equivalent of the hexadecimal number.

Example
Let's convert the hexadecimal number AF to decimal:
1. Assign powers of 16 to each digit:
A (16^1), F (16^0).
2. Convert A and F to decimal and multiply by the corresponding powers of
16:
A = 10, F = 15.
10*16^1, 15*16^0 = 160, 15.

3. Sum up all the values obtained:


160 + 15 = 175.
Therefore, the hexadecimal AF is equivalent to the decimal number 175.
Understanding hexadecimal format and the conversion process between
decimal and hexadecimal is key to mastering data representation in digital
systems. It is used extensively in various fields of computer science and
digital electronics, such as in coding, addressing, identifying, and color
representation.

OceanofPDF.com
3.2 BIT MANIPULATION
TECHNIQUES
Bit manipulation involves the construction and modification of data at the
level of individual bits. It's a powerful technique that can lead to more
efficient code, especially in systems programming and embedded systems.

Bitwise Operators
Bitwise operators perform operations on the binary representations of
integers. Here are the bitwise operators in C:
- Bitwise AND (`&`):
Bitwise AND (`&`) in C (or any other language) operates on the binary
representations of integers. It compares each bit of the first operand to the
corresponding bit of the second operand. If both bits are 1, the
corresponding result bit is set to 1. Otherwise, the result bit is set to 0.
Here is the truth table for the bitwise AND operation:
Operand 1 (Bit A) Operand 2 (Bit B) A `&` B (Result)
0 0 0
0 1 0
1 0 0
1 1 1

For instance, if you were to perform a bitwise AND operation on two


integers 12 and 13:
C:

int a = 12; // In binary: 1100


int b = 13; // In binary: 1101
int result = a & b; // Result: 1100 (in binary), which is 12 in decimal.

In this case, the bitwise AND operation would compare the binary bits from
`a` and `b` and give the result. The operation is performed on each
corresponding pair of bits, and the result is a binary number that represents
the decimal number 12, as demonstrated above.

- Bitwise OR (`|`):
Bitwise OR (`|`) in C (or any other language) operates on the binary
representations of integers. It compares each bit of the first operand to the
corresponding bit of the second operand. If either bit is 1, the corresponding
result bit is set to 1. Only if both bits are 0, the result bit is set to 0.

Here is the truth table for the bitwise OR operation:


Operand 1 (Bit A) Operand 2 (Bit B) A `|` B (Result)
0 0 0
0 1 1
1 0 1
1 1 1

For instance, if you were to perform a bitwise OR operation on two integers


12 and 13:

C:
int a = 12; // In binary: 1100
int b = 13; // In binary: 1101
int result = a | b; // Result: 1101 (in binary), which is 13 in decimal.

In this case, the bitwise OR operation would compare the binary bits from
`a` and `b` and give the result. The operation is performed on each
corresponding pair of bits, and the result is a binary number that represents
the decimal number 13, as demonstrated above.

- Bitwise XOR (`^`):


Bitwise XOR (`^`) in C (or any other language) operates on the binary
representations of integers. It compares each bit of the first operand to the
corresponding bit of the second operand. If the bits are different, the
corresponding result bit is set to 1. If the bits are the same, the result bit is
set to 0.

Here is the truth table for the bitwise XOR operation:

Operand 1 (Bit A) Operand 2 (Bit B) A `^` B (Result)


0 0 0
0 1 1
1 0 1
1 1 0

For instance, if you were to perform a bitwise XOR operation on two


integers 12 and 13:

C:

int a = 12; // In binary: 1100


int b = 13; // In binary: 1101
int result = a ^ b; // Result: 0001 (in binary), which is 1 in decimal.

In this case, the bitwise XOR operation would compare the binary bits from
`a` and `b` and give the result. The operation is performed on each
corresponding pair of bits, and the result is a binary number that represents
the decimal number 1, as demonstrated above.

- Bitwise NOT (`~`):


Bitwise NOT (`~`) in C (or any other language) operates on the binary
representation of an integer. It flips each bit of the operand. If a bit is 1, it
becomes 0. If a bit is 0, it becomes 1.

Here is the truth table for the bitwise NOT operation:

Operand (Bit A) `~`A (Result)


0 1
1 0

For instance, if you were to perform a bitwise NOT operation on an integer


12:

C:

int a = 12; // In binary: 1100


int result = ~a; // Result: 0011 (in binary for a 4-bit system), which is 3 in decimal.

However, keep in mind that integers in C are typically represented using


more than 4 bits. In a 32-bit system, the integer 12 is actually represented as
`00000000000000000000000000001100`, and the result of `~a` would be
`11111111111111111111111111110011`, which is -13 in decimal, because
the most significant bit is used for sign (1 means negative in a two's
complement representation, which is usually used in modern computers).
So, for actual C code, the `~a` operation for `a = 12` would give `-13`.

- Left shift (`<<`):


The left shift (`<<`) operator in C (or any other language) operates on the
binary representation of an integer. It shifts each bit of the operand to the
left by the specified number of positions. Zeroes are filled in from the right,
and the leftmost bits that 'fall off' are discarded.
Here is an example of the bitwise left shift operation:

C:

int a = 3; // In binary: 00000011


int result = a << 2; // Result: 00001100 (in binary), which is 12 in decimal.

In this case, the `<<` operator moves each bit in the binary representation of
`a` two positions to the left. Two zeros are added from the right, and the two
leftmost bits are dropped.
Here's another example:

C:

int b = 5; // In binary: 00000101


int result2 = b << 3; // Result: 01010000 (in binary), which is 40 in decimal.

In this case, the `<<` operator moves each bit in the binary representation of
`b` three positions to the left. Three zeros are added from the right, and the
three leftmost bits are dropped.

- Right shift (`>>`):


The right shift (`>>`) operator in C operates on the binary representation of
an integer. It shifts each bit of the operand to the right by the specified
number of positions. The bits that 'fall off' to the right are discarded.
If the number is an unsigned integer, zeros are filled in from the left. If the
number is a signed integer, the sign bit (i.e., the leftmost bit) is used to fill
in from the left. This is called sign extension and it preserves the sign of the
number when bits are shifted.
Here's an example of right shift with a positive number:

C:

int a = 12; // In binary: 00001100


int result = a >> 2; // Result: 00000011 (in binary), which is 3 in decimal.

In this case, the `>>` operator moves each bit in the binary representation of
`a` two positions to the right. Two zeros are added from the left because `a`
is a positive number, and the two rightmost bits are dropped.
And an example with a negative number:

C:

int b = -12; // In binary: 11110100 (assuming 8-bit two's complement representation)


int result2 = b >> 2; // Result: 11111101 (in binary), which is -3 in decimal (again, under 8-bit two's
complement representation).

In this case, the `>>` operator moves each bit in the binary representation of
`b` two positions to the right. Since `b` is negative and represented in two's
complement form, ones are added from the left (sign extension), and the
two rightmost bits are dropped. This preserves the negative sign of the
number.

Setting a Bit
To set a bit (change it to 1), you can use the bitwise OR operator with a
mask that has a 1 in the position of the bit you want to set. Here's an
example:

C:
unsigned int x = 0; // Binary: 0000 0000
x = x | (1 << 3); // Binary: 0000 1000

// To set the 3rd bit, we perform the following steps:


// 1. (1 << 3) shifts 1 three positions to the left, resulting in 0000 1000.
// 2. x | (1 << 3) performs a bitwise OR operation between x and the shifted value.
// This sets the 3rd bit of x to 1 while leaving other bits unchanged.
// Binary: 0000 0000 OR
// 0000 1000
// ---------------------------------------
// 0000 1000

In the given code, we have a variable `x` initially assigned the value `0`,
which in binary is `0000 0000`.
The line `x = x | (1 << 3);` performs the bitwise OR operation to set the
third bit of `x`. Here's the step-by-step process:
1. `(1 << 3)` shifts `1` three positions to the left, resulting in `0000
1000`, which is a mask with a `1` only in the third bit.
2. `x | (1 << 3)` performs the bitwise OR operation between `x` and
the shifted value. This operation sets the third bit of `x` to `1` while
leaving other bits unchanged.
After executing `x = x | (1 << 3);`, the value of `x` becomes `8`, as shown in
the binary representation `0000 1000`.
Therefore, the third bit of `x` has been successfully set to `1`.

Clearing a Bit
To clear a bit (change it to 0), you can use the bitwise AND operator with a
mask that has a 0 in the position of the bit you want to clear. Here's an
example:

C:
unsigned int x = 9; // Binary: 0000 1001
x = x & ~(1 << 3); // Binary: 0000 0001

// To clear the 3rd bit, we perform the following steps:


// 1. (1 << 3) shifts 1 three positions to the left, resulting in 0000 1000.
// 2. ~(1 << 3) performs a bitwise NOT operation, flipping all bits of the shifted value. Result: 1111
0111.
// 3. x & ~(1 << 3) performs a bitwise AND operation between x and the complement of the shifted
value.
// This clears the 3rd bit of x while leaving other bits unchanged.
// Binary: 0000 1001 AND
// 1111 0111
// ---------------------------------------
// 0000 0001

In the given code, we have a variable `x` initially assigned the value `9`,
which in binary is `0000 1001`.
The line `x = x & ~(1 << 3);` performs the bitwise AND operation to clear
the third bit of `x`. Here's the step-by-step process:
1. `(1 << 3)` shifts `1` three positions to the left, resulting in `0000
1000`, which is a mask with a `1` only in the third bit.
2. `~(1 << 3)` performs a bitwise NOT operation on the mask,
inverting all the bits except the third bit. The result is `1111 0111`.
3. `x & ~(1 << 3)` performs the bitwise AND operation between `x`
and the complemented mask. This operation clears the third bit of `x`
while leaving other bits unchanged.
After executing `x = x & ~(1 << 3);`, the value of `x` becomes `1`, as
shown in the binary representation `0000 0001`.
Therefore, the third bit of `x` has been successfully cleared.

Toggling a Bit
To toggle a bit (change it from 0 to 1 or from 1 to 0), you can use the
bitwise XOR operator with a mask that has a 1 in the position of the bit you
want to toggle. Here's an example:
C:

unsigned int x = 9; // Binary: 0000 1001


x = x ^ (1 << 3); // Binary: 0000 0001

// To toggle the 3rd bit, we perform the following steps:


// 1. (1 << 3) shifts 1 three positions to the left, resulting in 0000 1000.
// 2. x ^ (1 << 3) performs a bitwise XOR operation between x and the shifted value.
// This toggles the 3rd bit of x, leaving other bits unchanged.
// Binary: 0000 1001 XOR
// 0000 1000
// ---------------------------------------
// 0000 0001

In the given code, we have a variable `x` initially assigned the value `9`,
which in binary is `0000 1001`.
The line `x = x ^ (1 << 3);` performs the bitwise XOR operation to toggle
the third bit of `x`. Here's the step-by-step process:
1. `(1 << 3)` shifts `1` three positions to the left, resulting in `0000
1000`, which is a mask with a `1` only in the third bit.
2. `x ^ (1 << 3)` performs the bitwise XOR operation between `x` and
the shifted value. This operation toggles the third bit of `x`, leaving
other bits unchanged.
After executing `x = x ^ (1 << 3);`, the value of `x` becomes `1`, as shown
in the binary representation `0000 0001`.
Therefore, the third bit of `x` has been successfully toggled.
Bit manipulation is a powerful technique that can lead to more efficient and
compact code. However, it can also be tricky to get right, so it's important
to understand it well and use it carefully.

Checking the Value of a Bit


To check whether a bit is set (1) or not set (0), you can use the bitwise AND
operator with a mask that has a 1 in the position of the bit you want to
check. Here's an example:
C:

unsigned int x = 8; // Binary: 0000 1000


unsigned int mask = 1 << 3; // Binary: 0000 1000

if (x & mask) {
printf("The 3rd bit is set.\n");
} else {
printf("The 3rd bit is not set.\n");
}

In the above code, we have two variables: `x` and `mask`. Here are their
binary values:

- `x`: 0000 1000


- `mask`: 0000 1000

The variable `mask` is created by shifting `1` three positions to the left (`1
<< 3`), which sets the third bit to `1` and leaves all other bits as `0`.
The `if` condition checks if the bitwise AND operation between `x` and
`mask` evaluates to a non-zero value. If the third bit of `x` is set (i.e., `1`),
the condition is true. Otherwise, if the third bit is not set (i.e., `0`), the
condition is false.
Based on the condition, the appropriate message is printed: "The 3rd bit is
set." or "The 3rd bit is not set."
In this example, since `x` is `8` (which has its third bit set), the output will
be "The 3rd bit is set."

OceanofPDF.com
3.3 BITWISE OPERATORS
IN CERTAIN
MATHEMATICAL
OPERATIONS
There are specific scenarios where bitwise operators can be used to achieve
certain mathematical operations or optimizations. Here are a few examples:
1. Multiplication by powers of two: Multiplication by powers of two can be
accomplished using left bit shifting (<<). Shifting a number to the left by n
positions is equivalent to multiplying it by 2 to the power of n. For
example, x << 3 is equivalent to x * 8.
2. Division by powers of two: Division by powers of two can be achieved
using right bit shifting (>>). Shifting a number to the right by n positions is
equivalent to dividing it by 2 to the power of n. For example, x >> 2 is
equivalent to x / 4.
3. Addition and subtraction of powers of two: Addition and subtraction of
powers of two can be accomplished using bitwise OR (|) and bitwise XOR
(^) operations. For example, adding x and y, where y is a power of two, can
be achieved using x | y. Similarly, subtracting y from x can be achieved
using x ^ y.

OceanofPDF.com
3.3.1 MULTIPLICATION BY
POWERS OF TWO
Multiplication by powers of two refers to multiplying a number by 2 raised
to some exponent. Left bit shifting provides a convenient way to achieve
this multiplication by shifting the bits of the number to the left.
Here's an example to illustrate the concept:
Suppose we have a variable `x` with an initial value of 5, represented in
binary as `00000101`. If we want to multiply `x` by 8 (which is 2 raised to
the power of 3), we can use left bit shifting as follows:

C:

int x = 5; // Binary: 00000101


int result = x << 3;

The expression `x << 3` means shifting the bits of `x` to the left by 3
positions. After the left shift, the value of `result` will be:

result:

Binary: 00000101 << 3


Result: 00101000

The binary representation `00101000` is equivalent to the decimal value 40.


Therefore, the operation `x << 3` effectively multiplies `x` by 8, yielding
the result of 40.
In general, left shifting a number by `n` positions is equivalent to
multiplying the number by 2 raised to the power of `n`. Each shift to the left
effectively doubles the value of the number. For example, shifting by 1 is
equivalent to multiplying by 2, shifting by 2 is equivalent to multiplying by
4, and so on.
It's important to note that left shifting is applicable for unsigned integer
types and signed integer types (as long as the left-shifted value does not
result in overflow). When dealing with signed integers, care must be taken
to handle potential sign extension or overflow scenarios.
Using left bit shifting for multiplication by powers of two provides a more
efficient and direct approach compared to using the arithmetic
multiplication operator (`*`). It can be particularly useful in scenarios where
performance optimizations or bit-level manipulations are required.

OceanofPDF.com
3.3.2 DIVISION BY
POWERS OF TWO
Division by powers of two refers to dividing a number by 2 raised to some
exponent. Right bit shifting provides a convenient way to achieve this
division by shifting the bits of the number to the right.
Here's an example to illustrate the concept:
Suppose we have a variable `x` with an initial value of 16, represented in
binary as `00010000`. If we want to divide `x` by 4 (which is 2 raised to the
power of 2), we can use right bit shifting as follows:

C:

int x = 16; // Binary: 00010000


int result = x >> 2;

The expression `x >> 2` means shifting the bits of `x` to the right by 2
positions. After the right shift, the value of `result` will be:
result:

Binary: 00010000 >> 2


Result: 00000100

The binary representation `00000100` is equivalent to the decimal value 4.


Therefore, the operation `x >> 2` effectively divides `x` by 4, yielding the
result of 4.
In general, right shifting a number by `n` positions is equivalent to dividing
the number by 2 raised to the power of `n`. Each shift to the right
effectively halves the value of the number. For example, shifting by 1 is
equivalent to dividing by 2, shifting by 2 is equivalent to dividing by 4, and
so on.
It's important to note that right shifting is applicable for unsigned integer
types and signed integer types. When dealing with signed integers, the
behavior of right shifting depends on the implementation-defined sign
extension. If the sign bit (the leftmost bit) is set, right shifting can perform
an arithmetic right shift (preserving the sign) or a logical right shift (filling
with zeros). The specific behavior may vary depending on the compiler and
the signedness of the integer type.
Using right bit shifting for division by powers of two provides a more
efficient and direct approach compared to using the arithmetic division
operator (`/`). It can be particularly useful in scenarios where performance
optimizations or bit-level manipulations are required.
Bit manipulation is a powerful technique that can lead to more efficient and
compact code. However, it can also be tricky to get right, so it's important
to understand it well and use it carefully. As you continue to learn C, you
will encounter more complex uses of bit manipulation and learn how to use
it to write more advanced programs.

OceanofPDF.com
3.3.3 ADDITION AND
SUBTRACTION OF
POWERS OF TWO
1. Addition of Powers of Two:
When adding a number `x` to a power of two `y`, where `y` represents a
single bit set to `1`, we can use the bitwise OR operation (`|`). Here's an
example:

C:

int x = 10; // Binary: 00001010


int y = 4; // Binary: 00000100 (2^2)

int result = x | y;

The expression `x | y` performs the bitwise OR operation between `x` and


`y`. This operation combines the bits of `x` and `y`, setting any bit where at
least one of the operands has a corresponding bit set. After the operation,
the value of `result` will be:
result:

Binary: 00001010 | 00000100


Result: 00001110

The binary representation `00001110` is equivalent to the decimal value 14.


Therefore, the operation `x | y` effectively adds the power of two `y` to `x`,
yielding the result of 14.
2. Subtraction of Powers of Two:
When subtracting a power of two `y` from a number `x`, where `y`
represents a single bit set to `1`, we can use the bitwise XOR operation
(`^`). Here's an example:

C:

int x = 15; // Binary: 00001111


int y = 8; // Binary: 00001000 (2^3)

int result = x ^ y;

The expression `x ^ y` performs the bitwise XOR operation between `x` and
`y`. This operation compares the corresponding bits of `x` and `y`, setting a
bit to `1` if the bits differ. After the operation, the value of `result` will be:
result:

Binary: 00001111 ^ 00001000


Result: 00000111

The binary representation `00000111` is equivalent to the decimal value 7.


Therefore, the operation `x ^ y` effectively subtracts the power of two `y`
from `x`, yielding the result of 7.
It's important to note that the examples given assume `y` represents a power
of two with only a single bit set. If `y` has multiple bits set, the bitwise
operations will produce different results.
Using bitwise OR (`|`) and bitwise XOR (`^`) operations for addition and
subtraction of powers of two can provide a more efficient and direct
approach compared to using the arithmetic operators (`+` and `-`). These
operations take advantage of the binary representation of numbers and can
be useful in scenarios involving bit-level manipulations and optimizations.

OceanofPDF.com
3.4 LIST, SET, AND MAP
Unlike languages such as Python or Java, C does not provide built-in
support for data structures like lists, sets, or maps. However, these data
structures can be implemented in C using its fundamental building blocks,
such as arrays for lists, and structures and pointers for sets and maps. In this
chapter, we will explore how to implement and use these data structures in
C.

List
A list is a collection of elements with a specific order. In C, we can use an
array to implement a list. Here's an example:

C:

#include <stdio.h>

int main() {
int list[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
printf("%d ", list[i]);
}
return 0;
}

In this example, we declare an array of integers and initialize it with five


elements. We then print out each element of the list.

Set
A set is a collection of unique elements. In C, we can implement a set using
an array along with some additional logic to ensure uniqueness. Here's a
simple example:
C:

#include <stdio.h>

int main() {
int set[5] = {1, 2, 3, 4, 5};
int value = 3;
int exists = 0;

for (int i = 0; i < 5; i++) {


if (set[i] == value) {
exists = 1;
break;
}
}

if (exists) {
printf("%d exists in the set\n", value);
} else {
printf("%d does not exist in the set\n", value);
}

return 0;
}

In this example, we declare an array of integers and initialize it with five


elements. We then check if a certain value exists in the set.

Map
A map, also known as a dictionary or associative array, is a collection of
key-value pairs. In C, we can implement a map using an array of structures,
where each structure represents a key-value pair. Here's an example:

C:

#include <stdio.h>
#include <string.h>
typedef struct {
char key[20];
int value;
} KeyValuePair;

int main() {
KeyValuePair map[2] = {{"apple", 1}, {"banana", 2}};

char key[20] = "banana";


int value = 0;

for (int i = 0; i < 2; i++) {


if (strcmp(map[i].key, key) == 0) {
value = map[i].value;
break;
}
}

if (value != 0) {
printf("The value of %s is %d\n", key, value);
} else {
printf("%s does not exist in the map\n", key);
}

return 0;
}

In this example, we declare an array of `KeyValuePair` structures and


initialize it with two key-value pairs. We then search for a certain key in the
map and print out its value.
Please note that these are simple implementations and may not be efficient
for large collections of data. For more efficient implementations, you might
want to use a linked list for the list, a hash table for the set, and a binary
search tree or hash table for the map. There are also libraries available, such
as the GLib library, which provide implementations of these data structures.
OceanofPDF.com
3.4.1 USAGE OF LIST, SET,
AND MAP WITH GLIB
GLib is a utility library that provides support for many data structures that
are not natively supported in C, such as lists, sets, and maps. In this chapter,
we will explore how to use these data structures in C with GLib, focusing
on adding, iterating, finding, updating, and deleting elements.
Before we start, you need to install GLib on your system and include the
necessary header files in your program:

C:

#include <glib.h>
#include <glib/gprintf.h>

List
In GLib, lists are implemented as doubly-linked lists. Here's an example of
how to use a `GList`:

C:

// Create a list
GList* list = NULL;
list = g_list_append(list, "Hello");
list = g_list_append(list, "World");

// Iterate over the list


for (GList* iter = list; iter != NULL; iter = iter->next) {
g_printf("%s ", (char*)iter->data);
}
// Find an element in the list
GList* element = g_list_find_custom(list, "World", (GCompareFunc)g_strcmp0);
if (element != NULL) {
g_printf("\nFound: %s\n", (char*)element->data);
}

// Update an element in the list


element->data = "GLib";

// Delete an element from the list


list = g_list_remove(list, "Hello");

// Print the updated list


for (GList* iter = list; iter != NULL; iter = iter->next) {
g_printf("%s ", (char*)iter->data);
}

// Free the list


g_list_free(list);

Set
GLib does not have a specific set data structure, but it can be easily
implemented using a `GHashTable`. Here's an example:

C:

// Create a set
GHashTable* set = g_hash_table_new(g_str_hash, g_str_equal);

// Add elements to the set


g_hash_table_add(set, "Hello");
g_hash_table_add(set, "World");

// Check if an element exists in the set


gboolean exists = g_hash_table_contains(set, "Hello");
g_printf("\nHello %s in the set\n", exists ? "exists" : "does not exist");
// Remove an element from the set
g_hash_table_remove(set, "Hello");

// Check again if the element exists in the set


exists = g_hash_table_contains(set, "Hello");
g_printf("Hello %s in the set\n", exists ? "exists" : "does not exist");

// Destroy the set


g_hash_table_destroy(set);

Map
In GLib, maps are implemented as hash tables. Here's an example of how to
use a `GHashTable`:

C:

// Create a map
GHashTable* map = g_hash_table_new(g_str_hash, g_str_equal);

// Insert key-value pairs into the map


g_hash_table_insert(map, "apple", GINT_TO_POINTER(1));
g_hash_table_insert(map, "banana", GINT_TO_POINTER(2));

// Look up the value of a key in the map


int value = GPOINTER_TO_INT(g_hash_table_lookup(map, "banana"));
g_printf("\nThe value of banana is %d\n", value);

// Update a value in the map


g_hash_table_insert(map, "banana", GINT_TO_POINTER(3));

// Look up the updated value


value = GPOINTER_TO_INT(g_hash_table_lookup(map, "banana"));
g_printf("The updated value of banana is %d\n", value);
// Remove a key-value pair from the map
g_hash_table_remove(map, "apple");

// Check if the key exists in the map


gboolean exists = g_hash_table_contains(map, "apple");
g_printf("apple %s in the map\n", exists ? "exists" : "does not exist");

// Destroy the map


g_hash_table_destroy(map);

In this example, we create a `GHashTable`, insert two key-value pairs into


it, look up the value of a certain key in the map, update a value in the map,
remove a key-value pair from the map, check if a key exists in the map, and
finally destroy the map.
Please note that GLib uses pointers to store data in these data structures, so
you need to be careful with memory management. In these examples, we
use strings and integers that are not dynamically allocated, but if you store
dynamically allocated data in these data structures, you need to ensure that
the data is properly freed when it is no longer needed.

OceanofPDF.com
3.5 MEASURING
EXECUTION TIME AND
CLOCK TICKS
Understanding the performance of your program is crucial in software
development. It allows you to identify bottlenecks, optimize your code, and
ensure that your program runs efficiently. In C, you can measure the
execution time and clock ticks of a function using the `clock()` function and
the `CLOCKS_PER_SEC` macro provided by the `time.h` library.

Measuring Execution Time


The `clock()` function returns the processor time consumed by the program,
which is the sum of the CPU time spent in user mode and kernel mode. The
`CLOCKS_PER_SEC` macro gives the number of clock ticks per second.
By dividing the processor time by `CLOCKS_PER_SEC`, you can get the
execution time in seconds.
Here's an example of how to measure the execution time of a function:

C:

#include <stdio.h>
#include <time.h>

void some_function() {
for (long int i = 0; i < 1000000000; i++); // Some time-consuming task
}

int main() {
clock_t start, end;
double cpu_time_used;
start = clock();

some_function(); // Call the function whose execution time you want to measure

end = clock();

cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;

printf("The function took %f seconds to execute.\n", cpu_time_used);

return 0;
}

In this example, we first get the current processor time before the function
call using `clock()`. We then call the function and get the processor time
again after the function call. The difference between the end time and the
start time gives the processor time consumed by the function. Dividing this
by `CLOCKS_PER_SEC` gives the execution time in seconds.
`CLOCKS_PER_SEC` is a macro in C that represents the number of
processor clock ticks per second. It is defined in the `time.h` header file.
When you call the `clock()` function in C, it returns the processor time your
program has used. This is measured in clock ticks, which are units of time
of a constant but system-specific length, with respect to a certain time point.
However, to convert this measure to seconds, you need to know how many
clock ticks make up one second. That's what `CLOCKS_PER_SEC` gives
you. By dividing the number of clock ticks by `CLOCKS_PER_SEC`, you
can calculate the processor time used in seconds.
For example, if `clock()` returns 3000 and `CLOCKS_PER_SEC` is 1000,
then your program has used 3000 / 1000 = 3 seconds of processor time.
It's important to note that `CLOCKS_PER_SEC` does not change during
the execution of a program and is constant across all executions of all
programs on the same system. However, it can vary between systems. On
most systems, `CLOCKS_PER_SEC` is defined to be 1000000, meaning
that the `clock()` function returns the number of microseconds.
Please note that the `clock()` function and the `CLOCKS_PER_SEC` macro
measure processor time, not wall clock time. If your program is
multithreaded or if other processes are running on your system, the
processor time might not correspond to the real time. If you need to
measure real time, consider using other functions such as `gettimeofday()`
or `clock_gettime()`.

OceanofPDF.com
3.6 SOCKET
PROGRAMMING: TCP/IP
AND UDP
Transmission Control Protocol/Internet Protocol (TCP/IP) and User
Datagram Protocol (UDP) are two of the most commonly used protocols in
the realm of Internet communication. They both operate at the transport
layer of the Internet Protocol Suite, often referred to as TCP/IP, and are
used to send bits of data — known as packets — over the Internet.
However, they do so in very different ways and are used for different types
of communication.

TCP/IP
TCP is a connection-oriented protocol, meaning it first establishes a
connection with the recipient before sending any data. This connection
ensures that all data packets arrive at the destination in the correct order.
This is why TCP is used for tasks where order and reliability are important,
such as loading a webpage or sending an email.

Key features of TCP:


- Reliability: TCP ensures that all packets arrive at the destination and in the
correct order. If a packet is lost during transmission, TCP will retransmit the
packet.
- Ordered Packets: TCP rearranges data packets in the order they were sent.
- Error Checking: TCP includes error checking and error recovery routines.
Corrupted data is automatically retransmitted.
- Flow Control: TCP manages data transmission between devices to prevent
receiver overflow.
UDP
Unlike TCP, UDP is a connectionless protocol. It sends packets without
establishing a connection, resulting in a faster transmission but with no
guarantee that the packets will reach their destination or that they will do so
in the correct order. This is why UDP is used for tasks where speed is more
important than reliability, such as streaming live video or playing online
games.

Key features of UDP:


- Speed: Without the need to establish a connection, UDP is faster and more
efficient than TCP for many lightweight or time-sensitive applications.
- No Error Recovery: UDP does not provide error recovery. If a UDP packet
is lost, it is lost forever.
- No Ordering of Packets: If order is important, you'll need to manage this
at the application layer.
- Datagram Oriented: UDP is suitable for applications that need fast,
efficient transmission, such as games. UDP's stateless nature is also useful
for servers that answer small queries from huge numbers of clients.

Comparison
While both TCP and UDP are used for transmitting packets of data over the
Internet, they are used in different scenarios due to their distinct
characteristics. TCP is reliable and ensures that your data will arrive in
order, but it is slower and requires more resources. On the other hand, UDP
is not reliable, does not guarantee order, and does not establish a
connection, but it is faster. The choice between TCP and UDP depends on
what you need for your application.

In summary, understanding TCP/IP and UDP is crucial when working with


network programming, as the choice of protocol can significantly impact
the performance and reliability of your applications.

OceanofPDF.com
3.6.1 TCP/IP PROTOCOL
TCP/IP (Transmission Control Protocol/Internet Protocol) is the suite of
communications protocols used to connect hosts on the Internet. TCP/IP
uses several protocols, the two main ones being TCP and IP. TCP/IP is built
into the UNIX operating system and is used by the Internet, making it the
de facto standard for transmitting data over networks.
In this chapter, we will implement a simple TCP server-client program in C.
The server will accept a message from the client, print it, and then send a
response back.

OceanofPDF.com
3.6.1.1 TCP SERVER
The server creates a socket and listens for a client to make a connection
request. Once a client connects, the server receives data from the client and
sends a response back to the client. Here is a simple implementation of a
TCP server in C:

C:

#include <stdio.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h> // read(), write(), close()

#define MAX 80
#define PORT 8080
#define SA struct sockaddr

void func(int connfd){


char buff[MAX];
int n;
for (;;) {
bzero(buff, MAX);
read(connfd, buff, sizeof(buff));
printf("From client: %s\t To client : ", buff);
bzero(buff, MAX);
n = 0;
while ((buff[n++] = getchar()) != '\n')
;
write(connfd, buff, sizeof(buff));
if (strncmp("exit", buff, 4) == 0) {
printf("Server Exit...\n");
break;
}
}
}

int main(){
int sockfd, connfd, len;
struct sockaddr_in servaddr, cli;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0) {
printf("socket bind failed...\n");
exit(0);
}
else
printf("Socket successfully binded..\n");
if ((listen(sockfd, 5)) != 0) {
printf("Listen failed...\n");
exit(0);
}
else
printf("Server listening..\n");
len = sizeof(cli);
connfd = accept(sockfd, (SA*)&cli, &len);
if (connfd < 0) {
printf("server accept failed...\n");
exit(0);
}
else
printf("server accept the client...\n");
func(connfd);
close(sockfd);
}

OceanofPDF.com
3.6.1.2 TCP CLIENT
The client creates a socket, makes a connection request to the server, sends
data, and waits for a response. Here is a simple implementation of a TCP
client in C:

C:

#include <arpa/inet.h> // inet_addr()


#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h> // bzero()
#include <sys/socket.h>
#include <unistd.h> // read(), write(), close()

#define MAX 80
#define PORT 8080
#define SA struct sockaddr

void func(int sockfd){


char buff[MAX];
int n;
for (;;) {
bzero(buff, sizeof(buff));
printf("Enter the string : ");
n = 0;
while ((buff[n++] = getchar()) != '\n')
;
write(sockfd, buff, sizeof(buff));
bzero(buff, sizeof(buff));
read(sockfd, buff, sizeof(buff));
printf("From Server : %s", buff);
if ((strncmp(buff, "exit", 4)) == 0) {
printf("Client Exit...\n");
break;
}
}
}

int main(){
int sockfd, connfd;
struct sockaddr_in servaddr, cli;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(PORT);
if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr))
!= 0) {
printf("connection with the server failed...\n");
exit(0);
}
else
printf("connected to the server..\n");
func(sockfd);
close(sockfd);
}
To compile and run these programs, you can use the following commands:
Server side:

bash:

gcc server.c -o server


./server

Client side:

bash:

gcc client.c -o client


./client

The server will print the messages it receives from the client and the client
will print the responses it receives from the server. When the client sends
the message "exit", both the client and the server will terminate.
This is a basic example of how to use the TCP/IP protocol in C. In real-
world applications, you would need to handle errors and edge cases more
robustly, and you might need to use additional features of the TCP/IP
protocol, such as handling multiple clients simultaneously.
Above examples are using read() and write() methods which are general-
purpose functions for reading from file descriptors and writing to file
descriptors.
Let’s try now to use the key functions like `send()`, and `recv()` which are
specially designed for sockets. We will focus on how to handle sending and
receiving messages that are larger than the buffer size.

Server-side implementation
Let's start with the server-side implementation:

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080


#define BUFFER_SIZE 1024

int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};

if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {


perror("socket failed");
exit(EXIT_FAILURE);
}

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,


sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

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);
}

if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {


perror("accept");
exit(EXIT_FAILURE);
}

int valread;
while ((valread = recv(new_socket, buffer, BUFFER_SIZE, 0)) > 0) {
buffer[valread] = '\0'; // Ensure null-termination for string processing
printf("%s\n", buffer);
}

if (valread < 0) {
perror("recv");
exit(EXIT_FAILURE);
}

char *large_message = "A really large message that exceeds the buffer size...";
int total_sent = 0;
int length = strlen(large_message);
while (total_sent < length) {
int sent = send(new_socket, large_message + total_sent, length - total_sent, 0);
if (sent < 0) {
perror("send");
exit(EXIT_FAILURE);
}
total_sent += sent;
}

printf("Large message sent\n");


return 0;
}

This server reads incoming messages with a while-loop around `recv()` and
sends a large message with a while-loop around `send()`.

Client-side implementation
Now, let's look at the client-side implementation:

C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080


#define BUFFER_SIZE 1024

int main() {
struct sockaddr_in address;
int sock = 0;
struct sockaddr_in serv_addr;
char *large_message = "A really large message that exceeds the buffer size...";
char buffer[BUFFER_SIZE] = {0};

if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {


printf("\n Socket creation error \n");
return -1;
}

memset(&serv_addr,

'0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);

if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {


printf("\nInvalid address/ Address not supported \n");
return -1;
}

if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {


printf("\nConnection Failed \n");
return -1;
}

int total_sent = 0;
int length = strlen(large_message);
while (total_sent < length) {
int sent = send(sock, large_message + total_sent, length - total_sent, 0);
if (sent < 0) {
perror("send");
exit(EXIT_FAILURE);
}
total_sent += sent;
}

printf("Large message sent\n");

int valread;
while ((valread = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {
buffer[valread] = '\0'; // Ensure null-termination for string processing
printf("%s\n", buffer);
}

if (valread < 0) {
perror("recv");
exit(EXIT_FAILURE);
}
return 0;
}

The client sends a large message to the server with a while-loop around
`send()` and receives a large message back from the server with a while-
loop around `recv()`.

Read and Recv


The `read()` and `recv()` functions are both used to receive data from a file
descriptor, but there are a few differences between them due to the different
contexts they are used in.
1. Designed for different interfaces: The `read()` function is a system call
that is designed to read data from a variety of file descriptors, including
files, pipes, and sockets. On the other hand, `recv()` is specifically designed
for use with socket file descriptors.
2. Flags parameter: The `recv()` function has a `flags` parameter that the
`read()` function does not have. This parameter can be used to modify the
behavior of `recv()`, for example to peek at incoming data without
removing it from the queue (`MSG_PEEK`), to wait until the full amount of
data can be returned (`MSG_WAITALL`), and so on.
3. Return on connection close: If the other end of a TCP connection is
closed gracefully and there is no more data to read, `read()` will return 0,
indicating an end-of-file condition. In the same scenario, `recv()` will also
return 0.
4. Error handling: Both `read()` and `recv()` will return -1 if an error occurs,
but the types of errors they can encounter are different due to the different
contexts they are used in.
Despite these differences, in many common scenarios, `read()` and `recv()`
can be used interchangeably. The choice between `read()` and `recv()` often
depends on the specific requirements of your code. If you are working with
sockets and need to use the `flags` parameter, or if you are writing code that
should only work with sockets and not other types of file descriptors, then
`recv()` is the appropriate choice. On the other hand, if you are writing code
that should work with any type of file descriptor, or if you don't need the
additional functionality provided by `recv()`, then `read()` is a simpler and
more general solution.

Send and Write


`send()` and `write()` are both functions used to send data over a
connection. However, they are used in slightly different contexts and have
some differences in functionality.

1. Designed for different interfaces: `write()` is a system call that is


designed to write data to a variety of file descriptors, including files, pipes,
and sockets. On the other hand, `send()` is specifically designed for use
with socket file descriptors.
2. Flags parameter: The `send()` function has a `flags` parameter that the
`write()` function does not have. This parameter can be used to modify the
behavior of `send()`. For example, the `MSG_NOSIGNAL` flag can be
used to prevent `send()` from raising a `SIGPIPE` signal if the other end of
the connection is closed. The `MSG_DONTROUTE` flag can be used to
bypass the standard routing facilities. The `MSG_CONFIRM` flag tells the
link layer that forward progress happened: you got a successful reply from
the other side. If the link layer doesn't get this it will regularly reprobe the
neighbor (e.g., via a unicast ARP).
3. Error handling: Both `send()` and `write()` will return -1 if an error
occurs. However, the types of errors they can encounter are different due to
the different contexts they are used in.
4. Return on connection close: If the other end of a TCP connection is
closed gracefully, `write()` will return -1 and set `errno` to `EPIPE`. In the
same scenario, `send()` will return -1 and set `errno` to `ECONNRESET`,
or if the `MSG_NOSIGNAL` flag was set, it will return the number of
bytes sent.
Despite these differences, `send()` and `write()` can be used
interchangeably in many cases when working with TCP sockets. The choice
between `send()` and `write()` often depends on the specific requirements
of your code. If you need the additional functionality provided by the
`flags` parameter, then `send()` is the appropriate choice. Otherwise,
`write()` can be a simpler solution.
These examples demonstrate how to handle large messages with TCP/IP
sockets in C. Remember, TCP does not preserve message boundaries, so it's
necessary to implement some kind of message framing protocol if you need
to preserve individual messages in your application.

OceanofPDF.com
3.6.2 UDP PROTOCOL
In this chapter, we will demonstrate the User Datagram Protocol (UDP), a
core member of the Internet Protocol Suite. It uses a simple connectionless
communication model with a minimum of protocol mechanism. UDP
provides checksums for data integrity, and port numbers for addressing
different functions at the source and destination of the datagram.
We will create a simple UDP client and server. The client will send a
message, and the server will receive and print it.

OceanofPDF.com
3.6.2.1 UDP SERVER
Let's begin with the server:

C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>

#define MAX_BUFFER 1024


#define PORT 8080

int main() {
char buffer[MAX_BUFFER];
char *message = "Hello Client";

struct sockaddr_in serverAddr, clientAddr;

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);


if (sockfd < 0) {
perror("Failed to create socket");
exit(EXIT_FAILURE);
}

memset(&serverAddr, 0, sizeof(serverAddr));
memset(&clientAddr, 0, sizeof(clientAddr));

serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(PORT);
if (bind(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
perror("Failed to bind");
exit(EXIT_FAILURE);
}

int len, n;
len = sizeof(clientAddr);
n = recvfrom(sockfd, (char *)buffer, MAX_BUFFER, MSG_WAITALL, ( struct sockaddr *)
&clientAddr, &len);
buffer[n] = '\0';
printf("Client : %s\n", buffer);

sendto(sockfd, (const char *)message, strlen(message), MSG_CONFIRM, (const struct sockaddr


*) &clientAddr, len);
printf("Hello message sent.\n");

return 0;
}

Above, the server creates a socket with `socket()`, binds it to a specific IP


address and port with `bind()`, waits to receive messages from a client with
`recvfrom()`, and sends a response back to the client with `sendto()`.

OceanofPDF.com
3.6.2.2 UDP CLIENT
Now, let's look at the client:

C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>

#define MAX_BUFFER 1024


#define PORT 8080

int main() {
char buffer[MAX_BUFFER];
char *message = "Hello Server";

struct sockaddr_in serverAddr;

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);


if (sockfd < 0) {
perror("Failed to create socket");
exit(EXIT_FAILURE);
}

memset(&serverAddr, 0, sizeof(serverAddr));

serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = INADDR_ANY;

int n, len;
sendto(sockfd, (const char *)message, strlen(message), MSG_CONFIRM, (const struct sockaddr
*) &serverAddr, sizeof(serverAddr));
printf("Hello message sent.\n");

n = recvfrom(sockfd, (char *)buffer, MAX_BUFFER, MSG_WAITALL, (struct sockaddr *)


&serverAddr, &len);
buffer[n] = '\0';
printf("Server : %s\n", buffer);

shutdown(sockfd, SHUT_RDWR);
return 0;
}

The client also creates a socket with `socket()`, sends a message to the
server with `sendto()`, and waits to receive a response back from the server
with `recvfrom()`.
Please note that you need to run the server program before the client
program for the communication to work properly.

In this example, we used `INADDR_ANY` for the IP address to specify


that the socket should bind to all available local IP addresses. This makes it
easier to run the example on any machine without needing to know the
machine's specific IP address. In a real-world situation, you would likely
want to use a specific IP address instead.
Remember to compile your code with `gcc` and run the resultant output file.
If your source file is named `udp_server.c` or `udp_client.c`, you would
compile it like this:

bash:
gcc udp_server.c -o server
gcc udp_client.c -o client

And then run it with:

bash:

./server
./client

In separate terminal windows.


This is a simple example of UDP communication. UDP is used in scenarios
where speed is more crucial than reliability such as streaming audio and
video.

OceanofPDF.com
3.7 UNDERSTANDING THE
HTTP PROTOCOL
HTTP, or Hypertext Transfer Protocol, is a protocol used for transferring
hypertext requests and information between servers and browsers. HTTP is
the foundation of data communication on the World Wide Web.

Overview of HTTP
HTTP is a client-server protocol: requests are sent by one entity, the user-
agent (or, more typically, a web browser), and responses are received from
another, the origin server. HTTP is a stateless protocol, meaning each
command is executed separately, without any knowledge of the commands
that came before it.
HTTP uses a number of different methods, also known as verbs, to indicate
the desired action to be performed on the identified resource. Some of the
most commonly used HTTP methods include:
- GET: Requests a representation of the specified resource. Requests using
GET should only retrieve data and should have no other effect.
- POST: Sends data to the server for a new resource. It often causes a
change in state or side effects on the server.
- PUT: Replaces all the current representations of the target resource with
the uploaded content.
- DELETE: Deletes the specified resource.

HTTP Request
An HTTP request message is composed of the request line (which includes
the method, URI, and HTTP version), request headers, an empty line, and
an optional message body. Here's an example of an HTTP request to get an
HTML page:
GET /index.html HTTP/1.1
Host: www.example.com

In this example, "GET" is the HTTP method, "/index.html" is the resource


being requested, and "HTTP/1.1" is the version of HTTP being used. The
"Host" header line is used to specify the domain name of the site being
requested.

HTTP Response
An HTTP response message is composed of the status line (which includes
the HTTP version, status code, and status description), response headers, an
empty line, and the message body. Here's an example of an HTTP response
message:

HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
Content-Length: 88
Content-Type: text/html
Connection: Closed

<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>

The status line "HTTP/1.1 200 OK" indicates that the server is using
HTTP/1.1, and the status of the response is 200 (OK). The response headers
provide additional information about the response, such as the date and time
of the response, server details, and the content type and length. After the
headers and an empty line, the message body contains the requested
resource (in this case, an HTML document).
HTTP Status Codes
HTTP response status codes indicate whether a specific HTTP request has
been successfully completed. They are divided into five classes:
- 1xx (Informational): The request was received, and the process is
continuing.
- 2xx (Successful): The request was successfully received, understood, and
accepted. For example, 200 OK is the standard response for successful
HTTP requests.
- 3xx (Redirection): Further action needs to be taken to complete the
request.
- 4xx (Client Error): The request contains bad syntax or cannot be fulfilled.
For example, 404 Not Found means that the requested resource could not be
found on the server.
- 5xx (Server Error): The server failed to fulfill a valid request. For
example, 500 Internal Server Error means that an unexpected condition was
encountered and no more specific message is suitable.

Summary
In this chapter, we've provided a high-level overview of HTTP. HTTP is the
backbone of any data exchange on the Web, and understanding how it
works can be hugely beneficial for anyone working in web development or
networking.

OceanofPDF.com
3.7.1 SENDING HTTP
(CRUD) METHODS WITH
SOCKETS
HTTP is built on top of the TCP protocol. The client establishes a TCP
connection with the server, then sends HTTP requests over that connection,
and the server sends HTTP responses back. In this chapter, we'll
demonstrate how to create a simple HTTP client in pure C, using only
standard libraries, that can send GET, POST, PUT, and DELETE requests.

HTTP GET Method


Let's start with the GET method. Here's a simple function that sends an
HTTP GET request to a server and prints out the response:

C:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAX_BUFFER_SIZE 4096

void http_get(int socket_fd, const char *path) {


char buffer[MAX_BUFFER_SIZE];

sprintf(buffer, "GET %s HTTP/1.1\r\nHost: localhost\r\n\r\n", path);


send(socket_fd, buffer, strlen(buffer), 0);
memset(buffer, 0, sizeof(buffer));
while (recv(socket_fd, buffer, sizeof(buffer), 0) > 0) {
fputs(buffer, stdout);
}
}

int main() {
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &(server_addr.sin_addr));

connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));

http_get(socket_fd, "/index.html");

close(socket_fd);

return 0;
}

In this example, we establish a TCP connection to the server using


`socket()`, `connect()`, then we send an HTTP GET request using `send()`
and receive the response using `recv()`.

HTTP POST Method


Here's an example that sends an HTTP POST request:

C:

void http_post(int socket_fd, const char *path, const char *body) {


char buffer[MAX_BUFFER_SIZE];

sprintf(buffer, "POST %s HTTP/1.1\r\nHost: localhost\r\nContent-Length: %zu\r\n\r\n%s",


path, strlen(body), body);
send(socket_fd, buffer, strlen(buffer), 0);

memset(buffer, 0, sizeof(buffer));
while (recv(socket_fd, buffer, sizeof(buffer), 0) > 0) {
fputs(buffer, stdout);
}
}

// In main function, replace http_get with the following:


http_post(socket_fd, "/api/items", "{\"name\":\"My Item\"}");

This function is similar to `http_get()`, but it also includes a message body


in the request, and it includes a "Content-Length" header that specifies the
length of the body.

HTTP PUT Method


The PUT method is very similar to the POST method. Here's an example:

C:

void http_put(int socket_fd, const char *path, const char *body) {


char buffer[MAX_BUFFER_SIZE];

sprintf(buffer, "PUT %s HTTP/1.1\r\nHost: localhost\r\nContent-Length: %zu\r\n\r\n%s",


path, strlen(body), body);
send(socket_fd, buffer, strlen(buffer), 0);

memset(buffer, 0, sizeof(buffer));
while (recv(socket_fd, buffer, sizeof(buffer), 0) > 0) {
fputs(buffer, stdout);
}
}

// In main function
, replace http_post with the following:
http_put(socket_fd, "/api/items/1", "{\"name\":\"Updated Item\"}");

HTTP DELETE Method


Finally, the DELETE method:

C:

void http_delete(int socket_fd, const char *path) {


char buffer[MAX_BUFFER_SIZE];

sprintf(buffer, "DELETE %s HTTP/1.1\r\nHost: localhost\r\n\r\n", path);


send(socket_fd, buffer, strlen(buffer), 0);

memset(buffer, 0, sizeof(buffer));
while (recv(socket_fd, buffer, sizeof(buffer), 0) > 0) {
fputs(buffer, stdout);
}
}

// In main function, replace http_put with the following:


http_delete(socket_fd, "/api/items/1");

The DELETE method is similar to the GET method, but it uses the
DELETE verb instead of GET. It does not include a message body.
Note: This is a very basic HTTP client. It doesn't handle HTTP redirects, it
doesn't support HTTPS, and it doesn't do any error checking. Also, this
code assumes that the server will close the connection after sending the
response, which is typical for simple HTTP/1.1 servers. In a real-world
application, you would want to use a full-featured HTTP library, such as
libcurl, which can handle all these details for you.

OceanofPDF.com
3.7.2 PROCESSING HTTP
(CRUD) METHODS WITH
SOCKETS
In this chapter, we will learn how to create a basic HTTP server in C using
the standard socket libraries. This server will be able to handle basic GET,
POST, PUT, and DELETE requests. Note that creating a full-featured and
secure HTTP server is a complex task that requires a lot of careful
engineering. In practice, it's best to use a proven HTTP server like Apache
or Nginx. However, for educational purposes, we can create a simple one.

Creating the Server


The first step is to create a server socket that listens for incoming
connections. We then accept connections from clients, receive their
requests, and send back responses.

C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_BUFFER_SIZE 2048


#define PORT 8080

int main() {
int server_fd, client_fd;
struct sockaddr_in address;
int address_len = sizeof(address);
char buffer[MAX_BUFFER_SIZE] = {0};

// Create socket file descriptor


if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

// Bind socket to port


if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}

// Start listening for client connections


if (listen(server_fd, 3) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}

while(1) {
printf("\n+++++++ Waiting for new connection ++++++++\n\n");

// Accept a client connection


if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&address_len)) < 0)
{
perror("Accept failed");
exit(EXIT_FAILURE);
}
// Read the client's request
read(client_fd, buffer, MAX_BUFFER_SIZE);
printf("%s\n", buffer);

// TODO: Handle the client's request and send back a response

close(client_fd);
}

return 0;
}

In the code above, our server simply reads the client's request and prints it
out. To actually handle the client's request, we need to parse it and
determine which HTTP method the client wants to use.

Handling HTTP CRUD Requests in plain C


To handle an HTTP request, we need to parse the request line (the first line
of the request) and extract the HTTP method (GET, POST, PUT, DELETE),
the request URI, and the HTTP version. For simplicity, let's assume that the
request line is correctly formed. In a real server, we would need to handle
the case where the client sends a malformed request.
We'll use a `switch` statement to determine which HTTP method the client
wants to use. Here's how we can handle GET and DELETE requests:

C:

// Parse the HTTP method, the request URI, and the HTTP version
char method[16], uri[256], version[16];
sscanf(buffer, "%s %s %s", method, uri, version);

// Handle the client's request based on the HTTP method


if (strcmp(method, "GET") == 0) {
// TODO: Handle GET request
} else if (strcmp(method, "POST") == 0) {
// TODO: Handle POST request
} else if (strcmp(method, "PUT") == 0) {
// TODO: Handle PUT request
} else if (strcmp(method, "DELETE") == 0) {
// TODO: Handle DELETE request
} else {
printf("Unknown HTTP method: %s\n", method);
}

In this example, we haven't implemented the handling of the HTTP methods


yet. You would replace the `TODO` comments with code to handle each
type of HTTP request. This could involve retrieving a resource for a GET
request, updating a resource for a PUT request, creating a new resource for
a POST request, or deleting a resource for a DELETE request.
Keep in mind that POST and PUT requests often have a body that contains
data for the server to process. This could be in the form of form data or a
JSON payload. In our simple server, we could parse the body of the request
by looking for a blank line (indicating the end of the headers) and reading
everything after that line.
Here we have simple example how we might handle (process) the requests.
Let's assume our server only handles requests to the root URL ("/") and
responds with a simple text message. Also, to keep things simple, let's
ignore the request body for POST and PUT requests, even though these
methods typically include a body.

C:

if (strcmp(method, "GET") == 0) {
// Handle GET request
if (strcmp(uri, "/") == 0) {
char *response = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello, GET!";
send(client_fd, response, strlen(response), 0);
} else {
char *response = "HTTP/1.1 404 Not Found\n";
send(client_fd, response, strlen(response), 0);
}
} else if (strcmp(method, "POST") == 0) {
// Handle POST request
if (strcmp(uri, "/") == 0) {
char *response = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello, POST!";
send(client_fd, response, strlen(response), 0);
} else {
char *response = "HTTP/1.1 404 Not Found\n";
send(client_fd, response, strlen(response), 0);
}
} else if (strcmp(method, "PUT") == 0) {
// Handle PUT request
if (strcmp(uri, "/") == 0) {
char *response = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello, PUT!";
send(client_fd, response, strlen(response), 0);
} else {
char *response = "HTTP/1.1 404 Not Found\n";
send(client_fd, response, strlen(response), 0);
}
} else if (strcmp(method, "DELETE") == 0) {
// Handle DELETE request
if (strcmp(uri, "/") == 0) {
char *response = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello, DELETE!";
send(client_fd, response, strlen(response), 0);
} else {
char *response = "HTTP/1.1 404 Not Found\n";
send(client_fd, response, strlen(response), 0);
}
} else {
printf("Unknown HTTP method: %s\n", method);
char *response = "HTTP/1.1 400 Bad Request\n";
send(client_fd, response, strlen(response), 0);
}
In this code, for each HTTP method, we check if the URI is the root ("/"). If
it is, we send a 200 OK response with a simple greeting. If the URI is not
the root, we send a 404 Not Found response. If the HTTP method is not one
of the four we handle, we send a 400 Bad Request response. Remember that
in a real HTTP server, you'd typically have many more routes, and you'd
probably want to use a router to handle them.
To handle the body of a POST or PUT request, we first need to parse the
headers to find the `Content-Length` header, which tells us how much data
to read for the body. Here's a very simple way to do this:

C:

// Find the Content-Length header (this is not a robust way to parse HTTP headers,
// but it's okay for this simple example)
char *content_length_ptr = strstr(buffer, "Content-Length: ");
if (content_length_ptr != NULL) {
int content_length = atoi(content_length_ptr + 16); // Skip past "Content-Length: "

// Find the start of the body (again, this is not a robust method)
char *body_ptr = strstr(buffer, "\r\n\r\n");
if (body_ptr != NULL) {
body_ptr += 4; // Skip past "\r\n\r\n"

// Read the rest of the body


if (content_length > 0) {
char body[MAX_BUFFER_SIZE] = {0};
strncpy(body, body_ptr, strlen(body_ptr));

// Now body contains the body of the request


printf("Body: %s\n", body);
}
}
}
You can add this code before handling each method in our previous
example. This code first tries to find the `Content-Length` header in the
request. If it finds it, it parses the value (the part after "Content-Length: ")
and converts it to an integer. Then it finds the start of the body (after the
"\r\n\r\n" that separates the headers from the body), and reads the rest of the
body from the socket.
In the POST and PUT sections, you can now do something with the body,
for example, printing it out, or writing it to a file.
Again, note that this is a very simplified example. In a real HTTP server,
you would need to handle things like chunked transfer encoding, different
types of content (not just text), and large amounts of data that need to be
read in multiple calls to `recv()`. In addition, you would need to properly
sanitize and validate the input data to protect against attacks.

OceanofPDF.com
3.7.3 SENDING HTTP
(CRUD) METHODS USING
LIBCURL
libcurl is a powerful library for transfering data with URLs. It supports a
variety of protocols, including HTTP and HTTPS, and provides
functionality for multiple HTTP methods. In this chapter, we will
demonstrate how to use libcurl in C to implement the HTTP GET, POST,
PUT, and DELETE methods, including methods that use JSON payloads.
Firstly, ensure that libcurl is installed on your system. On a Ubuntu
machine, you can do this using the following command:

bash:
sudo apt-get install libcurl4-openssl-dev

Remember to link against the libcurl library when compiling your program
with `-lcurl`.

HTTP GET Method


Performing an HTTP GET request using libcurl is simple:

C:

#include <stdio.h>
#include <curl/curl.h>

int main() {
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/items");
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
return 0;
}

HTTP POST Method (with JSON)


Performing an HTTP POST request with a JSON payload requires setting
the appropriate headers and POST data:

C:

#include <stdio.h>
#include <curl/curl.h>

int main() {
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;
struct curl_slist *headers = NULL;

headers = curl_slist_append(headers, "Content-Type: application/json");

curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/items");


curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"name\":\"NewItem\"}");

res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));

curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
return 0;
}

HTTP PUT Method (with JSON)


Similar to the POST method, the PUT method can be implemented with a
JSON payload:

C:

#include <stdio.h>
#include <curl/curl.h>

int main() {
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;
struct curl_slist *headers = NULL;

headers = curl_slist_append(headers, "Content-Type: application/json");

curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/items/1");


curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"name\":\"UpdatedItem\"}");

res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
return 0;
}

HTTP DELETE Method


The DELETE method can be used to remove a specified resource:

C:

#include <stdio.h>
#include <curl/curl.h>

int main() {
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;

curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8080/api/items/1");


curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");

res

= curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));

curl_easy_cleanup(curl);
}
return 0;
}
This concludes our chapter on HTTP methods in C using libcurl, including
the use of JSON payloads. As always, be sure to check the return value of
`curl_easy_perform()` for any errors and clean up with
`curl_easy_cleanup()`.

OceanofPDF.com
3.7.4 PROCESSING HTTP
(CRUD) METHODS WITH
LIBEVENT
Creating an HTTP server in C language usually requires a low-level
manipulation of sockets which can be quite complex. Fortunately, libraries
like libevent or libuv can help us to deal with this complexity. In this
example, we will use libevent, which is a robust, high-performance event
notification library. Please note, libevent is not designed for creating HTTP
servers specifically, but it has an HTTP layer which can be used for this
purpose.
The following server is able to handle GET, POST, PUT, and DELETE
requests:

C:

#include <stdio.h>
#include <string.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>

void generic_handler(struct evhttp_request *req, void *arg) {


struct evbuffer *returnbuffer = evbuffer_new();

// For POST and PUT requests, extract the body


if (evhttp_request_get_command(req) == EVHTTP_REQ_POST ||
evhttp_request_get_command(req) == EVHTTP_REQ_PUT) {
struct evbuffer *input_buffer;
size_t len;
char *body;

input_buffer = evhttp_request_get_input_buffer(req);
len = evbuffer_get_length(input_buffer);
body = (char *)evbuffer_pullup(input_buffer, -1); /* -1 means "give me everything" */

printf("Received POST or PUT request with body: %.*s\n", (int)len, body);


}

// Now handle each request type


if (evhttp_request_get_command(req) == EVHTTP_REQ_GET) {
printf("Received a GET request\n");
evbuffer_add_printf(returnbuffer, "Hello, GET!");
} else if (evhttp_request_get_command(req) == EVHTTP_REQ_POST) {
printf("Received a POST request\n");
evbuffer_add_printf(returnbuffer, "Hello, POST!");
} else if (evhttp_request_get_command(req) == EVHTTP_REQ_PUT) {
printf("Received a PUT request\n");
evbuffer_add_printf(returnbuffer, "Hello, PUT!");
} else if (evhttp_request_get_command(req) == EVHTTP_REQ_DELETE) {
printf("Received a DELETE request\n");
evbuffer_add_printf(returnbuffer, "Hello, DELETE!");
} else {
printf("Received an unknown request\n");
evbuffer_add_printf(returnbuffer, "Unknown request type!");
}

evhttp_send_reply(req, HTTP_OK, "Client", returnbuffer);


evbuffer_free(returnbuffer);
return;
}

int main() {
struct event_base *base;
struct evhttp *http;
struct evhttp_bound_socket *handle;

base = event_base_new();
if (!base) {
fprintf(stderr, "Couldn't create an event_base: exiting\n");
return 1;
}

http = evhttp_new(base);
if (!http) {
fprintf(stderr, "couldn't create evhttp: exiting\n");
return 1;
}

evhttp_set_gencb(http, generic_handler, NULL);

handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", 8080);


if (!handle) {
fprintf(stderr, "couldn't bind to port 8080: exiting\n");
return 1;
}

event_base_dispatch(base);

return 0;
}

In the `generic_handler` function, we first handle POST and PUT requests,


for which we extract the body. For each request type, we print a
corresponding message to the console and create a response with a greeting
message. If an unknown request type is received, we send a response
indicating this.
Remember that to compile this code you need to install the libevent library
and link against it.
As a note, you would normally separate handling of each request type into
its own function, to make the code cleaner and easier to maintain.
This is a very simple example. A real server would need to handle many
other aspects, like routing, serving static files, handling errors, and so on.
But hopefully, this gives you a good starting point for creating an HTTP
server in C.

OceanofPDF.com
3.8 SERIAL PORT
PROGRAMMING
In the world of computer programming, the ability to communicate with
external devices is a crucial skill. One of the most common ways to achieve
this is through serial port programming. Serial ports allow for serial
communication, which involves sending data one bit at a time, sequentially,
over a communication channel or computer bus. This is in contrast to
parallel communication, where several bits are sent simultaneously.
In this chapter, we will delve into the world of serial port programming in
the C language. C, with its low-level capabilities and powerful library
functions, provides a robust platform for serial port programming. It allows
for direct manipulation of hardware components, making it an ideal
language for this kind of task.
We will begin by exploring the basics of serial communication and
understanding the role of serial ports. We will then discuss the various
aspects of serial port programming, including opening and closing ports,
configuring port settings, and reading from and writing to ports. We will
also cover error handling and discuss some common issues that can arise
when working with serial ports.
Throughout the chapter, we will provide practical examples and code
snippets to illustrate these concepts. We will guide you through the process
of creating a simple C program that can communicate with a device over a
serial port. This will not only help you understand the theoretical aspects of
serial port programming but also give you hands-on experience.
By the end of this chapter, you will have a solid understanding of serial port
programming in C and will be equipped with the knowledge to interact with
hardware devices, read data from sensors, control robotic devices, and
more. Whether you are an embedded systems developer, a hardware
enthusiast, or a curious programmer, the knowledge of serial port
programming is a valuable addition to your skill set. So, let's embark on this
exciting journey of exploring serial port programming in C.
Serial port programming is a crucial skill in the world of embedded systems
and hardware interfacing. In this chapter, we will walk through the process
of writing a simple C program that communicates with a device over a
serial port.
Before we start, it's important to note that serial port programming can be
highly platform-dependent. The code examples in this chapter will be based
on POSIX-compliant systems like Linux and macOS. If you're using a
different system, such as Windows, you may need to use different functions
and libraries.

OceanofPDF.com
3.8.1 OPENING AND
CONFIGURING A SERIAL
PORT
The first step in serial port programming is to open the serial port. Serial
ports are represented as files, and you can use the `open` function to open
the port:

C:

#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>

int main() {
int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
if (fd == -1) {
perror("open PORT");
return 1;
}
}

In this example, we're opening the serial port `/dev/ttyS0` for reading and
writing. The `O_NOCTTY` flag tells the system that this program doesn't
want to be the "controlling terminal" for that port.

Once the port is open, you can configure it using the `termios` structure:

C:
struct termios options;
tcgetattr(fd, &options);

cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);

options.c_cflag |= (CLOCAL | CREAD);


options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

tcsetattr(fd, TCSANOW, &options);

This code sets the baud rate to 9600, enables the receiver, sets 8 data bits,
and disables parity and stop bits. The `tcsetattr` function applies these
settings.

OceanofPDF.com
3.8.2 READING FROM AND
WRITING TO THE SERIAL
PORT
You can use the `read` and `write` functions to read from and write to the
serial port:

C:

char buffer[256];

int n = write(fd, "Hello, world!", 13);


if (n < 0) {
perror("Write PORT");
return 1;
}

n = read(fd, buffer, sizeof(buffer));


if (n < 0) {
perror("Read PORT");
return 1;
}

This code reads up to 256 bytes from the serial port into a buffer, and then
writes a string to the port.

Closing the Serial Port


When you're done with the serial port, you should close it using the `close`
function:
C:
close(fd);

This releases the resources associated with the port and makes it available
for other programs to use.
These examples demonstrate the basics of serial port programming in C.
There's a lot more to learn, including handling signals, using non-blocking
I/O, and dealing with various control lines. However, these examples
should give you a good starting point for working with serial ports in your
own programs.

The `open` function:


The `open` function in C is used to open a file or a device for reading,
writing, or both. It takes two or three parameters, depending on whether you
want to specify file permissions. Here's the prototype for the `open`
function:

C:

int open(const char *pathname, int flags);


int open(const char *pathname, int flags, mode_t mode);

Let's break down these parameters:

1. `const char *pathname`: This is a string that specifies the path to the file
or device you want to open. For example, it could be something like
"/dev/ttyS0" for a serial port, or "/home/user/myfile.txt" for a file.

2. `int flags`: This is a bitwise OR of several flags that control how the file
or device is opened:

- `O_RDONLY`: Open for reading only.


- `O_WRONLY`: Open for writing only.
- `O_RDWR`: Open for reading and writing.
- `O_APPEND`: Append to the file instead of overwriting it.
- `O_CREAT`: Create the file if it doesn't exist.
- `O_TRUNC`: Truncate the file to zero length if it already exists and is a
regular file.
- `O_EXCL`: Used with `O_CREAT`, the file must not exist beforehand.
- `O_NONBLOCK` or `O_NDELAY`: Open in non-blocking mode.
- `O_SYNC`: Write operations will complete as defined by synchronised
I/O file integrity completion.
- `O_NOCTTY`: If the named file is a terminal device, do not make it the
controlling terminal for the process.

3. `mode_t mode`: This parameter is only used if `O_CREAT` is specified


in the flags. It sets the permissions of the file if it is created. It's a
combination of the following flags:

- `S_IRUSR`: Read permission for the owner.


- `S_IWUSR`: Write permission for the owner.
- `S_IXUSR`: Execute/search permission for the owner.
- `S_IRGRP`: Read permission for the group.
- `S_IWGRP`: Write permission for the group.
- `S_IXGRP`: Execute/search permission for the group.
- `S_IROTH`: Read permission for others.
- `S_IWOTH`: Write permission for others.
- `S_IXOTH`: Execute/search permission for others.

The `open` function returns a file descriptor that you can use with other
functions like `read`, `write`, and `close`. If there's an error, it returns -1.

The `termios` structure:


The `termios` structure is used in Unix and Unix-like operating systems to
provide terminal I/O (Input/Output) interfacing. This structure is defined in
the `<termios.h>` header file and is used for getting and setting terminal
attributes.
Here is the general structure of `termios`:

C:
struct termios {
tcflag_t c_iflag; /* input modes */
tcflag_t c_oflag; /* output modes */
tcflag_t c_cflag; /* control modes */
tcflag_t c_lflag; /* local modes */
cc_t c_cc[NCCS]; /* control characters */
};

Each field in the `termios` structure represents a different aspect of terminal


I/O:

1. `c_iflag`: This field is used to specify input modes. It's a bitmask that can
include flags like `IGNBRK` (ignore break condition), `ICRNL` (map CR
to NL on input), `IXON` (enable XON/XOFF flow control on output), and
others.
2. `c_oflag`: This field is used to specify output modes. It can include flags
like `OPOST` (perform post-processing of output), `ONLCR` (map NL to
CR-NL on output), and others.
3. `c_cflag`: This field is used to specify control modes. It can include flags
like `CSIZE` (character size mask), `PARENB` (enable parity generation on
output and parity checking for input), `CSTOPB` (two stop bits, otherwise
one), and others.
4. `c_lflag`: This field is used to specify local modes. It can include flags
like `ECHO` (echo input characters), `ICANON` (canonical mode enabled),
`ISIG` (enable signals), and others.
5. `c_cc`: This field is an array that specifies control characters. For
example, `c_cc[VEOF]` specifies the character that should be interpreted as
end-of-file.

You can use the `tcgetattr` function to get the current terminal attributes into
a `termios` structure, and the `tcsetattr` function to set terminal attributes
from a `termios` structure. These functions provide a way to get and set a
wide range of terminal behaviors.

OceanofPDF.com
3.9 ESTABLISHING
DATABASE
CONNECTIONS
In the realm of software development, one of the most critical aspects is
data management. Whether it's for storing user information, tracking real-
time data, or maintaining system logs, databases play a pivotal role. As
such, understanding how to interact with databases is a vital skill for any
programmer.
In this chapter, we will explore the process of establishing database
connections using the C programming language. C, known for its efficiency
and control over system resources, is a powerful language for database
operations. It allows for direct interaction with the database server,
providing a high degree of customization and control.
We will begin by discussing the concept of a database and the role it plays
in software applications. We will then delve into the various types of
databases, such as relational databases (like MySQL, PostgreSQL) and
NoSQL databases (like MongoDB, Cassandra), and their respective use
cases.
The core of this chapter will be dedicated to understanding how to use C to
connect to a database. We will explore the use of various libraries and APIs
that facilitate database connectivity in C, such as the MySQL Connector/C
for MySQL databases or the C API for SQLite databases. We will discuss
how to set up these libraries, establish a connection to the database, execute
SQL queries, and handle the results.
Throughout the chapter, we will provide practical examples and code
snippets to illustrate these concepts. We will guide you through the process
of creating a simple C program that can connect to a database, perform
basic CRUD (Create, Read, Update, Delete) operations, and handle errors
effectively.
By the end of this chapter, you will have a solid understanding of how to
connect to a database using C and perform various database operations.
This knowledge will be invaluable whether you are developing a complex
enterprise application, a small command-line tool, or anything in between.
So, let's embark on this journey to explore the fascinating world of database
connectivity in C.

OceanofPDF.com
3.9.1 MYSQL DATABASE
In this chapter, we will explore how to connect to a MySQL database using
the C programming language. We will be using the MySQL Connector/C,
which is a C library that lets you connect to MySQL servers, execute SQL
statements, and retrieve results.

Setting Up MySQL Connector/C


Before we can start writing code, we need to install the MySQL
Connector/C library. This can typically be done through your system's
package manager. For example, on a Debian-based system like Ubuntu, you
can use the following command:

bash:
sudo apt-get install libmysqlclient-dev

Establishing a Connection
Once the library is installed, we can start writing our C program. The first
step is to include the necessary header file and establish a connection to the
MySQL server:

C:

#include <mysql.h>
#include <stdio.h>

int main() {
MYSQL *con = mysql_init(NULL);

if (con == NULL) {
fprintf(stderr, "%s\n", mysql_error(con));
return 1;
}

if (mysql_real_connect(con, "localhost", "user", "password",


"database", 0, NULL, 0) == NULL) {
fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
return 1;
}

// ... (execute queries and retrieve results)

mysql_close(con);
return 0;
}

In this code, we first call `mysql_init` to initialize a `MYSQL` object. We


then call `mysql_real_connect` to establish a connection to the MySQL
server. The parameters to `mysql_real_connect` are the `MYSQL` object,
the hostname of the MySQL server, the username, the password, the
database name, the port number (0 for default), the Unix socket (NULL for
default), and the client flag (0 for default).

Executing Queries
Once a connection is established, we can execute SQL queries using the
`mysql_query` function:

C:

if (mysql_query(con, "CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")) {


fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
return 1;
}
In this code, we're creating a new table named "Cars". If the query fails for
any reason, we print an error message and close the connection.

Retrieving Results
After executing a SELECT query, we can retrieve the results using the
`mysql_store_result` and `mysql_fetch_row` functions:

C:

if (mysql_query(con, "SELECT * FROM Cars")) {


fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
return 1;
}

MYSQL_RES *result = mysql_store_result(con);

if (result == NULL) {
fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
return 1;
}

MYSQL_ROW row;

while ((row = mysql_fetch_row(result))) {


printf("%s\n", row[0]);
}

mysql_free_result(result);

In this code, we're executing a SELECT query to retrieve all rows from the
"Cars" table. We then call `mysql_store_result` to get a `MYSQL_RES`
object that represents the result set. We can retrieve each row from the
result set using `mysql_fetch_row`, which returns a `MYSQL_ROW`
object. This object is an array of strings, where each string represents a field
in the row.

Closing the Connection


Finally, after we're done with the database operations, we close the
connection using the `mysql_close` function:

C:
mysql_close(con);

This is a basic overview of how to connect to a MySQL database using C


and the MySQL Connector/C library. There's a lot more you can do,
including prepared statements, error handling, and more. However, this
should give you a good starting point for working with MySQL databases in
your C programs. Always remember to check the return values of the
functions and handle any errors that might occur.

OceanofPDF.com
3.9.2 POSTGRESQL
DATABASE
In this chapter, we will explore how to connect to a PostgreSQL database
using the C programming language. We will be using the libpq library,
which is the C application programmer's interface to PostgreSQL.

Setting Up libpq
Before we can start writing code, we need to install the libpq library. This
can typically be done through your system's package manager. For example,
on a Debian-based system like Ubuntu, you can use the following
command:

bash:

sudo apt-get install libpq-dev

Establishing a Connection
Once the library is installed, we can start writing our C program. The first
step is to include the necessary header file and establish a connection to the
PostgreSQL server:

C:

#include <stdio.h>
#include <libpq-fe.h>

int main() {
PGconn *conn = PQconnectdb("user=user dbname=database password=password");

if (PQstatus(conn) == CONNECTION_BAD) {
fprintf(stderr, "Connection to database failed: %s\n",
PQerrorMessage(conn));
PQfinish(conn);
return 1;
}

// ... (execute queries and retrieve results)

PQfinish(conn);
return 0;
}

In this code, we first call `PQconnectdb` to establish a connection to the


PostgreSQL server. The parameter to `PQconnectdb` is a string of
key=value pairs separated by spaces. The keys are the option names and the
values are the option values.

Executing Queries
Once a connection is established, we can execute SQL queries using the
`PQexec` function:

C:

PGresult *res = PQexec(conn, "CREATE TABLE Cars(Id INT, Name TEXT, Price INT)");

if (PQresultStatus(res) != PGRES_COMMAND_OK) {
f​printf(stderr, "CREATE TABLE failed: %s", PQerrorMessage(conn));
​ Qclear(res);
P
​PQfinish(conn);
​return 1;
}

Pqclear(res);

In this code, we're creating a new table named "Cars". If the query fails for
any reason, we print an error message and close the connection.
Retrieving Results
After executing a SELECT query, we can retrieve the results using the
`PQgetvalue` function:

C:

res = PQexec(conn, "SELECT * FROM Cars");

if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}

int rows = PQntuples(res);

for(int i=0; i<rows; i++) {


printf("%s\n", PQgetvalue(res, i, 0));
}

Pqclear(res);

In this code, we're executing a SELECT query to retrieve all rows from the
"Cars" table. We then call `PQntuples` to get the number of rows in the
result set. We can retrieve each row from the result set using `PQgetvalue`,
which returns a string representing a field in the row.

Closing the Connection


Finally, after we're done with the database operations, we close the
connection using the `PQfinish` function:

C:
Pqfinish(conn);
This is a basic overview of how to connect to a PostgreSQL database using
C and the libpq library. There's a lot more you can do, including prepared
statements, error handling, and more. However, this should give you a good
starting point for working with PostgreSQL databases in your C programs.
Always remember to check the return values of the functions and handle
any errors that might occur.

OceanofPDF.com
3.9.3 SQLITE DATABASE
In this chapter, we will explore how to connect to an SQLite database using
the C programming language. SQLite is a C library that provides a
lightweight disk-based database. It doesn't require a separate server process
and allows accessing the database using a nonstandard variant of the SQL
query language.

Setting Up SQLite
Before we can start writing code, we need to install the SQLite library. This
can typically be done through your system's package manager. For example,
on a Debian-based system like Ubuntu, you can use the following
command:

bash:
sudo apt-get install libsqlite3-dev

Establishing a Connection
Once the library is installed, we can start writing our C program. The first
step is to include the necessary header file and establish a connection to the
SQLite database:

C:

#include <stdio.h>
#include <sqlite3.h>

int main() {
sqlite3 *db;
char *err_msg = 0;

int rc = sqlite3_open("test.db", &db);


if (rc != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);

return 1;
}

// ... (execute queries and retrieve results)

sqlite3_close(db);

return 0;
}

In this code, we first call `sqlite3_open` to establish a connection to the


SQLite database. The parameters to `sqlite3_open` are the filename (UTF-
8) of the SQLite database and a pointer to the SQLite database handle.

Executing Queries
Once a connection is established, we can execute SQL queries using the
`sqlite3_exec` function:

C:

char *sql = "CREATE TABLE Cars(Id INT, Name TEXT, Price INT);"
"INSERT INTO Cars VALUES(1, 'Audi', 52642);"
"INSERT INTO Cars VALUES(2, 'Mercedes', 57127);"
"INSERT INTO Cars VALUES(3, 'Skoda', 9000);"
"INSERT INTO Cars VALUES(4, 'Volvo', 29000);";

rc = sqlite3_exec(db, sql, 0, 0, &err_msg);

if (rc != SQLITE_OK ) {
f​printf(stderr, "SQL error: %s\n", err_msg);
s​ qlite3_free(err_msg);
​sqlite3_close(db);
​return 1;
}

In this code, we're creating a new table named "Cars" and inserting some
data into it. If the query fails for any reason, we print an error message and
close the connection.

Retrieving Results
After executing a SELECT query, we can retrieve the results using a
callback function:

C:

int callback(void *NotUsed, int argc, char **argv, char **azColName) {


NotUsed = 0;

for (int i = 0; i < argc; i++) {


printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}

printf("\n");

return 0;
}

char *sql = "SELECT * FROM Cars";

rc = sqlite3_exec(db, sql, callback, 0, &err_msg);

if (rc != SQLITE_OK ) {
fprintf(stderr, "Failed to select data\n");
fprintf(stderr, "SQL error: %s\n", err_msg);

sqlite3_free(err_msg);
sqlite3_close(db);
return 1;
}

In this code, we're executing a SELECT query to retrieve all rows from the
"Cars" table. We then call `sqlite3_exec` with our SQL query and a callback
function. The callback function will be invoked for each record returned by
the query. The parameters to the callback function are a void pointer, the
number of columns in the result, an array of strings representing the fields
in the row, and an array of strings representing the column names.

Closing the Connection


Finally, after we're done with the database operations, we close the
connection using the `sqlite3_close` function:

C:

sqlite3_close(db);

This is a basic overview of how to connect to an SQLite database using C


and the SQLite library. There's a lot more you can do, including prepared
statements, error handling, and more. However, this should give you a good
starting point for working with SQLite databases in your C programs.
Always remember to check the return values of the functions and handle
any errors that might occur.

OceanofPDF.com
3.9.4 MONGODB
DATABASE
In this chapter, we will explore how to connect to a MongoDB database
using the C programming language. MongoDB is a source-available cross-
platform document-oriented database program. Classified as a NoSQL
database program, MongoDB uses JSON-like documents with optional
schemas.

Setting Up MongoDB C Driver


Before we can start writing code, we need to install the MongoDB C Driver.
This can typically be done through your system's package manager. For
example, on a Debian-based system like Ubuntu, you can use the following
command:

bash:
sudo apt-get install libmongoc-1.0-0

Establishing a Connection
Once the library is installed, we can start writing our C program. The first
step is to include the necessary header file and establish a connection to the
MongoDB server:

C:

#include <bson/bson.h>
#include <mongoc/mongoc.h>
#include <stdio.h>

int main() {
mongoc_client_t *client;
mongoc_database_t *database;
mongoc_collection_t *collection;
bson_error_t error;
bson_oid_t oid;
bson_t *doc;

mongoc_init ();

client = mongoc_client_new ("mongodb://localhost:27017/?appname=connect-example");


database = mongoc_client_get_database (client, "mydb");
collection = mongoc_client_get_collection (client, "mydb", "mycoll");

// ... (execute queries and retrieve results)

bson_destroy (doc);
mongoc_collection_destroy (collection);
mongoc_database_destroy (database);
mongoc_client_destroy (client);

mongoc_cleanup ();

return 0;
}

In this code, we first call `mongoc_client_new` to establish a connection to


the MongoDB server. The parameter to `mongoc_client_new` is a string
representing the MongoDB URI.

Executing Queries
Once a connection is established, we can execute SQL queries using the
`mongoc_collection_insert_one` function:

C:

doc = bson_new ();


bson_oid_init (&oid, NULL);
BSON_APPEND_OID (doc, "_id", &oid);
BSON_APPEND_UTF8 (doc, "hello", "world");

if (!mongoc_collection_insert_one (collection, doc, NULL, NULL, &error)) {


fprintf (stderr, "%s\n", error.message);
}

In this code, we're creating a new BSON document and inserting it into the
collection. If the query fails for any reason, we print an error message.

Retrieving Results
After executing a SELECT query, we can retrieve the results using the
`mongoc_collection_find_with_opts` function:

C:

bson_t *query;
mongoc_cursor_t *cursor;
const bson_t *doc;
char *str;

query = bson_new ();


cursor = mongoc_collection_find_with_opts (collection, query, NULL, NULL);

while (mongoc_cursor_next (cursor, &doc)) {


str = bson_as_json (doc, NULL);
fprintf (stdout, "%s\n", str);
bson_free (str);
}

bson_destroy (query);
mongoc_cursor_destroy (cursor);
In this code, we're executing a SELECT query to retrieve all documents
from the collection. We then iterate over the results and print each
document.

Closing the Connection


Finally, after we're done with the database operations, we close the
connection using the `mongoc_cleanup` function:

C:

mongoc_cleanup();

This is a basic overview of how to connect to a MongoDB database using C


and the MongoDB C Driver. There's a lot more you can do, including
handling BSON data, error handling, and more.

However, this should give you a good starting point for working with
MongoDB databases in your C programs. Always remember to check the
return values of the functions and handle any errors that might occur.

OceanofPDF.com
3.9.5 CASSANDRA
DATABASE
In this chapter, we will explore how to connect to a Cassandra database
using the C programming language. Cassandra is a highly scalable, high-
performance distributed database designed to handle large amounts of data
across many commodity servers, providing high availability with no single
point of failure.

Setting Up DataStax C/C++ Driver for Apache Cassandra


Before we can start writing code, we need to install the DataStax C/C++
Driver for Apache Cassandra. This can typically be done by building the
driver from source like in the following example:

bash:

# Install required packages


sudo apt-get install git cmake build-essential libuv1-dev libssl-dev

# Clone the DataStax C/C++ driver for Apache Cassandra


git clone https://github.com/datastax/cpp-driver.git

# Enter the repository directory


cd cpp-driver

# Create and enter the build directory


mkdir build
cd build

# Build the driver


cmake ..
make

# Install the driver


sudo make install

More advanced instructions for building and installing the driver can be
found in the official DataStax documentation.

Establishing a Connection
Once the driver is installed, we can start writing our C program. The first
step is to include the necessary header files and establish a connection to the
Cassandra cluster:

C:

#include <cassandra.h>
#include <stdio.h>

int main() {
/* Setup and connect to cluster */
CassFuture* connect_future = NULL;
CassCluster* cluster = cass_cluster_new();
CassSession* session = cass_session_new();

/* Add contact points */


cass_cluster_set_contact_points(cluster, "127.0.0.1");

/* Provide the cluster object as configuration to connect the session */


connect_future = cass_session_connect(session, cluster);

/* This operation will block until the result is ready */


CassError rc = cass_future_error_code(connect_future);

printf("Connect result: %s\n", cass_error_desc(rc));

/* Handle connection error */


if (rc != CASS_OK) {
/* Handle error */
const char* message;
size_t message_length;
cass_future_error_message(connect_future, &message, &message_length);
fprintf(stderr, "Unable to connect: '%.*s'\n", (int)message_length, message);
}

/* ... (execute queries and retrieve results) */

/* Close the session */


cass_future_free(connect_future);
cass_cluster_free(cluster);
cass_session_free(session);

return 0;
}

In this code, we first create a new cluster object and set the contact points.
The contact points are used to initialize the driver and discover the rest of
the nodes in the cluster. We then connect the session to the cluster.

Executing Queries
Once a connection is established, we can execute CQL (Cassandra Query
Language) queries using the `cass_session_execute` function:

C:

CassStatement* statement = cass_statement_new("SELECT * FROM keyspace1.table1", 0);

CassFuture* query_future = cass_session_execute(session, statement);

/* Statement objects can be freed immediately after being executed */


cass_statement_free(statement);

/* This will block until the query has finished */


CassError rc = cass_future_error_code(query_future);
printf("Query result: %s\n", cass_error_desc(rc));

if (rc != CASS_OK) {
/* Handle error */
const char* message;
size_t message_length;
cass_future_error_message(query_future, &message, &message_length);
fprintf(stderr, "Unable to run query: '%.*s'\n", (int)message_length, message);
}

/* The future can be freed immediately after getting the result object */
cass_future_free(query_future);

In this code, we're executing a SELECT query to retrieve all rows from a
table. If the query fails for any reason, we print an error message.

Retrieving Results
After executing a SELECT query, we can retrieve the results using the
`cass_iterator_from_result` function:

C:

const CassResult* result = cass_future_get_result(query_future);


CassIterator* iterator = cass_iterator_from_result(result);

while (cass_iterator_next(iterator)) {
const CassRow* row = cass_iterator_get_row(iterator);
const CassValue* value = cass_row_get_column_by_name(row, "column1");

/* Handle the value retrieved from the column... */


}

cass_result_free(result);
cass_iterator_free(iterator);
In this code, we're executing a SELECT query to retrieve all rows from a
table. We then create an iterator from the result and iterate over the rows.
For each row, we retrieve a value by column name.

Closing the Connection


Finally, after we're done with the database operations, we close the
connection:

C:

CassFuture* close_future = cass_session_close(session);


cass_future_wait(close_future);
cass_future_free(close_future);

This is a basic overview of how to connect to a Cassandra database using C


and the DataStax C/C++ Driver for Apache Cassandra. There's a lot more
you can do, including prepared statements, error handling, and more.
However, this should give you a good starting point for working with
Cassandra databases in your C programs. Always remember to check the
return values of the functions and handle any errors that might occur.

OceanofPDF.com
3.10 FILE PROCESSING
(I/O OPERATIONS)
In the realm of programming, one of the most common tasks is file
processing. Whether it's reading data from files, writing results to files, or
manipulating file content, file processing is a crucial skill for any
programmer. In this chapter, we will delve into the various file processing
techniques in the C language, focusing on XML, JSON, CSV, and ZIP files.

Each of these file formats serves a unique purpose and has its own set of
benefits. XML and JSON, for instance, are widely used for storing and
exchanging data due to their readability and support for hierarchical
structures. CSV files, on the other hand, are a popular choice for storing
tabular data because of their simplicity and compatibility with spreadsheet
programs. ZIP files are used for compressing and archiving files, making
them an essential format for managing file storage and transfers.
In the context of the C language, there are numerous libraries available that
provide robust and efficient ways to handle these file formats. These
libraries abstract the complexities of file processing, allowing you to read,
write, and manipulate files with relative ease.
Throughout this chapter, we will provide a comprehensive overview of
these file processing methods. We will start by covering the basics of each
file format, followed by a detailed explanation of how they can be
manipulated using C. We will also provide code examples to illustrate these
concepts, giving you a practical understanding of how to implement these
techniques in your own programs.
Whether you're a beginner just starting out with C, or an experienced
programmer looking to expand your skill set, this chapter will equip you
with the knowledge and skills to handle a wide range of file processing
tasks in C. So, let's get started and dive into the fascinating world of file
processing.
OceanofPDF.com
3.10.1 HANDLING TEXT
STRINGS IN FILES
In this chapter, we'll learn how to write a string to a file and then read it
back from the file using C programming language.

Writing a string to a file


The `fputs` function is used to write a null-terminated string to a file. It is
defined in `stdio.h` and its syntax is as follows:

C:

int fputs(const char *str, FILE *stream);

Here is a simple program that writes a string to a file:

C:

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}

char *str = "Hello, World!\nHere is second line";


fputs(str, file);
fclose(file);
printf("Text has been written to file successfully.\n");

return 0;
}

Reading a string from a file


The `fgets` function is used to read a string from a file. It is also defined in
`stdio.h` and its syntax is as follows:

C:

char *fgets(char *str, int n, FILE *stream);

Here is a simple program that reads a string from a file:

C:

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}

char str[100];
while(fgets(str, 100, file) != NULL) {
printf("Line read: %s", str);
}

fclose(file);
return 0;
}

Please note, in production-level programs, always check the return values


of `fgets` and `fputs` for error handling. This is omitted in these examples
for clarity.

OceanofPDF.com
3.10.2 BUFFERED FILE
HANDLING
In this chapter, we will learn how to handle large amounts of data in files
using buffering in C. Buffering is an optimization technique where we
temporarily store data in an area (called a buffer) before it's written to the
disk or after it's read from the disk.
The example we will be using is writing a large array of integers to a file
and reading them back. We'll handle the data in chunks to minimize disk
I/O operations, thereby improving performance.

Writing Large Amounts of Data to a File

C:

#include <stdio.h>

#define SIZE 1005


#define BUFFER_SIZE 100

int main() {
// Create a large array filled with integer data
int data[SIZE];
for(int i = 0; i < SIZE; i++) {
data[i] = i;
}

// Open file in write mode


FILE *file = fopen("large_data.bin", "w");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}

// Write data to file in chunks of BUFFER_SIZE


int write_count = SIZE / BUFFER_SIZE;
for(int i = 0; i < write_count; i++) {
fwrite(data + i * BUFFER_SIZE, sizeof(int), BUFFER_SIZE, file);
}

// Write remaining data, if any


int remainder = SIZE % BUFFER_SIZE;
if (remainder != 0) {
fwrite(data + write_count * BUFFER_SIZE, sizeof(int), remainder, file);
}

// Close file
fclose(file);

printf("Data has been written to file successfully.\n");

return 0;
}

Reading Large Amounts of Data from a File

C:

#include <stdio.h>

#define SIZE 1005


#define BUFFER_SIZE 100
int main() {
// Create a buffer to temporarily hold data
int buffer[BUFFER_SIZE];

// Open file in read mode


FILE *file = fopen("large_data.bin", "r");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}

// Read data from file in chunks of BUFFER_SIZE


size_t result;
while ((result = fread(buffer, sizeof(int), BUFFER_SIZE, file)) > 0) {
// Process or use the data read from the file
// In this example, we'll print all elements in the buffer
for (int i = 0; i < result; i++) {
printf("Element %d in buffer: %d\n", i, buffer[i]);
}
printf("end of chunk\n");
}

// If result is less than BUFFER_SIZE, we might have reached EOF


if (feof(file)) {
printf("End of file reached.\n");
}
if (ferror(file)) {
printf("Error reading file.\n");
}

// Close file
fclose(file);

return 0;
}
In both examples:
- `#define` directives are used to set the size of the data array and the buffer.
Adjust these sizes according to your system's memory limits and the
amount of data you are dealing with.
- The `fopen` function is used to open the file. The "w" mode is for writing
and the "r" mode is for reading.
- The `fwrite` function in the write example and the `fread` function in the
read example are used for buffered I/O operations. They handle data in
chunks (blocks) instead of single elements.
- The `fclose` function is used to close the file once we are done with it. It's
always good practice to close files after use to avoid any potential I/O
issues and to free system resources.
Remember that you must always handle errors that may occur during file
I/O operations. In these examples, basic error handling is done by checking
if `fopen` returns `NULL`, which indicates that the file couldn't be opened.

OceanofPDF.com
3.10.3 WRITING AND
READING STRUCTURES
TO/FROM FILES
C provides several functions for reading from and writing to files. These
functions can be used to store structures in files and read them back into
memory. This can be useful for saving data between runs of your program,
sharing data between different programs, and more.

Defining Structures
A structure is defined using the struct keyword, followed by an optional tag,
followed by a list of fields enclosed in curly braces. Each field has a type
and a name. Here's an example:

C:

struct Student {
char name[50];
int age;
float grade;
};

Writing Structures to Files


To write a structure to a file, you can use the `fwrite` function. This function
writes a block of data to a file. Here's an example:

C:
struct Student john = {"John Doe", 20, 4.0};
FILE *file = fopen("student.dat", "wb");
if (file != NULL) {
fwrite(&john, sizeof(struct Student), 1, file);
fclose(file);
}

In this example, a `Student` structure is written to a file named


"student.dat". The "wb" mode opens the file for writing in binary mode,
which is appropriate for non-text data like structures.

Reading Structures from Files


To read a structure from a file, you can use the `fread` function. This
function reads a block of data from a file. Here's an example:

C:

struct Student john;


FILE *file = fopen("student.dat", "rb");
if (file != NULL) {
fread(&john, sizeof(struct Student), 1, file);
printf("name: %s, age:%d, grade:%f", john.name, john.age, john.grade);
fclose(file);
}

In this example, a `Student` structure is read from a file named


"student.dat". The "rb" mode opens the file for reading in binary mode.

Error Checking
When working with files, it's important to check for errors. The `fopen`
function returns `NULL` if the file cannot be opened (for example, if the
file does not exist). The `fwrite` and `fread` functions return the number of
items successfully read or written. If this number is less than the number of
items you expected to read or write, there was an error.
Writing and reading structures to and from files is a powerful technique that
allows you to save complex data types and share data between different
runs of your program or different programs. However, it's important to
handle files and memory carefully to avoid errors and data corruption.

OceanofPDF.com
3.10.4 FILE POSITIONING
WITH FSEEK (SIMPLE
DATABASE IN C)
In this chapter, we will explore a practical use case for the `fseek` function
in the C programming language. `fseek` is a library function that is used to
change the file position of a stream to a specified offset. This is particularly
useful in scenarios where we need to skip over parts of a file or revisit
certain sections of a file.
Consider the following scenario: You are implementing a basic database in
C. The record's data structure is defined as follows:

C:

// The structure of a record


struct record {
int id;
char name[20];
char address[30];
char phone[20];
char email[30];
};

A database file stores these records consecutively as in the following


example:
C:

#include <stdio.h>

// The structure of a record


struct record {
int id;
char name[20];
char address[30];
char phone[20];
char email[30];
};

int main() {
// Create a few records
struct record recs[10] = {
{1, "Alice", "123 Main St", "555-555-5555", "[email protected]"},
{2, "Bob", "456 High St", "555-555-5556", "[email protected]"},
{3, "Charlie", "789 Oak St", "555-555-5557", "[email protected]"},
{4, "Dave", "321 Maple St", "555-555-5558", "[email protected]"},
{5, "Eva", "654 Pine St", "555-555-5559", "[email protected]"},
{6, "Frank", "987 Elm St", "555-555-5560", "[email protected]"},
{7, "Grace", "123 Oak St", "555-555-5561", "[email protected]"},
{8, "Henry", "456 Pine St", "555-555-5562", "[email protected]"},
{9, "Ivy", "789 Maple St", "555-555-5563", "[email protected]"},
{10, "Jack", "321 Elm St", "555-555-5564", "[email protected]"}
};

// Open the database file


FILE *file = fopen("database.dat", "wb");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}
// Write the records to the file
int num_records = sizeof(recs) / sizeof(struct record);
for (int i = 0; i < num_records; i++) {
fwrite(&recs[i], sizeof(struct record), 1, file);
}

// Close the file


fclose(file);

printf("Database has been created and populated.\n");

return 0;
}

We want to implement a function that, given an ID, retrieves the


corresponding record without reading the entire file.
Here is how we might use `fseek` to accomplish this:

C:

#include <stdio.h>

// Structure for a record


struct record {
int id;
char name[20];
char address[30];
char phone[20];
char email[30];
};
// Function to retrieve a record by its id
struct record get_record_by_id(int id, FILE *file) {
struct record rec;

// Calculate the offset


long offset = (id - 1) * sizeof(struct record);

// Seek to the position of the record


fseek(file, offset, SEEK_SET);

// Read the record


fread(&rec, sizeof(struct record), 1, file);

return rec;
}

int main() {
// Open the database file
FILE *file = fopen("database.dat", "rb");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}

// Retrieve record with ID 5


struct record rec = get_record_by_id(5, file);

// Close the file


fclose(file);

// Print the retrieved record


printf("ID: %d\n", rec.id);
printf("Name: %s\n", rec.name);
printf("Address: %s\n", rec.address);
printf("Phone: %s\n", rec.phone);
printf("Email: %s\n", rec.email);
return 0;
}

In this example, `fseek` is used to jump directly to the position of the


desired record in the file. We calculate the position by multiplying the
record ID by the size of each record. We then read the record into our struct.
This significantly reduces the amount of data we need to read when
searching for a specific record, especially for large files. This example
demonstrates a real-world use case of `fseek` in a basic database
implementation.
Note: The program assumes that the records are ordered by ID in the file
and that each record is exactly 100 bytes. This may not be the case in a real-
world application, and additional error checking and handling would likely
be needed.

OceanofPDF.com
3.10.5 PARSING XML
In this chapter, we will explore how to parse XML documents in C using
the libxml2 library. libxml2 is a software library for parsing XML
documents. It provides a rich set of features and is widely used in open-
source and commercial software projects.

Setting Up libxml2
Before we can start writing code, we need to install the libxml2 library. This
can typically be done through your system's package manager. For example,
on a Debian-based system like Ubuntu, you can use the following
command:

bash:

sudo apt-get install libxml2-dev

Parsing an XML Document


Once the library is installed, we can start writing our C program to parse an
xml file.
Example of xml document (example.xml):

xml:

<?xml version="1.0" encoding="UTF-8"?>


<root>
<element1>Some text here</element1>
<element2>
<child>Some more text</child>
</element2>
</root>

The first step is to include the necessary header files and parse an XML
document:

C:

#include <stdio.h>
#include <libxml/parser.h>

int main() {
xmlDocPtr doc;
xmlNodePtr root, node;

doc = xmlParseFile("example.xml");
if (doc == NULL) {
fprintf(stderr, "Failed to parse example.xml\n");
return 1;
}

root = xmlDocGetRootElement(doc);
if (root == NULL) {
fprintf(stderr, "example.xml is empty\n");
xmlFreeDoc(doc);
return 1;
}

// ... (traverse and process the XML tree)

xmlFreeDoc(doc);
return 0;
}

In this code, we first call `xmlParseFile` to parse an XML document from a


file. We then call `xmlDocGetRootElement` to get the root element of the
XML document.

Traversing the XML Tree


Once we have the root element, we can traverse the XML tree using the
`xmlFirstElementChild` and `xmlNextElementSibling` functions:

C:

for (node = xmlFirstElementChild(root); node; node = xmlNextElementSibling(node)) {


xmlChar* content = xmlNodeGetContent(node);
printf("Node name: %s\n", node->name);
printf("Node content: %s\n", content);
xmlFree(content);
}

In this code, we're looping over the children of the root element. For each
child, we print the node name and content.

Extracting Attributes
We can also extract attributes from an XML node using the `xmlGetProp`
function:

C:

xmlChar* attr = xmlGetProp(node, "attr");


if (attr) {
printf("Attribute: %s\n", attr);
xmlFree(attr);
}

In this code, we're getting the value of the "attr" attribute from the node. If
the attribute exists, we print its value.
Here is a completed example how to traverse the xml document:
C:

#include <stdio.h>
#include <libxml/parser.h>

void traverse_tree(xmlNodePtr node) {


xmlNodePtr cur_node = NULL;
xmlChar* content;
xmlAttr* attribute;

for (cur_node = node; cur_node; cur_node = cur_node->next) {


if (cur_node->type == XML_ELEMENT_NODE) {
content = xmlNodeGetContent(cur_node);
printf("Node name: %s\n", cur_node->name);
printf("Node content: %s\n", content);
xmlFree(content);

for (attribute = cur_node->properties; attribute; attribute = attribute->next) {


xmlChar* value = xmlNodeGetContent(attribute->children);
printf("Attribute name: %s\n", attribute->name);
printf("Attribute value: %s\n", value);
xmlFree(value);
}
}

traverse_tree(cur_node->children);
}
}

int main() {
xmlDocPtr doc;
xmlNodePtr root, node;

doc = xmlParseFile("example.xml");
if (doc == NULL) {
fprintf(stderr, "Failed to parse example.xml\n");
return 1;
}

root = xmlDocGetRootElement(doc);
if (root == NULL) {
fprintf(stderr, "example.xml is empty\n");
xmlFreeDoc(doc);
return 1;
}

// ... (traverse and process the XML tree)


traverse_tree(root);

xmlFreeDoc(doc);
return 0;
}

Error Handling
libxml2 provides several functions for error handling. For example, you can
use the `xmlGetLastError` function to get the last error that occurred:

C:

xmlErrorPtr error = xmlGetLastError();


if (error) {
fprintf(stderr, "XML error: %s\n", error->message);
}

In this code, we're getting the last error and printing its message.

Cleaning Up
Finally, after we're done with the XML document, we free it using the
`xmlFreeDoc` function:
C:

xmlFreeDoc(doc);

This is a basic overview of how to parse XML documents in C using the


libxml2 library. There's a lot more you can do, including namespace
handling, XPath queries, and more. However, this should give you a good
starting point for working with XML in your C programs. Always
remember to check the return values of the functions and handle any errors
that might occur.

OceanofPDF.com
3.10.6 PARSING JSON
In this chapter, we will explore how to parse JSON documents in C using
the Jansson library. Jansson is a C library for encoding, decoding, and
manipulating JSON data.
Setting Up Jansson
Before we can start writing code, we need to install the Jansson library. This
can typically be done through your system's package manager. For example,
on a Debian-based system like Ubuntu, you can use the following
command:

bash:
sudo apt-get install libjansson-dev

Parsing a JSON Document


Once the library is installed, we can start writing our C program to process
following json file:

json:

{
"data": {
"name": "John Doe",
"age": 30
}
}

The first step is to include the necessary header files and parse a JSON
document:
C:

#include <stdio.h>
#include <jansson.h>

int main() {
json_error_t error;
json_t *root;

root = json_load_file("example.json", 0, &error);


if (!root) {
fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
return 1;
}

// ... (traverse and process the JSON tree)

json_decref(root);
return 0;
}

In this code, we first call `json_load_file` to parse a JSON document from a


file. If the parsing fails, we print an error message.

Traversing the JSON Tree


Once we have the root JSON object, we can traverse the JSON tree using
the `json_object_get` function:

C:

json_t *data, *name, *age;

data = json_object_get(root, "data");


if (!json_is_object(data)) {
fprintf(stderr, "error: data is not an object\n");
json_decref(root);
return 1;
}

name = json_object_get(data, "name");


if (!json_is_string(name)) {
fprintf(stderr, "error: name is not a string\n");
json_decref(root);
return 1;
}

age = json_object_get(data, "age");


if (!json_is_integer(age)) {
fprintf(stderr, "error: age is not an integer\n");
json_decref(root);
return 1;
}

printf("Name: %s\n", json_string_value(name));


printf("Age: %lld\n", json_integer_value(age));

In this code, we're getting the "data" object from the root object, and then
the "name" and "age" values from the "data" object. We then print the name
and age.

Error Handling
Jansson provides several functions for error handling. For example, you can
use the `json_error_t` structure to get information about an error that
occurred during parsing:

C:

json_error_t error;
json_t *root = json_load_file("example.json", 0, &error);
if (!root) {
fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
return 1;
}

In this code, we're passing a pointer to a `json_error_t` structure to


`json_load_file`. If the parsing fails, we print the line number and error
message.

Cleaning Up
Finally, after we're done with the JSON object, we decrement its reference
count using the `json_decref` function:

C:

json_decref(root);

This is a basic overview of how to parse JSON documents in C using the


Jansson library. There's a lot more you can do, including creating JSON
objects, arrays, and values, and encoding JSON documents. However, this
should give you a good starting point for

working with JSON in your C programs. Always remember to check the


return values of the functions and handle any errors that might occur.

OceanofPDF.com
3.10.7 PARSING CSV
In this chapter, we will explore how to parse CSV (Comma Separated
Values) files in C. CSV is a simple file format used to store tabular data,
such as a spreadsheet or database.

Reading a CSV File


The C standard library provides functions for reading and writing files,
which we can use to read a CSV file.
Here is an example of a CSV file which we will try to process:

csv:

Name, Age, Occupation


John Doe, 30, Engineer
Jane, 28, Scientist
Alice, 35, Manager
Bob, 40, CTO

Here's a simple example of how to open a CSV file and read its contents
line by line:

C:

#include <stdio.h>
int main() {
FILE *file = fopen("/home/pi/tmp/example.csv", "r");
if (file == NULL) {
fprintf(stderr, "Failed to open file\n");
return 1;
}

char line[1024];
fgets(line, sizeof(line), file); // Skip the header line

while (fgets(line, sizeof(line), file)) {


// Process the line
char name[32], occupation[32];
int age;
int fields = sscanf(line, "%[^,], %d, %[^,\n]", name, &age, occupation);
if (fields == 3) {
printf("name: %s, age: %d, occupation: %s\n", name, age, occupation);
}
}

fclose(file);
return 0;
}

In this code, we first open the file with `fopen`. We then read each line of
the file with `fgets`.

Handling CSV Quoting


CSV files often contain quoted values, which can include commas and
newlines, like in this example:

csv:

"Name", "Age", "Occupation"


"John Doe", "30", "Software Engineer"
"Jane, Doe", "28", "Data Scientist"
"Alice", "35", "Product Manager"
"Bob", "40", "CTO"
"Charlie Brown", "50", "\"Manager, Engineering\""
"\"Line with
newline\"", "33", "Developer"

To handle quoted values, we need a more sophisticated CSV parser. There


are many CSV parsing libraries available for C, such as libcsv.
Here's an example of how to use libcsv to parse a CSV file:

C:

#include <stdio.h>
#include <csv.h>

void cb1 (void *s, size_t len, void *data) {


printf("%.*s\n", (int) len, (char *) s);
}

void cb2 (int c, void *data) {


printf("\n");
}

int main() {
struct csv_parser p;
char buf[1024];
size_t bytes_read;
FILE *file = fopen("example.csv", "r");

if (csv_init(&p, CSV_APPEND_NULL) != 0) {
fprintf(stderr, "Failed to initialize csv parser\n");
return 1;
}
while ((bytes_read=fread(buf, 1, 1024, file)) > 0) {
if (csv_parse(&p, buf, bytes_read, cb1, cb2, NULL) != bytes_read) {
fprintf(stderr, "Error while parsing file: %s\n", csv_strerror(csv_error(&p)));
return 1;
}
}

csv_fini(&p, cb1, cb2, NULL);


csv_free(&p);
fclose(file);
return 0;
}

In this code, we first initialize a `csv_parser` object with `csv_init`. We then


read the file in chunks and pass each chunk to `csv_parse`, which calls the
`cb1` callback for each field and the `cb2` callback for each row. Finally,
we call `csv_fini` to process any remaining data and `csv_free` to free the
parser.
The `cb1` callback function is called with the field data and its length. In
this example, we simply print the field as a string. The `cb2` callback
function is called at the end of each row. In this example, we simply print a
newline.
This is a basic overview of how to parse CSV files in C. There's a lot more
you can do, including handling errors, dealing with different CSV formats,
and more. However, this should give you a good starting point for working
with CSV data in your C programs. Always remember to check the return
values of the functions and handle any errors that might occur.

OceanofPDF.com
3.10.8 WORKING WITH ZIP
FILES
In this chapter, we will explore how to work with ZIP files in C using the
zlib and minizip libraries. These libraries provide functions for compressing
and decompressing data using the ZIP file format.

Setting Up zlib and minizip


Before we can start writing code, we need to install the zlib and minizip
libraries. This can typically be done through your system's package
manager. For example, on a Debian-based system like Ubuntu, you can use
the following command:

bash:

sudo apt-get install zlib1g-dev

The minizip library is part of zlib but is not always installed by default. You
may need to download and install it manually from the zlib website.

OceanofPDF.com
3.10.8.1 DECOMPRESSING
A ZIP FILE
Here's an example of how to decompress a ZIP file using minizip:

C:

#include <stdio.h>
#include <unzip.h>

#define CHUNK 16384

int main() {
unzFile *zipfile = unzOpen("example.zip");
if (zipfile == NULL) {
fprintf(stderr, "Failed to open ZIP file\n");
return 1;
}

int ret = unzGoToFirstFile(zipfile);


while (ret == UNZ_OK) {
char filename[256];
unz_file_info fileInfo;
unzGetCurrentFileInfo(zipfile, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0);

ret = unzOpenCurrentFile(zipfile);
if (ret != UNZ_OK) {
fprintf(stderr, "Failed to open file inside ZIP\n");
unzClose(zipfile);
return 1;
}
// Unzip (decompress) the file
FILE *out = fopen(filename, "wb");
if (out == NULL) {
fprintf(stderr, "Could not open output file\n");
return 1;
}
char buffer[CHUNK];
int readBytes;
while ((readBytes = unzReadCurrentFile(zipfile, buffer, CHUNK)) > 0) {
fwrite(buffer, 1, readBytes, out);
}

fclose(out);

unzCloseCurrentFile(zipfile);
ret = unzGoToNextFile(zipfile);
}

unzClose(zipfile);
return 0;
}

In this code, we first open the ZIP file with `unzOpen`. We then loop over
each file in the ZIP file with `unzGoToFirstFile` and `unzGoToNextFile`.
For each file, we open it with `unzOpenCurrentFile`, and decompress it,
and then close it with `unzCloseCurrentFile`.

OceanofPDF.com
3.10.8.2 COMPRESSING A
ZIP FILE
Here's an example of how to compress a file into a ZIP file using minizip:

C:

#include <stdio.h>
#include <zip.h>

int main() {
zipFile *zipfile = zipOpen("example.zip", APPEND_STATUS_CREATE);
if (zipfile == NULL) {
fprintf(stderr, "Failed to create ZIP file\n");
return 1;
}

FILE *file = fopen("example.txt", "r");


if (file == NULL) {
fprintf(stderr, "Failed to open file\n");
zipClose(zipfile, NULL);
return 1;
}

zip_fileinfo fileinfo = {0};


int ret = zipOpenNewFileInZip(zipfile, "example.txt", &fileinfo, NULL,
0, NULL, 0, NULL,
Z_DEFLATED, Z_DEFAULT_COMPRESSION);
if (ret != ZIP_OK) {
fprintf(stderr, "Failed to add file to ZIP\n");
fclose(file);
zipClose(zipfile, NULL);
return 1;
}

char buffer[4096];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
zipWriteInFileInZip(zipfile, buffer, bytes_read);
}

zipCloseFileInZip(zipfile);
fclose(file);
zipClose(zipfile, NULL);
return 0;
}

In this code, we first create a new ZIP file with `zipOpen`. We then open the
file we want to compress with `fopen`. We add a new file to the ZIP file
with `zipOpenNewFileInZip`, write data to it with `zipWriteInFileInZip`,
and then close it with `zipCloseFileInZip`.

Error Handling
Both zlib and minizip provide functions for error handling. For example,
the `unzOpen`, `unzGoToFirstFile`, `unzOpenCurrentFile`, `zipOpen`,
`zipOpenNewFileInZip`, and `zipWriteInFileInZip` functions all return an
error code if they fail. You should always check the return value of these
functions and handle any errors that occur.

Cleaning Up
Finally, after we're done with the ZIP file, we close it using the `unzClose`
or `zipClose` function:
C:

unzClose(zipfile);

or

C:

zipClose(zipfile, NULL);

This is a basic overview of how to work with ZIP files in C using the zlib
and minizip libraries. There's a lot more you can do, including adding
multiple files to a ZIP file, compressing and decompressing data in memory,
and more. However, this should give you a good starting point for working
with ZIP files in your C programs. Always remember to check the return
values of the functions and handle any errors that might occur.

OceanofPDF.com
3.10.9 WORKING WITH
PDF FILES
The Portable Document Format (PDF) is a versatile file format that
encapsulates a complete description of a fixed-layout flat document,
including the text, fonts, graphics, and other information needed to display
it. In this chapter, we will delve into the structure of PDF documents,
focusing on how data is typically stored as characters (vectorized text),
images, or a mixture of both.

PDF Documents as Vectorized Text


Typically, PDF documents are stored as characters in a form known as
vectorized text. This means that the text in the document is represented by
mathematical shapes, or "vectors", rather than as a series of pixels (like a
bitmap image would be). This allows for the text to be selected, copied,
searched, and otherwise manipulated as text.
When a PDF document is created from a word processor or a similar
program, it usually contains vectorized text. This text can be extracted
programmatically using libraries such as MuPDF, Poppler, or PDFBox,
which interpret the vectors and produce the corresponding characters.

PDF Documents as Images


In some cases, PDF documents might be composed of images rather than
vectorized text. This is often the case for scanned documents or photos of
pages that have been saved as a PDF. In these documents, the text is not
represented as selectable characters, but rather as a raster image.
These images can contain text, but it's not directly accessible as characters.
Instead, to extract the text from these images, you need to use Optical
Character Recognition (OCR) techniques. OCR is a process that can detect
and recognize text within images. Libraries like Tesseract can be used to
perform OCR and extract text from images within PDF documents.

A Mixture of Both
PDF documents can also contain a mix of vectorized text and images. For
instance, a document might have some pages where the text is vectorized,
and other pages that are scans or photos. In such cases, the approach to
extracting data will depend on the individual pages.
Furthermore, PDFs can include other elements such as annotations,
hyperlinks, and form fields. These might not be extractable as regular text
or images, but can be accessed using specialized PDF processing libraries.
In conclusion, while PDF documents can hold data in various formats,
understanding whether a PDF is composed of vectorized text or image data
helps determine the best approach for extracting the required information.
It's worth noting that even within these categories, PDFs can vary greatly,
and a solution that works for one document might not work for another.
Hence, it's crucial to have a good understanding of the structure and
composition of your specific PDF documents for effective processing.
Extracting text from PDF document pages is an essential part of various
applications ranging from data mining to information retrieval and more. In
this chapter, we will explore how to perform this operation using the
MuPDF library in the C programming language.
MuPDF is a lightweight PDF, XPS, and E-book viewer and toolkit written
in portable C. It provides various functionalities including text extraction
from PDF documents.
Before we begin, ensure you have the MuPDF library installed on your
system.

OceanofPDF.com
3.10.9.1 BASIC TEXT
EXTRACTION FROM PDF
PAGE
In this chapter, we will learn how to extract text from a PDF document
using the C programming language and an open-source library called
Poppler.

Poppler is a PDF rendering library that is widely used in applications like


PDF viewers. While the main Poppler library is written in C++, it provides
a C interface that we can use in our program. The Poppler library is built on
top of other libraries such as Fontconfig, FreeType, and Cairo to provide
high-quality rendering of PDF documents.

Installing Poppler
Before we can use Poppler in our program, we need to install it. Many
Linux distributions have Poppler available in their package managers. For
example, on an Ubuntu system, you can install Poppler using the following
command:

bash:
sudo apt-get install libpoppler-glib-dev

This command installs the Poppler library along with its GLib-based
wrapper, which provides the C interface that we will be using.

Writing the Code


Let's look at a basic program that opens a PDF document and extracts the
text from each page:

C:

#include <poppler.h>

int main() {
PopplerDocument *doc;
GError *error = NULL;
gchar *uri;

uri = g_filename_to_uri("example.pdf", NULL, &error);

doc = poppler_document_new_from_file(uri, NULL, &error);


g_free(uri);

if (doc == NULL) {
g_print("Error creating document\n");
return 1;
}

int num_pages, i;
num_pages = poppler_document_get_n_pages(doc);

for (i = 0; i < num_pages; i++) {


PopplerPage *page;
gchar *text;

page = poppler_document_get_page(doc, i);


text = poppler_page_get_text(page);

g_print("%s", text);
g_free(text);
g_object_unref(G_OBJECT(page));
}
g_object_unref(G_OBJECT(doc));

return 0;
}

This program first converts the file path to a URI using the
`g_filename_to_uri` function. It then creates a new `PopplerDocument`
from the file using `poppler_document_new_from_file`. If the document
cannot be created, the program prints an error message and exits.
The program then retrieves the number of pages in the document using
`poppler_document_get_n_pages` and loops over each page. For each page,
it creates a `PopplerPage` object using `poppler_document_get_page`,
extracts the text from the page using `poppler_page_get_text`, prints the
text, and then cleans up the `PopplerPage` and the text.
Finally, the program cleans up the `PopplerDocument` before exiting.

Building the Program


To build the program, you will need to compile it and link against the
Poppler and GLib libraries. If you are using a build system like CMake, you
can add the following lines to your `CMakeLists.txt` file to specify these
libraries:

cmake:

include_directories(/usr/include/glib-2.0 /usr/lib/glib-2.0/include /usr/include/poppler/glib)


target_link_libraries(your_program_name poppler-glib gobject-2.0 glib-2.0)

Then, you can build your program as you normally would.


This is just a basic example of what you can do with Poppler. The Poppler
library supports many more features of the PDF format, including graphics,
form fields, and annotations. By exploring the Poppler API, you can build
more complex applications for working with PDF documents.
OceanofPDF.com
3.10.9.2 DETECTING IF
PDF PAGE IS TEXT OR
IMAGE
The Portable Document Format (PDF) is a versatile file type that can
contain a mix of text, images, and other data. Often, it's important to
differentiate between these different types of content. For instance, you
might need to know whether a given page is primarily made up of readable
text or if it's mostly an image, such as in the case of a scanned document.
To perform this analysis, we can use Poppler, a free and open-source library
used for rendering PDF documents. However, Poppler does not natively
support detailed analysis of image content within a PDF. Instead, we can
use Poppler to extract text content, which indirectly gives us information
about whether a page is image-based (like a scanned document) or text-
based.
Consider the following C code example:

C:

#include <poppler.h>

int main() {
PopplerDocument *doc;
GError *error = NULL;
gchar *uri;

uri = g_filename_to_uri("example.pdf", NULL, &error);

doc = poppler_document_new_from_file(uri, NULL, &error);


g_free(uri);
if (doc == NULL) {
g_print("Error creating document\n");
return 1;
}

int num_pages, i;
num_pages = poppler_document_get_n_pages(doc);

for (i = 0; i < num_pages; i++) {


PopplerPage *page;
gchar *text;

page = poppler_document_get_page(doc, i);


text = poppler_page_get_text(page);

if (*text)
g_print("Page %d contains text.\n", i+1);
else
g_print("Page %d does not contain text.\n", i+1);

g_free(text);
g_object_unref(G_OBJECT(page));
}

g_object_unref(G_OBJECT(doc));

return 0;
}

This program opens a PDF file and iterates through each page, attempting
to extract text. For each page, if text extraction returns a non-empty string,
we infer that the page contains textual content. If the extraction returns an
empty string, we infer that the page does not contain (readable) text.
Please note that this approach is somewhat indirect and has its limitations. It
can't distinguish between a page that is entirely an image (like a scanned
document) and a blank page, or a page with non-text elements. In order to
make this distinction, you would need to use image analysis techniques on
each page.
In addition, if your PDF contains scanned documents that have undergone
Optical Character Recognition (OCR), the text from those documents will
be extractable, even though visually they may appear as images.
In summary, while Poppler doesn't provide a direct way to distinguish text
from image content, it provides us with tools to make a reasonable
inference, especially in the context of text extraction tasks. For more
detailed image analysis, consider integrating other libraries or tools into
your workflow.

OceanofPDF.com
3.10.9.3 CONVERTING
SPECIFIC PDF PAGES TO
IMAGES
In scenarios where you need to convert a specific page of a PDF to an
image, the Poppler utility `pdftoppm` offers an efficient solution. It allows
you to specify both the first and last pages to convert using the `-f` and `-l`
flags, respectively. When converting a single page, you simply set both
flags to the desired page number.
For instance, to convert solely the third page of a PDF to an image, you
would execute the following command:

bash:

pdftoppm -png -r 300 -f 3 -l 3 input.pdf output

In the command above:


- `-f 3` designates the first page to be converted as page 3.
- `-l 3` designates the last page to be converted as page 3.
Consequently, this command only converts the third page of the input PDF
into an image.
The `pdftoppm` utility can be called directly from a C program using the
`system()` function. Here is an example:

C:

#include <stdlib.h>
int main() {
system("pdftoppm -png -r 300 -f 3 -l 3 input.pdf output");
return 0;
}

This simple program will execute the `pdftoppm` command to convert the
third page of "input.pdf" into a PNG image with a resolution of 300 DPI.
The resulting image file will be saved as "output-3.png" in the current
working directory.
Caution: If you're working with user-provided inputs, be very cautious
when using the `system()` function. Always ensure that you thoroughly
sanitize the inputs to prevent potential security risks, as the `system()`
function can execute arbitrary shell commands. For example, an attacker
could provide a filename like `"; rm -rf /"` which would have devastating
effects when passed to `system()`. Always validate and sanitize user inputs
when using this function.

OceanofPDF.com
3.10.9.4 CONVERTING PDF
PAGE TO IMAGE USING
POPPLER'S C API
When working with PDF files in C, you may come across scenarios where
you need to convert a page of the PDF to an image. This might be
particularly relevant when dealing with OCR (Optical Character
Recognition) tasks, where the text content of the PDF needs to be extracted
from a scanned image. One way to achieve this is by utilizing the Poppler
library's C API, which allows for extensive interactions with PDF files.
As a practical example, suppose you need to convert a page to an image
with a resolution of 300 DPI (dots per inch). The process to achieve this
involves scaling the Cairo context using the `cairo_scale()` function. This
scaling is necessary because in the PDF coordinate system, 1 unit is
equivalent to 1/72 of an inch. To achieve a resolution of 300 DPI, we need
to scale by 300/72, which is approximately 4.1667.
The following code demonstrates this process:

C:

#include <poppler.h>
#include <cairo.h>

int main() {
PopplerDocument *doc;
GError *error = NULL;
gchar *uri;
int page_num = 1; // Page number to convert

uri = g_filename_to_uri("input.pdf", NULL, &error);


doc = poppler_document_new_from_file(uri, NULL, &error);
g_free(uri);

if (doc == NULL) {
g_print("Error creating document\n");
return 1;
}

PopplerPage *page = poppler_document_get_page(doc, page_num - 1);


if (page == NULL) {
g_print("Error, page not found\n");
return 1;
}

double width, height;


poppler_page_get_size(page, &width, &height);

double scale = 300.0 / 72.0; // Scale factor for 300 DPI


width *= scale;
height *= scale;

cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width,


height);
cairo_t *cr = cairo_create(surface);

cairo_scale(cr, scale, scale); // Apply the scale to the Cairo context

poppler_page_render(page, cr);
cairo_surface_write_to_png(surface, "output-300dpi.png");

cairo_destroy(cr);
cairo_surface_destroy(surface);
g_object_unref(page);
g_object_unref(doc);

return 0;
}
In this code, the PDF document is first loaded, and a specific page is
selected for conversion. The size of the page in the PDF document is then
obtained, and a scaling factor for a 300 DPI resolution is calculated. A new
Cairo surface is created with the scaled dimensions, and the page from the
PDF document is rendered onto this surface. The surface is then saved as a
PNG image.
The output of this code will be a PNG image representation of the specified
page of the PDF document, rendered at a resolution of 300 DPI. This
resolution is achieved due to the page size returned by
`poppler_page_get_size()`, which is measured in points where 1 point
equals 1/72 of an inch.
Using Poppler's C API in this manner allows you to convert specific pages
of PDF documents to high-resolution images, which can be beneficial for
various purposes such as preparing data for OCR tasks.

OceanofPDF.com
3.11 CONCURRENCY
In the modern world of computing, the ability to perform multiple tasks
simultaneously is a fundamental requirement. This is where the concept of
concurrency comes into play. Concurrency is a property of systems in
which several independent tasks are executing in overlapping time
intervals, maximizing the utilization of computing resources and improving
the overall performance of the system.
In the context of the C programming language, understanding concurrency
is crucial. C, being a low-level language, provides a rich set of features and
libraries that allow programmers to manage and control concurrent
execution in their applications. However, with great power comes great
responsibility. Concurrency, while powerful, introduces a new class of
potential bugs and complexities. Issues such as race conditions, deadlocks,
and resource starvation are common pitfalls that can occur in concurrent
programs.
This chapter aims to provide a comprehensive overview of concurrency in
C. We will start by defining the concept of concurrency and discussing its
advantages and challenges. We will then delve into the various techniques
and constructs used to achieve concurrency in C, such as threads, processes,
and synchronization primitives.
We will explore the creation and management of threads and processes, the
fundamental units of concurrent execution. We will also discuss inter-
process communication and synchronization, which are essential for
coordinating concurrent tasks and preventing concurrency-related issues.
Moreover, we will cover the topic of locks, mutexes, semaphores, and
condition variables, which are used to protect shared resources and
synchronize the execution of threads or processes. We will also look at
some of the common pitfalls and challenges in concurrent programming
and provide strategies to identify and avoid them.
Through practical examples and detailed explanations, this chapter will
equip you with the knowledge and skills to write efficient and reliable
concurrent programs in C. Whether you are building a multi-threaded web
server or a high-performance computing application, the concepts and
techniques covered in this chapter will be invaluable.
So, let's embark on this journey to unravel the complexities of concurrency
in C and learn how to harness its power to build faster and more efficient
applications.

OceanofPDF.com
3.11.1 UNDERSTANDING
PTHREAD_CREATE AND
PTHREAD_JOIN
In this chapter, we will delve into two fundamental functions provided by
the POSIX threads (pthreads) library in C: `pthread_create` and
`pthread_join`. These functions are essential for creating and managing
threads, the basic units of execution in concurrent programming.

pthread_create
The `pthread_create` function is used to create a new thread. The syntax of
`pthread_create` is as follows:

C:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,


​void *(*start_routine) (void *), void *arg);

Here's what each parameter means:


- `thread`: This is a pointer to a `pthread_t` variable, which will hold the ID
of the new thread.
- `attr`: This is a pointer to a `pthread_attr_t` structure, which can be used to
set various attributes for the new thread, such as its stack size or scheduling
policy. If you pass `NULL` for this parameter, the thread is created with
default attributes.
- `start_routine`: This is a pointer to the function that the thread will run.
The function should take a single `void *` argument and return a `void *`
result.
- `arg`: This is a pointer to the argument that will be passed to the
`start_routine` function.
Here's an example of how to use `pthread_create`:

C:

#include <pthread.h>

void *my_thread(void *arg) {


// Do some work...
return NULL;
}

int main() {
pthread_t thread_id;
int result = pthread_create(&thread_id,
NULL, my_thread, NULL);
if (result != 0) {
// Handle error...
}
// Continue execution...
return 0;
}

In this example, we create a new thread that runs the `my_thread` function.
We pass `NULL` for the `attr` and `arg` parameters because we don't need
to set any special attributes or pass any arguments to the function.

pthread_join
The `pthread_join` function is used to wait for a thread to finish. The syntax
of `pthread_join` is as follows:

C:
int pthread_join(pthread_t thread, void **retval);

Here's what each parameter means:


- `thread`: This is the ID of the thread to wait for. This should be a
`pthread_t` value that was filled in by `pthread_create`.
- `retval`: This is a pointer to a location where `pthread_join` will store the
return value of the thread. If you pass `NULL` for this parameter, the return
value is ignored.
Here's an example of how to use `pthread_join`:

C:

#include <pthread.h>

void *my_thread(void *arg) {


// Do some work...
return NULL;
}

int main() {
pthread_t thread_id;
pthread_create(&thread_id, NULL, my_thread, NULL);

// Do some other work...

int result = pthread_join(thread_id, NULL);


if (result != 0) {
// Handle error...
}
// Continue execution...
return 0;
}
Please note! Compilation should be done with gcc my_program.c -lpthread
-lrt
In this example, after creating the thread, we do some other work and then
call `pthread_join` to wait for the thread to finish. We pass `NULL` for the
`retval` parameter because we don't care about the thread's return value.
In conclusion, `pthread_create` and `pthread_join` are fundamental
functions for working with threads in C. By understanding how to use these
functions, you can create multithreaded programs that can perform multiple
tasks simultaneously, making your
programs more efficient and responsive. However, keep in mind that
multithreaded programming introduces new complexities and potential
pitfalls, such as race conditions and deadlocks. Always carefully design
your programs to ensure that threads correctly coordinate their work and
safely share resources.

OceanofPDF.com
3.11.2 PASSING
PARAMETERS TO
THREAD FUNCTIONS
In concurrent programming with threads, it's often necessary to pass
parameters to the functions that the threads will run. This allows each
thread to have its own unique data to work with, or to share data between
threads. In this chapter, we will explore how to pass parameters to thread
functions in C using the POSIX threads (pthreads) library.

Passing a Single Parameter


The `pthread_create` function allows you to pass a single `void *` argument
to the thread function. This can be a pointer to any type of data. Here's an
example:

C:

#include <pthread.h>
#include <stdio.h>

void *print_number(void *arg) {


int *number_ptr = (int *)arg;
printf("Number: %d\n", *number_ptr);
return NULL;
}

int main() {
pthread_t thread_id;
int number = 42;
pthread_create(&thread_id, NULL, print_number, &number);
pthread_join(thread_id, NULL);
return 0;
}

In this example, the `print_number` function takes a `void *` argument,


casts it to an `int *`, and then dereferences it to print the number. In the
`main` function, we pass the address of `number` as the argument to
`pthread_create`.

Passing Multiple Parameters


If you need to pass multiple parameters to the thread function, you can
define a struct that holds all the parameters, and then pass a pointer to the
struct. Here's an example:

C:

#include <pthread.h>
#include <stdio.h>

// Define a struct that will be passed as argument


typedef struct {
char* name;
int age;
float grade;
// you can add other fields here
} Student;

void *print_number(void *arg) {


Student *data = (Student *)arg;
printf("Name: %s, Age: %d, Grade: %.2f\n", data->name, data->age, data->grade);
return NULL;
}
int main() {
pthread_t thread_id;

// Create and populate the struct


Student data;
data.name = "Adam";
data.age = 22;
data.grade = 4.2;

// Pass the address of the struct as the argument


pthread_create(&thread_id, NULL, print_number, &data);

pthread_join(thread_id, NULL);
return 0;
}

In this example, the `thread_args` struct holds two integers. The


`print_numbers` function casts its argument to a `thread_args *` and then
accesses the fields of the struct. In the `main` function, we create a
`thread_args` variable, set its fields, and then pass its address to
`pthread_create`.

Caution with Local Variables


When passing parameters to a thread function, be careful if the parameters
are local variables. If the function that calls `pthread_create` (such as
`main` in the above examples) returns before the thread has a chance to use
the parameters, those variables may go out of scope and their values may be
lost. To avoid this, you can dynamically allocate memory for the
parameters, or ensure that the function doesn't return until the thread is
done, for example by calling `pthread_join`.
In conclusion, passing parameters to thread functions in C is a powerful
technique that allows each thread to have its own unique data or to share
data between threads. By understanding how to use this technique, you can
write more flexible and powerful multithreaded programs.

OceanofPDF.com
3.11.3 LOCKS, MUTEXES,
SEMAPHORES, AND
CONDITION VARIABLES
In concurrent programming, multiple threads or processes often need to
access shared resources. This can lead to race conditions, where the
behavior of the program depends on the relative timing of these accesses.
To prevent race conditions and ensure that shared resources are accessed in
a controlled and predictable manner, we use synchronization primitives
such as locks, mutexes, semaphores, and condition variables. In this
chapter, we will explore these concepts in detail.

Locks
A lock is a basic synchronization primitive that provides mutual exclusion.
This means that only one thread can hold the lock at a time. Other threads
that attempt to acquire the lock while it's held by another thread will be
blocked until the lock is released. In C, you can use the POSIX threads
(pthreads) library to work with locks. Here's a simple example:

C:

#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void critical_section() {
pthread_mutex_lock(&lock);
// Access shared resources
pthread_mutex_unlock(&lock);
}

In this code, we first initialize a lock with


`PTHREAD_MUTEX_INITIALIZER`. We then acquire the lock with
`pthread_mutex_lock` before accessing shared resources, and release it with
`pthread_mutex_unlock` afterwards.

Mutexes
A mutex (short for "mutual exclusion") is similar to a lock. It provides
mutual exclusion, ensuring that only one thread can access a critical section
at a time. The terms "lock" and "mutex" are often used interchangeably, but
some systems or libraries may provide additional features for mutexes, such
as error checking, recursive locking, or priority inversion protection.

Semaphores
A semaphore is a more advanced synchronization primitive that can be used
to control access to multiple instances of a resource or to synchronize
multiple threads. A semaphore maintains a count of available resources.
Threads can decrement the count with a "wait" operation or increment the
count with a "signal" operation. If a thread performs a wait operation while
the count is zero, the thread will be blocked until another thread performs a
signal operation. Here's an example using the POSIX semaphores library:

C:

#include <semaphore.h>

sem_t sem;

void init() {
sem_init(&sem, 0, 1);
}
void critical_section() {
sem_wait(&sem);
// Access shared resources
sem_post(&sem);
}

In this code, we first initialize a semaphore with `sem_init`. The second


argument is `0` to indicate that the semaphore is shared between threads of
a process, and the third argument is `1` to set the initial value of the
semaphore. We then use `sem_wait` to decrement the semaphore and
`sem_post` to increment it.

Condition Variables
A condition variable is a synchronization primitive that allows threads to
wait until a certain condition is met. A thread can wait on a condition
variable, and another thread can signal the condition variable to wake up
one or more waiting threads. Condition variables are often used in
conjunction with a mutex to protect the condition. Here's an example:

C:

#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;


pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;

void produce() {
pthread_mutex_lock(&lock);
ready = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
}
void consume() {
pthread_mutex_lock(&lock);
while (!ready) {
pthread_cond_wait(&cond, &lock);
}
// Consume the resource
ready = 0;
pthread_mutex_unlock(&lock);
}

In this code, we first initialize a mutex and a condition variable. We then


define a `produce` function that produces a resource, sets `ready` to `1`, and
signals the condition variable. The `consume` function waits for `ready` to
be `1`. If `ready` is `0`, it waits on the condition variable. When it's signaled
by the `produce` function, it consumes the resource and sets `ready` back to
`0`.
These are the basic synchronization primitives that you can use in C to
control access to shared resources and synchronize threads. It's important to
use them correctly to avoid issues such as deadlocks, where two or more
threads are unable to proceed because each is waiting for the other to
release a resource. Always remember to release any locks, mutexes, or
semaphores that you acquire, and be careful when using condition variables
to avoid spurious wakeups or missed signals.

Deadlocks and Race Conditions


While using these synchronization primitives, it's crucial to be aware of
potential pitfalls like deadlocks and race conditions. A deadlock occurs
when two or more threads are unable to proceed because each is waiting for
the other to release a resource. A race condition, on the other hand, happens
when the behavior of a program depends on the relative timing of events,
such as the order in which threads are scheduled.
To avoid deadlocks, one common strategy is to always acquire locks in a
pre-defined order. If two threads need to acquire locks A and B, both should
acquire them in the same order, either A then B or B then A.
Race conditions can be avoided by ensuring that shared data is always
accessed in a mutually exclusive manner. This can be achieved by using
locks, mutexes, semaphores, or other synchronization primitives to protect
the shared data.

Conclusion
In conclusion, locks, mutexes, semaphores, and condition variables are
powerful tools for managing concurrency in C. They allow you to control
access to shared resources and coordinate the execution of multiple threads
or processes. By understanding these concepts and using them correctly,
you can write concurrent programs that are efficient, correct, and free of
race conditions and deadlocks.

OceanofPDF.com
3.11.4 AVOIDING
DEADLOCKS AND RACE
CONDITIONS
In this chapter, we will delve into practical examples of concurrent
programming in C, focusing on strategies to avoid common pitfalls such as
deadlocks and race conditions. We will demonstrate how to use
synchronization primitives like locks and mutexes to ensure that shared data
is accessed in a mutually exclusive manner, and how to avoid deadlocks by
acquiring locks in a pre-defined order.

Avoiding Race Conditions


Race conditions occur when the behavior of a program depends on the
relative timing of events, such as the order in which threads are scheduled.
To avoid race conditions, we can use locks or mutexes to ensure that shared
data is accessed in a mutually exclusive manner. Here's an example:

C:

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;


int shared_data = 0;

void increment() {
pthread_mutex_lock(&lock);
shared_data++;
printf("incremented: %d\n", shared_data);
pthread_mutex_unlock(&lock);
}

void decrement() {
pthread_mutex_lock(&lock);
shared_data--;
printf("decremented: %d\n", shared_data);
pthread_mutex_unlock(&lock);
}

int main() {
pthread_t t3, t4;

// Create threads
pthread_create(&t3, NULL, (void *)increment, NULL);
pthread_create(&t4, NULL, (void *)decrement, NULL);

// Wait for threads to finish


pthread_join(t3, NULL);
pthread_join(t4, NULL);

return 0;
}

In this example, `increment` and `decrement` both modify `shared_data`. To


ensure that these modifications are mutually exclusive and avoid a race
condition, both functions acquire `lock` before modifying `shared_data` and
release it afterwards.

Avoiding Deadlocks
Deadlocks can occur when two or more threads each hold a lock and wait
for the other to release their lock. One common strategy to avoid deadlocks
is to always acquire locks in a pre-defined order. Here's an example:

C:
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;


pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

int resource1 = 0;
int resource2 = 0;

void* thread1() {
pthread_mutex_lock(&lock1);
// Do some work with resource 1
resource1 += 1;
printf("Thread 1 incremented resource 1. It's now %d.\n", resource1);
pthread_mutex_unlock(&lock1);

pthread_mutex_lock(&lock2);
// Do some work with resource 2
resource2 += 1;
printf("Thread 1 incremented resource 2. It's now %d.\n", resource2);
pthread_mutex_unlock(&lock2);

return NULL;
}

void* thread2() {
pthread_mutex_lock(&lock1); // Note the same order of acquiring locks
// Do some work with resource 1
resource1 += 1;
printf("Thread 2 incremented resource 1. It's now %d.\n", resource1);
pthread_mutex_unlock(&lock1);

pthread_mutex_lock(&lock2);
// Do some work with resource 2
resource2 += 1;
printf("Thread 2 incremented resource 2. It's now %d.\n", resource2);
pthread_mutex_unlock(&lock2);

return NULL;
}

int main() {
pthread_t t1, t2;

// Create threads
pthread_create(&t1, NULL, thread1, NULL);
pthread_create(&t2, NULL, thread2, NULL);

// Wait for threads to finish


pthread_join(t1, NULL);
pthread_join(t2, NULL);

return 0;
}

In this example, both `thread1` and `thread2` need to access two shared
resources, protected by `lock1` and `lock2`. To avoid a deadlock, both
threads acquire the locks in the same order: `lock1` first, then `lock2`.
These examples demonstrate the basic techniques for avoiding deadlocks
and race conditions in concurrent programming in C. However, keep in
mind that concurrency is a complex topic and these techniques may not be
sufficient for all situations. Always carefully analyze your program to
identify potential concurrency issues and use the appropriate
synchronization primitives and strategies to address them.

OceanofPDF.com
3.12 GRAPHICS
PROGRAMMING WITH
OPENGL
Welcome to this enlightening journey into the world of graphics
programming with the OpenGL library in the C programming language.
This chapter is designed to provide you with a comprehensive
understanding of the OpenGL library and its immense capabilities,
particularly in the context of C programming.
OpenGL, which stands for Open Graphics Library, is a powerful cross-
platform, cross-language API that is used for rendering 2D and 3D vector
graphics. Its versatility and efficiency have made it a standard choice for
graphics programming across various domains, including game
development, simulation, and even scientific visualization.
In this chapter, we will delve into the intricacies of the OpenGL library,
exploring its fundamental concepts, functionalities, and the vast array of
graphical tasks it can perform. We will start with the basics of setting up an
OpenGL environment in a C program and gradually move towards more
complex topics such as drawing shapes, handling user input, and even
creating animations.
We will provide practical code examples at each step, demonstrating how to
effectively use the OpenGL functions to create visually appealing graphics.
We will also discuss the best practices for graphics programming with
OpenGL, helping you write efficient and maintainable code.
By the end of this chapter, you will have a solid understanding of the
OpenGL library and its use in C programming. You will be equipped with
the knowledge and skills to create stunning graphics and animations in your
C programs, opening up a world of possibilities for your future projects.
So, let's embark on this exciting journey into the world of graphics
programming with OpenGL and C. Whether you are a novice programmer
or an experienced developer looking to expand your skills, this chapter
promises to be a valuable resource. Let's get started!

OceanofPDF.com
3.12.1 DRAWING SIMPLE
SHAPES WITH OPENGL
OpenGL (Open Graphics Library) is a powerful cross-language, cross-
platform application programming interface (API) for rendering 2D and 3D
vector graphics. In this chapter, we will explore how to use OpenGL to
draw simple shapes in C.

Creating a Window
First, we need to create a window where we can draw our shapes. We can
use the GLUT (OpenGL Utility Toolkit) library, which provides a portable
API for handling windows and user input. Here's how to create a window:

C:

#include <GL/glut.h>

void display() {
// This function will be filled in later
}

int main(int argc, char** argv) {


glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL - Creating a window");
glutDisplayFunc(display);
glutMainLoop();
return 0;
}

In this example, we first initialize GLUT with `glutInit`, set the display
mode with `glutInitDisplayMode`, set the window size with
`glutInitWindowSize`, set the window position with
`glutInitWindowPosition`, and create the window with
`glutCreateWindow`. We then set the display callback function with
`glutDisplayFunc` and enter the GLUT event processing loop with
`glutMainLoop`.

Drawing a Triangle
Now let's draw a triangle. We can do this by specifying the coordinates of
the triangle's vertices and using the `glVertex` function to draw the vertices:

C:

#include <GL/glut.h>

void display() {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set background color to black and opaque
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer

// Draw a Red 1x1 Square centered at origin


glBegin(GL_TRIANGLES); // Each set of 3 vertices form a triangle
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex2f(-0.5f, -0.5f); // x, y
glVertex2f( 0.5f, -0.5f);
glVertex2f( 0.0f, 0.5f);
glEnd();

glFlush(); // Render now


}
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL - Drawing a Triangle");
glutDisplayFunc(display);
glutMainLoop();
return 0;
}

In this example, we first clear the color buffer with `glClear`. We then start
drawing a triangle with `glBegin`, set the color to red with `glColor3f`,
draw the vertices with `glVertex2f`, and end the drawing with `glEnd`. We
then render the drawing with `glFlush`.
You can draw other shapes such as squares, rectangles, and circles by
specifying the appropriate vertices and using the appropriate OpenGL
functions. Remember to always clear the color buffer before drawing and to
render your drawing after you're done.

OceanofPDF.com
3.12.2 CREATING SIMPLE
ANIMATIONS WITH
OPENGL
Animation is a powerful tool that can bring your applications to life. In this
chapter, we will explore how to create simple animations using the OpenGL
library in C.

Creating a Window
As in the previous chapter, we first need to create a window where we can
draw our animation. We can use the GLUT library for this purpose:

C:

#include <GL/glut.h>

void display() {
// This function will be filled in later
}

int main(int argc, char** argv) {


glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL - Creating a window");
glutDisplayFunc(display);
glutMainLoop();
return 0;
}

In this example, we use `GLUT_DOUBLE` in the display mode to enable


double buffering, which is necessary for animation.

Animating a Square
Let's create a simple animation of a square moving across the screen. We
can do this by changing the position of the square in each frame:

C:

#include <GL/glut.h>

float x_position = 0.0f; // Horizontal position of the square


float delta = 0.01f; // Amount to increase the position each frame

void display() {
glClear(GL_COLOR_BUFFER_BIT);

// Draw a square
glBegin(GL_QUADS);
glVertex2f(x_position, 0.5f);
glVertex2f(x_position + 0.5f, 0.5f);
glVertex2f(x_position + 0.5f, -0.5f);
glVertex2f(x_position, -0.5f);
glEnd();

// Swap buffers to display the next frame


glutSwapBuffers();

// Update the position for the next frame


x_position += delta;

// Reverse direction when the square reaches the edge of the screen
if (x_position >= 1.0f || x_position <= -1.0f) {
delta = -delta;
}

// Redisplay the screen


glutPostRedisplay();
}

int main(int argc, char** argv) {


glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL - Simple Animation");
glutDisplayFunc(display);
glutMainLoop();
return 0;
}

In this example, we first clear the color buffer with `glClear`. We then draw
a square at the current position with `glBegin` and `glVertex2f`, and swap
the buffers with `glutSwapBuffers` to display the next frame. We then
update the position for the next frame and reverse the direction if the square
reaches the edge of the screen. Finally, we call `glutPostRedisplay` to
redraw the screen for the next frame.
This is a simple example of animation in OpenGL. By changing the
position, size, color, or other properties of your shapes over time, you can
create more complex animations.

OceanofPDF.com
3.12.3 HANDLING MOUSE
EVENTS IN OPENGL
Creating interactive applications in OpenGL involves more than just
rendering objects on the screen. In many cases, user input, such as mouse
clicks, plays a significant role in how these applications behave. In this
chapter, we will explore how to handle mouse events to draw lines in an
OpenGL window.

Example 1: Drawing a Single Line on Mouse Clicks


The first example demonstrates a basic scenario: drawing a single line
based on two points, each defined by a mouse click.
This program waits for two mouse clicks. The first click sets the start point
of the line and the second click determines the end point. Once both points
have been defined, the program draws a red line connecting these points. To
achieve this, we utilize the GLUT (GL Utility Toolkit) mouse function
which helps to handle mouse events.
It's important to mention that the mouse's y-coordinate is inverted in
computer graphics (origin at the top), hence we convert the coordinates
accordingly to make sure the line is drawn at the correct position.

C:

#include <GL/glut.h>

int point1[2], point2[2]; // Coordinates of the two points


int point_count = 0; // Number of points specified

void display() {
glClear(GL_COLOR_BUFFER_BIT);
if (point_count == 2) {
glColor3f(1.0, 0.0, 0.0); // Set color to red
glBegin(GL_LINES);
glVertex2i(point1[0], point1[1]);
glVertex2i(point2[0], point2[1]);
glEnd();
glFlush();
}
}

void reshape(int w, int h){


glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0,w,h,0);
glMatrixMode(GL_MODELVIEW);
}

void mouse(int button, int state, int x, int y) {


if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
if (point_count == 0) {
point1[0] = x;
point1[1] = y;
point_count++;
} else if (point_count == 1) {
point2[0] = x;
point2[1] = y;
point_count++;
glutPostRedisplay();
}
}
}
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL - Drawing a Line with Mouse Clicks");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse); // Register the mouse callback function
glutMainLoop();
return 0;
}

Example 2: Drawing Multiple Lines on Mouse Clicks


The second example extends the first by allowing the user to draw multiple
lines. Each pair of mouse clicks defines a new line. The start point of the
line is set by the first click, and the end point is defined by the second click.
After a pair of clicks, a new line is drawn in addition to the previous lines.
To keep track of all points, we use a dynamic array of `Point` structures,
which grow as the user clicks the mouse. After each pair of clicks,
`glutPostRedisplay` is called to redraw the screen with all defined lines.

C:

#include <GL/glut.h>
#include <stdlib.h>

typedef struct {
int x, y;
} Point;

Point* points = NULL;


int point_count = 0; // Number of points specified
void display() {
glClear(GL_COLOR_BUFFER_BIT);

glColor3f(1.0, 0.0, 0.0); // Set color to red


for (int i = 0; i < point_count - 1; i += 2) {
glBegin(GL_LINES);
glVertex2i(points[i].x, points[i].y);
glVertex2i(points[i + 1].x, points[i + 1].y);
glEnd();
}

glFlush();
}

void reshape(int w, int h){


glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0,w,h,0);
glMatrixMode(GL_MODELVIEW);
}

void mouse(int button, int state, int x, int y) {


if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
points = (Point*)realloc(points, sizeof(Point) * (point_count + 1));
points[point_count].x = x;
points[point_count].y = y;
point_count++;
if (point_count % 2 == 0) {
glutPostRedisplay();
}
}
}

int main(int argc, char** argv) {


glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL - Drawing a Line with Mouse Clicks");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse); // Register the mouse callback function
glutMainLoop();
return 0;
}

Example 3: Real-Time Line Drawing


The third example adds a layer of interactivity by allowing users to draw a
line in real-time. As the user holds down the mouse button and drags the
cursor, a line is drawn from the click point to the current cursor position,
updating as the mouse moves.
We accomplish this by introducing a "drawing" flag that tracks whether the
user is currently holding down the mouse button. When the mouse button is
pressed, the flag is set, and the starting point of the line is defined. As the
mouse moves, the end point of the line is continuously updated to the
current mouse position, which is achieved by using `glutMotionFunc` to
register a callback that is called when the mouse moves.
When the mouse button is released, the "drawing" flag is cleared, indicating
that we're done drawing the line. Note that the drawn line doesn't persist on
the screen after the mouse button is released. If you want the line to persist,
you would need to store each line's start and end points in a data structure,
similar to Example 2.

C:

#include <GL/glut.h>

int startPoint[2]; // Starting point of the line


int endPoint[2]; // End point of the line
int drawing = 0; // Boolean indicating if we are currently drawing

void display() {
glClear(GL_COLOR_BUFFER_BIT);

if (drawing) {
glColor3f(1.0, 0.0, 0.0); // Set color to red
glBegin(GL_LINES);
glVertex2i(startPoint[0], startPoint[1]);
glVertex2i(endPoint[0], endPoint[1]);
glEnd();
}

glFlush();
}

void reshape(int w, int h) {


glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, w, h, 0);
glMatrixMode(GL_MODELVIEW);
}

void mouse(int button, int state, int x, int y) {


if (button == GLUT_LEFT_BUTTON) {
if (state == GLUT_DOWN) {
startPoint[0] = x;
startPoint[1] = y;
endPoint[0] = x;
endPoint[1] = y;
drawing = 1;
} else if (state == GLUT_UP) {
drawing = 0;
}
glutPostRedisplay();
}
}

void motion(int x, int y) {


if (drawing) {
endPoint[0] = x;
endPoint[1] = y;
glutPostRedisplay();
}
}

int main(int argc, char** argv) {


glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL - Drawing a Line with Mouse Clicks");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse); // Register the mouse callback function
glutMotionFunc(motion); // Register the motion callback function
glutMainLoop();
return 0;
}

All these examples are built on top of the GLUT library, which provides a
simple and portable way to create interactive applications with OpenGL. By
utilizing the mouse callback function provided by GLUT, we can easily
create applications that respond to mouse events.
Remember, these examples represent just a small fraction of what's possible
with mouse interactions in OpenGL. Depending on your application, you
might need to handle more complex scenarios, like detecting clicks on
specific objects, dragging objects, or even more complex gestures. The
principles, however, remain the same: capture the mouse events, determine
what action to take based on those events, and then update your scene
accordingly.

OceanofPDF.com
3.13 IMAGE OPERATIONS
AND GRAPHIC FORMATS
Welcome to this comprehensive exploration of image operations and
graphic formats using the C programming language. This chapter aims to
provide a deep dive into the realm of image processing and rendering, fields
that play crucial roles in many areas of computer science, including
computer vision, artificial intelligence, graphics, and more.
Image operations encompass a wide range of techniques that allow us to
manipulate and analyze images in various ways. From simple
transformations like scaling and rotation to more complex operations like
edge detection and histogram equalization, each technique offers unique
capabilities that can be harnessed for different purposes.
This chapter also introduces popular graphic formats such as SVG (Scalable
Vector Graphics), OBJ (Wavefront Object file), and DXF (Drawing
Exchange Format). These formats are widely used in the world of 3D
graphics and vector images, enabling artists and developers to create,
manipulate, and store complex geometric and graphic data. Understanding
these formats is critical for working with graphics in a programming
context.
In this chapter, we will delve into a variety of topics, including:
- Basic image operations such as blurring, sharpening, and converting
images to grayscale.
- Geometric transformations like scaling, rotation, and translation, as well
as more complex affine transformations.
- Adjustments to image properties such as brightness and contrast.
- Advanced techniques for image analysis, including edge detection,
thresholding, and histogram equalization and matching.
- Morphological operations like erosion, dilation, opening, and closing.
- More complex operations like image blending and difference extraction.
- An introduction to graphic formats like SVG, OBJ, and DXF, including
their structure, use cases, and how to work with them in C.
- Practical code examples that demonstrate how to generate, manipulate,
and store data in these formats.
For each operation and format, we will provide a detailed explanation of the
underlying concepts, followed by practical code examples demonstrating
how to implement these operations and work with these formats in C. We
will also discuss the applications and implications of each operation and
format, helping you understand not just how to use them, but also why and
when to use them.
By the end of this chapter, you will have a solid understanding of a wide
range of image operations and graphic formats and how to implement them
in C. Whether you're a beginner looking to learn the basics of image
processing and graphics, or an experienced programmer aiming to deepen
your knowledge, this chapter promises to be a valuable resource.
So, let's embark on this exciting journey into the world of image operations
and graphic formats in C. Let's get started!

OceanofPDF.com
3.13.1 DISPLAYING AN
IMAGE WITH THE SDL
LIBRARY
The Simple DirectMedia Layer (SDL) library is a low-level, cross-platform
library that provides hardware access in multimedia applications. One of the
many uses of SDL is to display images.
Here's a step-by-step guide on how to display an image using SDL in C:

Step 1: Include SDL and SDL Image Headers


The first step is to include the necessary header files. You'll need the main
SDL header, as well as the SDL_image header.

C:

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>

Step 2: Initialize SDL


Before using any SDL functionality, you need to initialize the library.

C:

if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize! SDL Error: %s\n", SDL_GetError());
return 1;
}
We also need to initialize the SDL_image library. This library extends
SDL's image loading capabilities, and is necessary for loading image
formats like JPEG and PNG.

C:

int imgFlags = IMG_INIT_PNG;


if (!(IMG_Init(imgFlags) & imgFlags)) {
printf("SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError());
return 1;
}

Step 3: Create a Window


Next, we create an SDL window where we can draw our image.

C:

SDL_Window* window = SDL_CreateWindow("SDL Image Display",


SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600,
SDL_WINDOW_SHOWN);
if (!window) {
printf("Window could not be created! SDL Error: %s\n", SDL_GetError());
return 1;
}

Step 4: Load an Image


Now we can load our image. We'll use the `IMG_Load` function from the
SDL_image library.

C:

SDL_Surface* image = IMG_Load("path_to_your_image.png");


if (!image) {
printf("Unable to load image! SDL_image Error: %s\n", IMG_GetError());
return 1;
}

Step 5: Draw the Image


To draw the image, we first need to get a handle to the window's surface,
then we can draw our image onto this surface.

C:

SDL_Surface* screenSurface = SDL_GetWindowSurface(window);


SDL_BlitSurface(image, NULL, screenSurface, NULL);
SDL_UpdateWindowSurface(window);

Step 6: Wait and Clean Up


Finally, we'll wait for a few seconds before exiting and cleaning up. This
will let us see the image before the program finishes.

C:

SDL_Delay(5000); // Wait five seconds

//Free resources and close SDL


SDL_FreeSurface(image);
SDL_DestroyWindow(window);
IMG_Quit();
SDL_Quit();

return 0;
Now, if you run this program and provide a valid image file path, the image
should appear in a window for five seconds.
This is a very simple example, and in a real application, you'll likely want
to add more complex control flow to handle user input, deal with errors
more robustly, and so on. However, this should give you a good start in
displaying images with SDL in C.

OceanofPDF.com
3.13.2 IMAGE
CONVERSION WITH
LEPTONICA LIBRARY
The Leptonica library is a comprehensive open-source library for image
processing and image analysis applications. It is widely used in the field of
document image analysis and computer vision. Here are some simple code
examples that demonstrate the use of the Leptonica library:
Leptonica provides functions to read and write images in various formats.
Here's a simple example of reading a JPEG image and writing it back out as
a PNG image:

C:

#include <leptonica/allheaders.h>

int main() {
PIX *pixs;

// Read a PNG image


pixs = pixRead("input.jpg");
if (pixs == NULL) {
fprintf(stderr, "Unable to read image\n");
return 1;
}

// Write the image as a JPEG image


if (pixWrite("output.png", pixs, IFF_PNG) != 0) {
fprintf(stderr, "Unable to write image\n");
pixDestroy(&pixs);
return 1;
}

// Clean up
pixDestroy(&pixs);
return 0;
}

OceanofPDF.com
3.13.3 IMAGE SCALING
WITH LEPTONICA
LIBRARY
Leptonica provides functions to scale images. Here's an example of scaling
an image to half its original size:

C:

#include "allheaders.h"

int main() {
PIX *pixs, *pixd;

// Read an image
pixs = pixRead("input.jpg");
if (pixs == NULL) {
fprintf(stderr, "Unable to read image\n");
return 1;
}

// Scale the image to half its original size


pixd = pixScale(pixs, 0.5, 0.5);
if (pixd == NULL) {
fprintf(stderr, "Unable to scale image\n");
pixDestroy(&pixs);
return 1;
}

// Write the scaled image


if (pixWrite("output.jpg", pixd, IFF_JFIF_JPEG) != 0) {
fprintf(stderr, "Unable to write image\n");
pixDestroy(&pixs);
pixDestroy(&pixd);
return 1;
}

// Clean up
pixDestroy(&pixs);
pixDestroy(&pixd);
return 0;
}

OceanofPDF.com
3.13.4 IMAGE ROTATION
WITH LEPTONICA
LIBRARY
Leptonica also provides functions to rotate images. Here's an example of
rotating an image by 45 degrees:

C:

#include <math.h>
#include "allheaders.h"

int main() {
PIX *pixs, *pixd;

// Read an image
pixs = pixRead("input.jpg");
if (pixs == NULL) {
fprintf(stderr, "Unable to read image\n");
return 1;
}

// Rotate the image by 45 degrees


pixd = pixRotate(pixs, 45 * M_PI / 180, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, 0, 0);
if (pixd == NULL) {
fprintf(stderr, "Unable to rotate image\n");
pixDestroy(&pixs);
return 1;
}
// Write the rotated image
if (pixWrite("output.jpg", pixd, IFF_JFIF_JPEG) != 0) {
fprintf(stderr, "Unable to write image\n");
pixDestroy(&pixs);
pixDestroy(&pixd);
return 1;
}

// Clean up
pixDestroy(&pixs);
pixDestroy(&pixd);

return 0;
}

These examples demonstrate some of the basic operations that can be


performed using the Leptonica library. The library provides a wide range of
functions for image processing and analysis, including operations for binary
and grayscale morphology, affine and projective transformations, color
quantization, skew detection, connected components, and much more.

The Leptonica library is written in ANSI C, making it portable and allowing


it to be used on a wide range of systems. It also provides a large number of
example programs that demonstrate how to use the library's functions,
making it a great resource for learning about image processing and analysis
in C.

OceanofPDF.com
3.13.5 IMAGE
DIFFERENCE
EXTRACTION WITH
LEPTONICA LIBRARY
Should one need to extract the difference between two images, for example
for motion detection, this can be realized via pixel subtraction.

Pixel Subtraction
A common requirement in image processing is to extract the differences
between two images. A simplified approach is to assume that the images
being compared are the same size and are 8-bit grayscale images.
Here is a basic example demonstrating this:

C:

#include "allheaders.h"

int main() {
PIX *pix1, *pix2, *pixd;

/* Load the two images. */


pix1 = pixRead("image1.png");
pix2 = pixRead("image2.png");

/* Subtract pix2 from pix1, clamping at 0. */


pixd = pixSubtractGray(NULL, pix1, pix2);
/* Write the result to a file. */
pixWrite("diff.png", pixd, IFF_PNG);

/* Cleanup. */
pixDestroy(&pix1);
pixDestroy(&pix2);
pixDestroy(&pixd);

return 0;
}

This simple program reads two images: "image1.png" and "image2.png",


and subsequently subtracts the second image from the first. The resulting
difference image is written to "diff.png". The function `pixSubtractGray()`
accomplishes the image subtraction.

Notes for Effective Use


Bear in mind, this code example does not include error checking. In
production-grade programs, it is essential to ensure that the images loaded
correctly, they are in the expected format and size, and the resulting image
was written out properly.
Moreover, Leptonica supports a plethora of image formats, thereby
eliminating the necessity for the images to exclusively be PNGs. Leptonica
ascertains the image format from the file extension. To load or write a
different format, the file extension can be altered accordingly; for instance,
".jpg" for JPEGs or ".tif" for TIFFs.

Further Exploration
Leptonica is a multifaceted library that offers an extensive array of
operations one can perform on images. There are numerous details to
consider when performing these operations. Therefore, it is beneficial to
consult the Leptonica documentation and scour for practical examples
online for deeper understanding and comprehensive utilization.
This chapter provides a fundamental overview of the image difference
extraction process using Leptonica. With further exploration and practice,
you can harness the full capabilities of this powerful library.

OceanofPDF.com
3.13.6 CONTRAST
STRETCHING WITH
LEPTONICA LIBRARY
Contrast stretching is a simple image enhancement technique that improves
the contrast in an image by stretching the range of intensity values it
contains to span a desired range of values.
Here is a simple example of how you can use Leptonica library for contrast
stretching in C:

C:

#include "allheaders.h"

int main() {
PIX *pixs, *pixd;

/* Load the source image. */


pixs = pixRead("/home/pi/tmp/input.jpeg");
if (pixs == NULL) {
fprintf(stderr, "failed to load image\n");
return 1;
}

/* Perform contrast stretching. */


pixd = pixContrastTRC(NULL, pixs, 1.0);
if (pixd == NULL) {
fprintf(stderr, "failed to enhance image\n");
pixDestroy(&pixs);
return 1;
}

/* Write the result to a file. */


if (pixWrite("/home/pi/tmp/destination.png", pixd, IFF_PNG)) {
fprintf(stderr, "failed to write image\n");
}

/* Cleanup. */
pixDestroy(&pixs);
pixDestroy(&pixd);

return 0;
}

This program reads an image from "source.png", enhances the contrast of


the image using the `pixContrastTRC` function, and then writes the
resulting image to "destination.png".
In this code, the `pixContrastTRC` function is used to enhance the contrast
of the image. The second argument is the source image, and the third
argument is the contrast factor. A contrast factor greater than 1.0 will
increase the contrast, while a contrast factor less than 1.0 (but greater than
0) will decrease the contrast.
Remember, error checking and handling is simplified in this example for
the sake of clarity. In a real-world application, you would want to add more
extensive error handling.

OceanofPDF.com
3.13.7 IMAGE
TRANSLATION WITH
LEPTONICA LIBRARY
Image translation is a common operation in image processing that involves
shifting an image in the horizontal and vertical directions. In this chapter,
we will explore how to perform image translation using the C language and
the Leptonica library. Leptonica is an open-source image processing library
that provides a set of powerful functions and data structures for image
manipulation.
Let's examine the complete code that demonstrates image translation using
Leptonica:

C:

#include <stdio.h>
#include <stdlib.h>
#include "allheaders.h"

int main()
{
PIX *image, *translatedImage;
l_int32 xTrans, yTrans;

// Load the image


image = pixRead("input.png");
if (image == NULL) {
fprintf(stderr, "Failed to load input image.\n");
return 1;
}
// Specify the translation offsets
xTrans = 100; // horizontal translation
yTrans = 50; // vertical translation

// Perform image translation


translatedImage = pixCreateTemplate(image);
pixClearAll(translatedImage); // Set white background
pixRasterop(translatedImage, xTrans, yTrans, image->w, image->h, PIX_SRC, image, 0, 0);

// Save the translated image


if (pixWrite("translated_image.png", translatedImage, IFF_PNG) != 0) {
fprintf(stderr, "Failed to save translated image.\n");
}

// Clean up
pixDestroy(&image);
pixDestroy(&translatedImage);

return 0;
}

1. Including the necessary header files: We begin by including the required


header files: `stdio.h`, `stdlib.h`, and `"allheaders.h"`. The `"allheaders.h"`
header provides access to the Leptonica library functions and data
structures.
2. Loading the input image: We use the `pixRead` function from Leptonica
to load the input image into a `PIX` structure named `image`. If the image
fails to load, an error message is displayed, and the program exits with a
non-zero return code.
3. Specifying the translation offsets: We declare and initialize two variables,
`xTrans` and `yTrans`, to specify the horizontal and vertical translation
offsets, respectively. Modify these values according to your desired
translation.
4. Performing image translation: To perform image translation, we create a
destination `PIX` structure named `translatedImage` using
`pixCreateTemplate` with the same dimensions as the original image. Then,
we use `pixClearAll` to set the entire `translatedImage` to white. Finally,
the `pixRasterop` function is called to perform the translation operation
using the specified translation offsets, the dimensions of the original image,
the `PIX_SRC` operation to copy the source image, and the source image
itself.
5. Saving the translated image: We use the `pixWrite` function to save the
translated image to a file. In this example, we save the image as a PNG file
named "translated_image.png". If the save operation fails, an error message
is displayed.
6. Cleaning up: To prevent memory leaks, we destroy the `image` and
`translatedImage` structures using `pixDestroy`.
We explored how to perform image translation in C language using the
Leptonica library. We learned how to load an image, specify translation
offsets, perform the translation, save the translated image, and clean up the
allocated memory. You can now apply this knowledge to perform image
translation in your own C projects, opening up possibilities for various
image processing tasks.

OceanofPDF.com
3.13.8 IMAGE
SHARPENING WITH
LEPTONICA LIBRARY
Image sharpening is a technique used to enhance the edges and fine details
in an image. It helps to make the image appear more crisp and clear. In this
chapter, we will explore how to perform image sharpening using the C
language and the Leptonica library.
Let's examine the complete code that demonstrates image sharpening using
Leptonica:

C:

#include <stdio.h>
#include <stdlib.h>
#include "allheaders.h"

int main()
{
PIX *image, *sharpenedImage;

// Load the image


image = pixRead("input_image.jpg");
if (image == NULL) {
fprintf(stderr, "Failed to load input image.\n");
return 1;
}

// Perform image sharpening


sharpenedImage = pixUnsharpMasking(image, 3, 0.5);
if (sharpenedImage == NULL) {
fprintf(stderr, "Failed to sharpen image.\n");
pixDestroy(&image);
return 1;
}

// Save the sharpened image


if (pixWrite("sharpened_image.jpg", sharpenedImage, IFF_JFIF_JPEG) != 0) {
fprintf(stderr, "Failed to save sharpened image.\n");
}

// Clean up
pixDestroy(&image);
pixDestroy(&sharpenedImage);

return 0;
}

1. Including the necessary header files: We begin by including the required


header files: `stdio.h`, `stdlib.h`, and `"allheaders.h"`. The `"allheaders.h"`
header provides access to the Leptonica library functions and data
structures.
2. Loading the input image: We use the `pixRead` function from Leptonica
to load the input image into a `PIX` structure named `image`. If the image
fails to load, an error message is displayed, and the program exits with a
non-zero return code.
3. Performing image sharpening: To perform image sharpening, we use the
`pixUnsharpMasking` function, and here are the parameters explained of
that function:
image: This parameter represents the original input image that you want to
sharpen. It is of type PIX*, which is a Leptonica structure used to store
image data. In the provided code, image is the input image loaded from the
file "input_image.jpg" using the pixRead function.
halfwidth: The halfwidth parameter specifies the half-width of the
smoothing filter that is used to compute the blurred version of the image.
This blurred version is subtracted from the original image to enhance the
edges and fine details. A larger halfwidth value results in a stronger
smoothing effect. In the provided code, a value of 3 is used as the halfwidth
parameter.
fract: The fract parameter determines the strength of the sharpening effect.
It controls the fraction of the blurred image that is subtracted from the
original image. A higher fract value increases the strength of sharpening,
while a lower value produces a more subtle effect. In the provided code, a
value of 0.5 is used as the fract parameter.
By adjusting the values of halfwidth and fract, you can control the level of
sharpening applied to the image. Experimenting with different parameter
values can help achieve the desired sharpening effect based on the
characteristics of your input image.
4. Saving the sharpened image: We use the `pixWrite` function to save the
sharpened image to a file. In this example, we save the image as a JPEG file
named "sharpened_image.jpg". If the save operation fails, an error message
is displayed.
5. Cleaning up: To prevent memory leaks, we destroy the `image` and
`sharpenedImage` structures using `pixDestroy`.
We explored how to perform image sharpening in C language using the
Leptonica library. We learned how to load an image, perform the sharpening
operation, save the sharpened image, and clean up the allocated memory.

OceanofPDF.com
3.13.9 IMAGE BLURRING
WITH LEPTONICA
LIBRARY
Image blurring, also known as image smoothing or image convolution, is a
common technique in image processing to reduce noise and create a softer
appearance in an image. In this chapter, we will explore how to perform
image blurring using the C language and the Leptonica library.
Let's examine the complete code that demonstrates image blurring using
Leptonica:

C:

#include <stdio.h>
#include <stdlib.h>
#include "allheaders.h"

int main()
{
PIX *image, *blurredImage;

// Load the image


image = pixRead("input_image.jpg");
if (image == NULL) {
fprintf(stderr, "Failed to load input image.\n");
return 1;
}

// Perform image blurring


blurredImage = pixBlockconv(image, 3, 3);
if (blurredImage == NULL) {
fprintf(stderr, "Failed to blur image.\n");
pixDestroy(&image);
return 1;
}

// Save the blurred image


if (pixWrite("blurred_image.jpg", blurredImage, IFF_JFIF_JPEG) != 0) {
fprintf(stderr, "Failed to save blurred image.\n");
}

// Clean up
pixDestroy(&image);
pixDestroy(&blurredImage);

return 0;
}

1. Including the necessary header files: We begin by including the required


header files: `stdio.h`, `stdlib.h`, and `"allheaders.h"`. The `"allheaders.h"`
header provides access to the Leptonica library functions and data
structures.
2. Loading the input image: We use the `pixRead` function from Leptonica
to load the input image into a `PIX` structure named `image`. If the image
fails to load, an error message is displayed, and the program exits with a
non-zero return code.
3. Performing image blurring: To perform image blurring, we use the
`pixBlockconv` function. It takes the original image, the width and height
of the blurring kernel, and returns the blurred image as a `PIX` structure. In
the provided code, a kernel size of 3x3 is used.
4. Saving the blurred image: We use the `pixWrite` function to save the
blurred image to a file. In this example, we save the image as a JPEG file
named "blurred_image.jpg". If the save operation fails, an error message is
displayed.
5. Cleaning up: To prevent memory leaks, we destroy the `image` and
`blurredImage` structures using `pixDestroy`.
In this chapter, we explored how to perform image blurring in C language
using the Leptonica library. We learned how to load an image, perform the
blurring operation, save the blurred image, and clean up the allocated
memory.

OceanofPDF.com
3.13.10 BRIGHTNESS
ADJUSTMENT WITH
LEPTONICA LIBRARY
Brightness adjustment can be achieved with Leptonica library using the
`pixGammaTRC` function, which can adjust the gamma value of an image.
The gamma value is related to the brightness of an image.
Here is a simple C code snippet demonstrating this:

C:

#include "allheaders.h"

int main() {
PIX *pixs, *pixd;

/* Load the source image. */


pixs = pixRead("input.png");
if (pixs == NULL) {
fprintf(stderr, "failed to load image\n");
return 1;
}

/* Adjust the brightness (gamma). */


pixd = pixGammaTRC(NULL, pixs, 1.50, 0, 255);
if (pixd == NULL) {
fprintf(stderr, "failed to adjust brightness\n");
pixDestroy(&pixs);
return 1;
}
/* Write the result to a file. */
if (pixWrite("destination.png", pixd, IFF_PNG)) {
fprintf(stderr, "failed to write image\n");
}

/* Cleanup. */
pixDestroy(&pixs);
pixDestroy(&pixd);

return 0;
}

This program reads an image from "source.png", adjusts the brightness of


the image using the `pixGammaTRC` function, and then writes the resulting
image to "destination.png".
In this code, the `pixGammaTRC` function is used to adjust the brightness
of the image. The second argument is the source image, the third argument
is the gamma factor, and the fourth and fifth arguments are the black and
white points respectively. A gamma factor greater than 1.0 will darken the
image, while a gamma factor less than 1.0 will brighten the image.
As always, remember that error checking and handling is simplified in this
example for the sake of clarity. In a real-world application, you would want
to add more extensive error handling.

OceanofPDF.com
3.13.11 CONVERTING TO
GRAYSCALE WITH
LEPTONICA LIBRARY
Leptonica provides functions for converting images to grayscale. The main
function to use is `pixConvertRGBToLuminance()`, which converts an
RGB image to grayscale using a specific weighted average of the RGB
values. Here's a simple example of how you can use it:

C:

#include "allheaders.h"

int main() {
PIX *pixs, *pixd;

/* Load the source image. */


pixs = pixRead("source.png");
if (pixs == NULL) {
fprintf(stderr, "failed to load image\n");
return 1;
}

/* Convert to grayscale. */
pixd = pixConvertRGBToLuminance(pixs);
if (pixd == NULL) {
fprintf(stderr, "failed to convert image\n");
pixDestroy(&pixs);
return 1;
}
/* Write the result to a file. */
if (pixWrite("destination.png", pixd, IFF_PNG)) {
fprintf(stderr, "failed to write image\n");
}

/* Cleanup. */
pixDestroy(&pixs);
pixDestroy(&pixd);

return 0;
}

This program reads an image from "source.png", converts it to grayscale,


and then writes the resulting image to "destination.png".
Please note that this example lacks advanced error handling, and it's
simplified for the sake of clarity. You should add more extensive error
handling for a production-grade application.

OceanofPDF.com
3.13.12 IMAGE
THRESHOLDING WITH
LEPTONICA LIBRARY
Thresholding is a popular technique in image processing that converts a
grayscale image to a binary image, where the pixels are either 0 or 1. The
`pixThresholdToBinary()` function in the Leptonica library can be used to
perform thresholding on an image.
Here is a simple C code snippet demonstrating how to use this function:

C:

#include "allheaders.h"

int main() {
PIX *pixs, *pixd;

/* Load the source image. */


pixs = pixRead("source.png");
if (pixs == NULL) {
fprintf(stderr, "failed to load image\n");
return 1;
}

/* Convert to grayscale if the image is color. */


if (pixGetDepth(pixs) != 8) {
PIX *pixt = pixConvertTo8(pixs, 0);
pixDestroy(&pixs);
pixs = pixt;
}
/* Perform thresholding. */
pixd = pixThresholdToBinary(pixs, 128);
if (pixd == NULL) {
fprintf(stderr, "failed to threshold image\n");
pixDestroy(&pixs);
return 1;
}

/* Write the result to a file. */


if (pixWrite("destination.png", pixd, IFF_PNG)) {
fprintf(stderr, "failed to write image\n");
}

/* Cleanup. */
pixDestroy(&pixs);
pixDestroy(&pixd);

return 0;
}

This program reads an image from "source.png", converts it to grayscale if


necessary, thresholds it to a binary image, and then writes the resulting
image to "destination.png".
In this code, the `pixThresholdToBinary()` function is used to perform the
thresholding. The second argument is the source image, and the third
argument is the threshold value. All pixels with a value less than the
threshold are set to 0 (black), and all pixels with a value greater than or
equal to the threshold are set to 1 (white).
Please note that error checking and handling is simplified in this example
for the sake of clarity. In a real-world application, you would want to add
more extensive error handling.

OceanofPDF.com
3.13.13 IMAGE BLENDING
WITH LEPTONICA
LIBRARY
Blending images is a common task in many image processing applications.
The Leptonica library provides a convenient way to blend two images
together using a gray mask. In this chapter, we will explore how to use the
`pixBlendWithGrayMask()` function to blend two images together.
Let's first consider the example code:

C:

#include "allheaders.h"

int main() {
PIX *pixs1, *pixs2, *pixd, *pixg;

/* Load the two images. */


pixs1 = pixRead("source1.png");
pixs2 = pixRead("source2.png");

/* Check if images are successfully loaded. */


if (pixs1 == NULL || pixs2 == NULL) {
fprintf(stderr, "failed to load images\n");
return 1;
}

/* Create a gray mask for blending. */


pixg = pixCreate(pixGetWidth(pixs1), pixGetHeight(pixs1), 8);
pixSetAll(pixg);
/* Blend the images. */
pixd = pixBlendWithGrayMask(pixs1, pixs2, pixg, 0, 0);
if (pixd == NULL) {
fprintf(stderr, "failed to blend images\n");
pixDestroy(&pixs1);
pixDestroy(&pixs2);
pixDestroy(&pixg);
return 1;
}

/* Write the result to a file. */


if (pixWrite("blended.png", pixd, IFF_PNG)) {
fprintf(stderr, "failed to write image\n");
}

/* Cleanup. */
pixDestroy(&pixs1);
pixDestroy(&pixs2);
pixDestroy(&pixg);
pixDestroy(&pixd);

return 0;
}

This C program demonstrates how to blend two images using a gray mask.
The key steps are as follows:
1. Loading Images: Two images, named "source1.png" and "source2.png",
are loaded into `PIX` structures using the `pixRead()` function.
2. Creating a Gray Mask: A new image, `pixg`, is created with the same
dimensions as the source images. This image will be used as a gray mask
for blending. The `pixCreate()` function creates an image of a specified
width, height, and depth. The `pixSetAll()` function is then used to set all
pixels in the mask to white (maximum gray value).
3. Blending the Images: The `pixBlendWithGrayMask()` function is used
to blend the two images together. This function takes five arguments: the
two images to blend, the gray mask, and the x and y offsets of the second
image relative to the first. The function returns a new image that is a blend
of the two source images based on the gray mask. The more white a pixel in
the mask, the more of the second image is used in the blending at that pixel.
4. Writing the Result to a File: The `pixWrite()` function is used to write
the resulting image to a file. In this case, the resulting image is written to
"blended.png".
5. Cleanup: The `pixDestroy()` function is used to free the memory
allocated for the images.
Remember, the effectiveness of blending depends heavily on the gray mask
used. While this example uses a simple white mask to blend the images
equally, more sophisticated effects can be achieved by using a mask with
varying gray levels. For instance, you can create a mask that has a gradient
from black to white to smoothly

OceanofPDF.com
3.13.14 EXTRACTING
TEXT FROM IMAGE WITH
LEPTONICA AND
TESSERACT
Extracting text from an image, a process known as Optical Character
Recognition (OCR), can be a complex procedure that isn't directly
supported by the standard library of the C programming language.
However, you can use an OCR library, such as Tesseract, which offers a C
API, to carry out this task.
Tesseract, an open-source library for OCR developed by Google, is capable
of recognizing more than 100 languages out-of-the-box.

Steps to Implement OCR using Tesseract


1. Installing Tesseract
Install Tesseract on your system using package managers. For example, on
Ubuntu, you can use the command `sudo apt install libtesseract-dev`.

2. Linking the Tesseract Library to Your C Program


In your C compiler, you need to specify that you're using the Tesseract
library. For example, if you're using gcc, you might include `-ltesseract` in
your command line call to gcc.

3. Including the Tesseract Header in Your C Code


At the top of your C code, you should include `#include <tesseract/capi.h>`.
This allows you to use the Tesseract library's functions in your code.

4. Using Tesseract to Extract Text from an Image


You can use the Tesseract API to read an image file and extract the text
from it. Below is a basic example of what that code might look like:

C:

#include <tesseract/capi.h>
#include <leptonica/allheaders.h>

int main() {
TessBaseAPI *api = TessBaseAPICreate();
TessBaseAPIInit3(api, NULL, "eng");

PIX *image = pixRead("image.png");


TessBaseAPISetImage2(api, image);

if (TessBaseAPIRecognize(api, NULL) != 0) {
fprintf(stderr, "Could not recognize image\n");
return 1;
}

char *outText = TessBaseAPIGetUTF8Text(api);


printf("OCR output: %s", outText);

// Clean up
TessDeleteText(outText);
pixDestroy(&image);
TessBaseAPIEnd(api);
TessBaseAPIDelete(api);

return 0;
}

This code represents a basic implementation. In a real-world program, you


will need to handle errors and edge cases. It is also advisable to create a
separate function for OCR to keep your `main` function clean.
Tesseract performs best on clean, high-resolution images. If your images are
less than ideal, you might need to preprocess them by binarizing, reducing
noise, correcting skew, and so on to get good OCR results. Libraries such as
Leptonica, as shown in the example, can assist with these image processing
tasks.

Solving CMake's Tesseract Package Finding Issue


In some instances, CMake is unable to locate the Tesseract package. This
usually happens because Tesseract might not provide a CMake
configuration file, or if it does, it's not located in a directory that CMake is
aware of.
To address this, you will have to manually specify the include directories
and libraries. Here's an example of how you can accomplish this:

cmake:

cmake_minimum_required(VERSION 3.22)
project(untitled34 C)

set(CMAKE_C_STANDARD 17)

# Path to Tesseract and Leptonica


# Be sure to change these paths to match where your libraries are installed.
set(Tesseract_INCLUDE_DIRS "/usr/local/include")
set(Tesseract_LIBRARIES "/usr/local/lib/libtesseract.so")

set(Leptonica_INCLUDE_DIRS "/usr/local/include/leptonica")
set(Leptonica_LIBRARIES "/usr/local/lib/liblept.so")

add_executable(untitled34 main.c)

target_link_libraries(untitled34 PRIVATE ${Tesseract_LIBRARIES} ${Leptonica_LIBRARIES})

target_include_directories(untitled34 PRIVATE
​ {Tesseract_INCLUDE_DIRS}
$
​ {Leptonica_INCLUDE_DIRS})
$
Please note that you should replace "/usr/local/include",
"/usr/local/lib/libtesseract.so", "/usr/local/include/leptonica" and
"/usr/local/lib/liblept.so" with the actual paths where Tesseract and
Leptonica libraries and headers are installed on your system.
You can use the find or locate command in a Linux terminal to find where
these files are located.
For example:
To find the Tesseract library: find / -name libtesseract.so 2>/dev/null
To find the Tesseract include directory: find / -name tesseract 2>/dev/null
To find the Leptonica library: find / -name liblept.so 2>/dev/null
To find the Leptonica include directory: find / -name leptonica 2>/dev/null
This will search your entire file system for the specified files and output
their paths. Then replace the paths in the CMakeLists.txt with the correct
paths.

OceanofPDF.com
3.13.15 THE SVG
(SCALABLE VECTOR
GRAPHICS) FORMAT
What is SVG?
SVG stands for Scalable Vector Graphics, an XML-based vector image
format for two-dimensional graphics with support for interactivity and
animation. Developed by the World Wide Web Consortium (W3C) in 2001,
SVG has become a popular choice for graphics on the web due to its
scalability and flexibility.
Unlike raster graphics (e.g., JPEG, PNG), which are made up of a fixed set
of pixels, SVGs are composed of scalable vectors. These vectors consist of
paths, shapes, and fills defined by mathematical equations. This means
SVGs maintain their high quality at any scale or resolution, making them
ideal for responsive web design.

Structure of SVG Files


SVG files are text-based and can be edited directly in a text editor. The
syntax is XML, so it's both human-readable and machine-readable.
An SVG file begins with an `<svg>` tag, and all shapes, lines, and other
elements are contained within this tag. SVG elements include `<rect>` for
rectangles, `<circle>` for circles, `<line>` for straight lines, `<polyline>` for
non-closed shapes, `<polygon>` for closed shapes, and `<path>` for
complex shapes.
Each of these elements can have attributes for positioning (like `x`, `y`,
`cx`, `cy`, etc.), dimensions (`width`, `height`, `r` for radius), and styling
(like `fill`, `stroke`, `stroke-width`).

Here's an example of an SVG image:


xml:

<svg width="100" height="100">


<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>

This will produce a red circle with a black border.

SVG and HTML


SVG images can be included directly in HTML documents, giving
developers a powerful tool for integrating graphics into web pages.
Once an SVG image is embedded in an HTML document, individual SVG
elements can be manipulated using JavaScript or styled with CSS, adding
interactivity and dynamic visual effects.

html:

<!DOCTYPE html>
<html>
<body>
<svg width="100" height="100">
<circle id="myCircle" cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
<script>
document.getElementById("myCircle").addEventListener("click", function(){
this.setAttribute("fill", "blue");
});
</script>
</body>
</html>

In this HTML document, the SVG circle changes color when clicked.

Using SVGs in C with Cairo and librsvg


SVGs can also be used in C programs through libraries like Cairo and
librsvg. Cairo is a 2D graphics library that supports SVG output, among
other formats, and librsvg is a library to render SVG documents.
Creating SVG files directly in C involves creating shapes, lines, and paths
using Cairo's functions, then saving the output as an SVG. Reading SVG
files in C can be done using librsvg, which turns SVG documents into
renderable objects.
These libraries offer powerful tools for generating, manipulating, and
rendering SVG graphics, but they require a good understanding of both the
SVG format and the C language.

Conclusion
SVG is a versatile and powerful format for 2D graphics that has widespread
use on the web and in many other contexts. Its scalability, flexibility, and
ease of use make it an invaluable tool for developers and designers alike.
Whether you're creating complex web applications, simple icons, or
programmatically generating graphics in C, understanding SVG can be a
huge benefit.

OceanofPDF.com
3.13.15.1 CREATING SVG
WITH CAIRO AND
LIBRSVG LIBRARY
Introduction
Cairo is a powerful 2D graphics library that supports multiple output
formats, including SVG. librsvg is a library that provides the ability to
render SVG documents, offering a pathway to interface with Cairo.
Let's look at several examples that showcase the capabilities of both
libraries.

Installing Cairo and librsvg


Before we start, ensure that both Cairo and librsvg are installed on your
system. For Debian-based systems, use the following commands:

bash:

sudo apt-get install libcairo2-dev


sudo apt-get install librsvg2-dev

Drawing with Cairo


First, let's create a simple SVG with Cairo. This code will generate a red
circle with a black border:

C:
#include <cairo.h>
#include <cairo-svg.h>

int main() {
cairo_surface_t *surface;
cairo_t *cr;

surface = cairo_svg_surface_create("circle.svg", 200, 200);


cr = cairo_create(surface);

cairo_set_source_rgb(cr, 1, 0, 0); // Red


cairo_arc(cr, 100, 100, 50, 0, 2 * 3.14159); // Full circle
cairo_fill_preserve(cr); // Fill the circle, preserving the path

cairo_set_source_rgb(cr, 0, 0, 0); // Black


cairo_set_line_width(cr, 10);
cairo_stroke(cr); // Stroke the path

cairo_destroy(cr);
cairo_surface_destroy(surface);

return 0;
}

Compile it using:

bash:

gcc -o circle circle.c `pkg-config --cflags --libs cairo cairo-svg`

Run the program and it will generate an SVG file named `circle.svg` with a
red circle having a black border.

Reading SVG with librsvg and Rendering with Cairo


Let's use librsvg to read an SVG file and render it with Cairo. This program
reads an SVG file, `input.svg`, and creates a new PNG image:

C:

#include <stdio.h>
#include <cairo.h>
#include <librsvg/rsvg.h>

int main() {
RsvgHandle *handle;
RsvgDimensionData dim;
cairo_surface_t *surface;
cairo_t *cr;

rsvg_init();

handle = rsvg_handle_new_from_file("circle.svg", NULL);


rsvg_handle_get_dimensions(handle, &dim);

surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dim.width, dim.height);


cr = cairo_create(surface);

rsvg_handle_render_cairo(handle, cr);

cairo_surface_write_to_png(surface, "output.png");

cairo_destroy(cr);
cairo_surface_destroy(surface);
g_object_unref(handle);
rsvg_term();

return 0;
}
Compile and run it with:

bash:

gcc -o svg2png svg2png.c `pkg-config --cflags --libs librsvg-2.0 cairo`


./svg2png

After running this, you'll have a PNG file, `output.png`, rendered from your
input SVG file.

Drawing Complex Shapes with Cairo


Cairo supports complex shapes through the `<path>` function. Below is a
program that creates an SVG of a five-pointed star:

C:

#include <cairo.h>
#include <cairo-svg.h>
#include <math.h>

#define WIDTH 200


#define HEIGHT 200

int main() {
cairo_surface_t *surface;
cairo_t *cr;
int i;

surface = cairo_svg_surface_create("star.svg", WIDTH, HEIGHT);


cr = cairo_create(surface

);

cairo_translate(cr, WIDTH / 2, HEIGHT / 2);


cairo_move_to(cr, 0, -50);
for (i = 1; i < 5; i++) {
double angle = 2 * M_PI * i / 5 - M_PI / 2;
cairo_line_to(cr, 50 * cos(angle), 50 * sin(angle));
}
cairo_close_path(cr); // Close the star shape

cairo_set_source_rgb(cr, 1, 0, 0); // Red


cairo_fill(cr);

cairo_destroy(cr);
cairo_surface_destroy(surface);

return 0;
}

Compile it with:

bash:

gcc -o star star.c `pkg-config --cflags --libs cairo cairo-svg`

This will create an SVG of a red, five-pointed star.


These examples demonstrate some basic capabilities of Cairo and librsvg.
Complex projects may involve transforming SVGs, applying gradients,
creating animations, handling mouse events, and more. Be sure to consult
the Cairo and librsvg documentation to explore the full breadth of
possibilities these libraries offer.

OceanofPDF.com
3.13.16 THE DRAWING
EXCHANGE FORMAT
(DXF)
Drawing Exchange Format (DXF) is a CAD data file format developed by
Autodesk for enabling data interoperability between AutoCAD and other
programs. DXF was originally introduced in December 1982 as part of
AutoCAD 1.0. It is a text-based format, where data is represented as a
series of sections, each containing a variable number of parameters.

DXF Structure
A DXF file is built up of "sections", each section is used to represent a
different type of the information contained within the file. Here are the
main sections you may find in a typical DXF file:

1. HEADER: Contains information such as version number, default paths,


system variables, etc.
2. CLASSES: Holds information for application-defined classes whose
instances appear in the BLOCKS, ENTITIES, and OBJECTS sections of
the database.
3. TABLES: This section contains definitions of named items.
4. BLOCKS: This section contains Block Definition entities describing the
entities comprising each Block in the drawing.
5. ENTITIES: Contains the drawing entities, including any Block
References.
6. OBJECTS: Contains the data that apply to nongraphical objects, used by
AutoCAD.
7. THUMBNAILIMAGE: Contains the preview image for the DXF file.
Each section is made up of a series of "group codes" and "values". A group
code is an integer that indicates the meaning of the value that follows. The
value can be of various types, such as string, integer, hex, etc.
Let's take a look at a simple example to illustrate the structure of a DXF
file.

dxf:

0
SECTION
2
HEADER
9
$ACADVER
1
AC1009
0
ENDSEC
0
SECTION
2
TABLES
0
ENDSEC
0
SECTION
2
BLOCKS
0
ENDSEC
0
SECTION
2
ENTITIES
0
LINE
8
0
10
0.0
20
0.0
11
100.0
21
100.0
0
ENDSEC
0
EOF

This is an extremely simplified DXF file that represents a line from `(0, 0)`
to `(100, 100)`.

Building a DXF File


Building a DXF file manually is a meticulous process that involves
understanding how each entity in DXF works. For instance, to create a
LINE entity, you need to specify the layer, line type, color, and the start and
end points.
For modern applications, it's common to use a library to handle the DXF
format, such as libdxfrw for C++ or ezdxf for Python. These libraries take
care of the low-level details, allowing you to focus on the higher-level
operations like creating and positioning entities.

Conclusion
The DXF format is a powerful tool for interoperability between different
CAD programs. Despite its complexity, it's quite flexible and allows for a
wide range of applications. Whether you're manually building a DXF file or
using a library, a deep understanding of the DXF structure can help you
diagnose problems and make the most of its features.
OceanofPDF.com
3.13.16.1 SIMPLIFIED
EXAMPLE OF DXF FILE IN
PLAIN C
Drawing DXF files in C can be done using libraries such as LibreDWG or
dxflib. However, these libraries may not be as straightforward to use as
Cairo for SVGs, mainly because DXF is a binary format and much more
complex. Unfortunately, as of my knowledge cutoff in September 2021,
there isn't an easy-to-use, widely accepted C library for creating DXF files.
That said, here's a rudimentary example of how to write a DXF file
containing a single line, without using any external libraries. Note that this
example is very simplified and might not work with all DXF viewers or
CAD software:

C:

#include <stdio.h>

int main() {
FILE *file = fopen("line.dxf", "w");
if (file == NULL) {
printf("Error opening file!\n");
return 1;
}

fprintf(file,
"0\n"
"SECTION\n"
"2\n"
"HEADER\n"
"0\n"
"ENDSEC\n"
"0\n"
"SECTION\n"
"2\n"
"ENTITIES\n"
"0\n"
"LINE\n"
"10\n"
"0.0\n" // Start X
"20\n"
"0.0\n" // Start Y
"11\n"
"100.0\n" // End X
"21\n"
"100.0\n" // End Y
"0\n"
"ENDSEC\n"
"0\n"
"EOF\n");

fclose(file);
return 0;
}

When you compile and run this program, it will create a DXF file named
"line.dxf" containing a single line from (0,0) to (100,100).
Again, please note that this is a very simplified example, and DXF files for
complex drawings or for use with professional CAD software will require a
much more detailed and correctly formatted DXF file, likely best created
with a specialized library. Always consult the documentation for the
software you're interfacing with to understand its specific requirements.

OceanofPDF.com
3.13.17 THE WAVEFRONT
OBJ FORMAT
The Wavefront OBJ file format is a simple data format that represents 3D
geometry. It was developed by Wavefront Technologies for its Advanced
Visualizer animation package. The format is widely used in 3D graphics for
data interchange between 3D applications because of its simplicity and ease
of implementation.
The file format is ASCII, which means it can be opened and modified with
any text editor. An OBJ file can describe geometry of a 3D object, including
vertices, normals, texture coordinates and polygonal faces. It can also
reference material definitions in another file, using the Material Template
Library (MTL) format.

Structure of an OBJ File


The basic structure of an OBJ file is quite straightforward. It contains a list
of vertices, a list of vertex normals, a list of texture coordinates, and a list of
faces that make up the surface of the 3D object.
- Vertices: Vertices are defined using the `v` keyword followed by their x, y,
and z coordinates. For example:

obj:

v 1.000000 -1.000000 -1.000000


v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000

- Vertex Normals: Vertex normals are used for shading calculations and are
defined with the `vn` keyword, followed by the x, y, and z components of
the normal's vector.

obj:

vn 0.0000 1.0000 0.0000


vn -1.0000 -0.0000 -0.0000

- Texture Coordinates: Texture coordinates are defined using the `vt`


keyword, followed by the u and v coordinates (and optionally a w
coordinate, for 3D textures).

obj:

vt 0.500 1
vt 1 0

- Faces: Faces are defined using the `f` keyword followed by a series of
vertex, texture and normal indices that define the polygon. For example:

obj:

f 1/1/1 2/2/1 3/3/1


f 3/1/2 4/2/2 5/3/2

Each number in the series corresponds to an index in the previously defined


lists of vertices, textures and normals. The indices are 1-based, not 0-based
(so the first vertex is 1, not 0).

Considerations When Using OBJ Files


While the OBJ file format is straightforward and easy to use, it does have
some limitations. It's a geometry-definition format, so it doesn't include
scene information (like cameras or lights), animation data, or advanced
shading or rendering attributes. For complex scenes and animations, a more
feature-rich format like FBX or COLLADA may be more appropriate.
Also, because OBJ files are text-based, they can get quite large for complex
models. Some programs use binary formats for efficiency, but these are not
standard and not as widely supported.
In conclusion, the OBJ file format is a useful and simple format for 3D
geometry. It's widely supported, easy to understand and use, and is a good
choice for basic 3D modeling tasks.

OceanofPDF.com
3.13.17.1 SIMPLIFIED
EXAMPLES OF OBJ IN
PLAIN C
Creating a 3D cube
In this chapter, we will explore the process of creating a simple 3D box (or
cube) and saving it as an OBJ file, using the C programming language. An
OBJ file is a standard 3D image format that can be exported and opened by
various 3D image editing software.
OBJ files store information about 3D models. The information includes the
positions of the vertices of the object, the texture coordinates, and the
normals. In the case of our cube, the vertex information will be most
relevant.
To create an OBJ file, we need to understand its structure. An OBJ file is a
plain text file containing:
1. Vertex positions, denoted by `v x y z`, where x, y, and z are the 3D
coordinates of a vertex.
2. Vertex normals, denoted by `vn x y z`, where x, y, and z are the 3D
coordinates of a normal.
3. Texture coordinates, denoted by `vt u v w`, where u, v, and w are the 2D
texture coordinates.
4. Face elements, denoted by `f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...`,
where `v`, `vt`, and `vn` are the indices of the vertex, texture, and normal
respectively for each corner of a polygon face.
Here's a code example that will generate a 3D box and save it to an OBJ
file:
C:

#include <stdio.h>

void write_obj_file(const char *filename) {


FILE *file = fopen(filename, "w");

if (file == NULL) {
printf("Error: unable to open file for writing\n");
return;
}

// Vertex positions
const float cube_vertices[8][3] = {
{-0.5f, -0.5f, 0.5f},
{0.5f, -0.5f, 0.5f},
{0.5f, 0.5f, 0.5f},
{-0.5f, 0.5f, 0.5f},
{-0.5f, -0.5f, -0.5f},
{0.5f, -0.5f, -0.5f},
{0.5f, 0.5f, -0.5f},
{-0.5f, 0.5f, -0.5f}
};

for (int i = 0; i < 8; i++) {


fprintf(file, "v %f %f %f\n", cube_vertices[i][0], cube_vertices[i][1], cube_vertices[i][2]);
}

// Faces
const int cube_faces[12][3] = {
{1, 2, 3},
{1, 3, 4},
{5, 8, 7},
{5, 7, 6},
{1, 5, 6},
{1, 6, 2},
{2, 6, 7},
{2, 7, 3},
{3,

7, 8},
{3, 8, 4},
{4, 8, 5},
{4, 5, 1}
};

for (int i = 0; i < 12; i++) {


fprintf(file, "f %d %d %d\n", cube_faces[i][0], cube_faces[i][1], cube_faces[i][2]);
}

fclose(file);
}

int main() {
write_obj_file("cube.obj");
return 0;
}

This code defines the vertex positions and faces of a 3D box and writes
them to an OBJ file named "cube.obj". Note that the indices in the
`cube_faces` array are 1-based as per the OBJ file format convention.
By running this program, you'll create an OBJ file representing a 3D box.
You can view this file with any 3D modeling software that supports the OBJ
format.

Creating a 3D Sphere
In this chapter, we will delve into how to generate a 3D sphere in C and
then write the data to an OBJ file.
Firstly, we need to generate points on the sphere. We can do this by varying
the azimuthal (θ) and polar (φ) angles, generating Cartesian coordinates (x,
y, z) from these angles. We'll generate these points in a grid, with varying θ
and φ, and then construct triangles from these points to form the mesh.
Let's get started with the code:

C:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define PI 3.14159265358979323846

// Function to write the vertices of the sphere to the OBJ file


void write_vertices(FILE *file, double radius, int divisions) {
double theta, phi; // Azimuthal and Polar angles
double x, y, z; // Cartesian coordinates

for (int i = 0; i <= divisions; i++) {


theta = i * 1.0 / divisions * PI; // Range of theta: [0, PI]

for (int j = 0; j <= 2 * divisions; j++) {


phi = j * 1.0 / divisions * PI; // Range of phi: [0, 2PI]

x = radius * sin(theta) * cos(phi);


y = radius * sin(theta) * sin(phi);
z = radius * cos(theta);

fprintf(file, "v %f %f %f\n", x, y, z);


}
}
}

// Function to write the faces of the sphere to the OBJ file


void write_faces(FILE *file, int divisions) {
int k1, k2;
for (int i = 0; i < divisions; i++) {
for (int j = 0; j < 2 * divisions; j++) {
k1 = i * (2 * divisions + 1) + j + 1;
k2 = k1 + 2 * divisions + 1;

fprintf(file, "f %d %d %d\n", k1, k2, k1 + 1);


fprintf(file, "f %d %d %d\n", k1 + 1, k2, k2 + 1);
}
}
}

// Function to generate the OBJ file for the sphere


void generate_sphere_obj(double radius, int divisions, const char *filename) {
FILE *file = fopen(filename, "w");

if (!file) {
fprintf(stderr, "Unable to open file %s\n", filename);
exit(1);
}

write_vertices(file, radius, divisions);


// write_faces(file, divisions);

fclose(file);
}

int main() {
generate_sphere_obj(1.0, 40, "sphere.obj");
return 0;
}

This code creates an OBJ file that describes a 3D sphere. The


`write_vertices` function writes the vertices of the sphere to the OBJ file,
while the `write_faces` function writes the faces of the sphere to the OBJ
file.
Please note that this is a simple example and doesn't include writing
normals or texture coordinates, which you might need depending on your
use case. Also, this code doesn't generate a perfect sphere; the quality of the
sphere depends on the divisions parameter. The higher the divisions value,
the better the sphere will look, but it will also increase the computational
cost and the size of the resulting OBJ file.
This code will work for simple visualizations or for getting started with 3D
programming, but for production-quality 3D graphics, you might need to
use a full-featured 3D library or engine.

OceanofPDF.com
3.13.17.2 CREATING OBJ
WITH CGLM LIBRARY
The CGLM library (short for C OpenGL Mathematics) is a high-
performance library created specifically for computer graphics applications.
As the name suggests, CGLM is written in C and aims to provide a C-
friendly interface for GLM, a popular graphics math library for C++.
CGLM offers an efficient and intuitive interface for performing operations
that are crucial in computer graphics. This includes operations on vectors
and matrices, as well as transformations like scaling, rotating, and
translating objects in 3D space.

Understanding the CGLM Library


Let's start by understanding what the CGLM library provides to its users.
The most fundamental entities in computer graphics are vectors and
matrices, and CGLM provides comprehensive support for these.
1. Vectors: CGLM provides types and functions for 2D, 3D, and 4D
vectors. For instance, `vec3` is a type representing a 3D vector. You can
perform common operations like addition, subtraction, dot product, and
cross product between vectors.
2. Matrices: Similarly, CGLM supports 2x2, 3x3, and 4x4 matrices with
types like `mat3` and `mat4`. Apart from basic operations like addition,
subtraction, and multiplication, you can also calculate the determinant,
inverse, and transpose of a matrix.
3. Transformations: CGLM provides functions to generate transformation
matrices for common operations like scaling, rotation, and translation. You
can also create perspective and orthogonal projection matrices.
4. Quaternions: CGLM also supports quaternions, which are very useful
for representing rotations in 3D space.
Employing CGLM in Your C Code
Let's examine a comprehensive example which utilizes various aspects of
the CGLM library. This program writes a .obj file for a 3D cube, applying a
scaling and rotation transformation to the cube's vertices:

C:

#include <stdio.h>
#include <cglm/cglm.h>

void write_obj_file(const char *filename) {


FILE *file = fopen(filename, "w");

if (file == NULL) {
printf("Error: unable to open file for writing\n");
return;
}

// Vertex positions
vec3 cube_vertices[8] = {
{-0.5f, -0.5f, 0.5f},
{0.5f, -0.5f, 0.5f},
{0.5f, 0.5f, 0.5f},
{-0.5f, 0.5f, 0.5f},
{-0.5f, -0.5f, -0.5f},
{0.5f, -0.5f, -0.5f},
{0.5f, 0.5f, -0.5f},
{-0.5f, 0.5f, -0.5f}
};

vec3 scale = {2.0f, 2.0f, 2.0f}; // scale factor

// Define a rotation matrix


mat4 rotate;
glm_rotate_make(rotate, glm_rad(45.0f), (vec3){0.0f, 1.0f, 0.0f});
for (int i = 0; i < 8; i++) {
glm_vec3_mul(cube_vertices[i], scale, cube_vertices[i]);
glm_mat4_mulv3(rotate, cube_vertices[i], 1.0f, cube_vertices[i]); // apply rotation
fprintf(file, "v %f %f %f\n", cube_vertices[i][0], cube_vertices[i][1], cube_vertices[i][2]);
}

// Faces
const int cube_faces[12][3] = {
{1, 2, 3},
{1, 3, 4},
{5, 8, 7},
{5, 7, 6},
{1, 5, 6},
{1, 6, 2},
{2, 6, 7},
{2, 7, 3},
{3, 7, 8},
{3, 8, 4},
{4, 8, 5},
{4, 5, 1}
};

for (int i = 0; i < 12; i++) {


fprintf(file, "f %d %d %d\n", cube_faces[i][0], cube_faces[i][1], cube_faces[i][2]);
}

fclose(file);
}

int main() {
write_obj_file("cube.obj");
return 0;
}
This code provides an excellent demonstration of how one can utilize
CGLM for creating, transforming, and working with 3D objects. With its
rich set of features and user-friendly interface, CGLM makes it easier than
ever to delve into the world of computer graphics with C.

OceanofPDF.com
3.13.18 THE STL
(STEREOLITHOGRAPHY)
FORMAT
The STL, short for Stereolithography, file format is a cornerstone in the
world of 3D modeling, printing, and computer-aided design (CAD). In this
chapter, we're going to examine the composition of STL files, how they are
structured, and the process of creating and manipulating them.
1. Structure of STL Files:
The STL file format's main purpose is to represent 3D models as a series of
triangular facets. Each facet is defined by a normal vector (indicating the
outward-facing direction) and three vertices (the corners of the triangle).
This triangulated representation can model any 3D shape, albeit with
varying levels of efficiency depending on the object's complexity.
2. Types of STL Files:
STL files come in two flavors: ASCII and binary. Both forms carry the
same data but present it differently. ASCII STL files are human-readable
and relatively straightforward to work with but consume significantly more
space. In contrast, binary STL files are more space-efficient, though they
can present additional challenges due to their non-readable nature.
3. Anatomy of an STL File:
An STL file consists of several sections, each playing a unique role in
defining the 3D model. The structure is as follows:
a. Header: The header occupies 80 characters and generally contains
metadata about the file, such as the name or the creator. However, this data
isn't strictly mandated, and many applications disregard it.
b. Number of Facets: A 4-byte integer follows the header, denoting the
number of facets (triangles) contained in the file.
c. Facet Data: Each facet is represented by 12 floating-point numbers: 3
for the normal vector and 3 for each vertex of the triangle. Each floating-
point number is 4 bytes, adding up to 48 bytes per facet.
d. Attribute Byte Count: Each facet concludes with a 2-byte "attribute
byte count." In a conventional STL file, this attribute is usually zero
because STL doesn't inherently support facets attributes. However, some
software applications use this to store additional data.
4. Creating STL Files:
Creating an STL file involves writing the header, specifying the number of
facets, and then documenting the data for each facet. This is typically done
in binary to ensure space efficiency. When storing the floating-point
numbers, endianness should be handled correctly – the STL format adopts
little-endian numbers.
5. Conclusion:
The STL format is a powerful and uncomplicated way of storing 3D model
data. Even though it doesn't incorporate some of the features of modern
formats (like textures, colors, or material properties), its simplicity and
widespread acceptance make it a crucial format to understand.
In the next chapter, we'll be diving into the practical applications and
manipulation of STL files. From comprehending the nuances of binary file
I/O to developing an understanding of 3D data manipulation, we're in for an
informative ride. So, let's move forward!

OceanofPDF.com
3.13.18.1 CREATING A 3D
CUBE AS AN STL IN
PLAIN C
In this chapter, we will be generating a simple 3D cube and saving it as an
STL (Stereolithography) file using the C programming language. The STL
file format is commonly used for 3D printing and computer-aided
manufacturing (CAM).

Understanding the STL Format


STL files can have either a binary or an ASCII representation. In this
chapter, we'll use the ASCII representation for simplicity. The ASCII STL
file starts with the line `solid name` where `name` is the name of the object.
This is followed by any number of triangles that make up the surface of the
object. Each triangle is represented by:

stl:

facet normal ni nj nk
outer loop
vertex v1x v1y v1z
vertex v2x v2y v2z
vertex v3x v3y v3z
endloop
endfacet

where `(ni, nj, nk)` are the components of the normal to the triangle, and
`(v1x, v1y, v1z)`, `(v2x, v2y, v2z)`, `(v3x, v3y, v3z)` are the 3D coordinates
of the vertices of the triangle. The file ends with `endsolid name`.
Crafting a 3D Cube in STL Format
A cube is made up of 6 square faces, each of which can be divided into 2
triangles. Thus, a cube can be represented with 12 triangles. We can
calculate the vertices and normals of these triangles and write them to an
STL file.
Here's an example of how you can do it:

C:

#include <stdio.h>

void write_stl_file(const char *filename) {


FILE *file = fopen(filename, "w");

if (file == NULL) {
printf("Error: unable to open file for writing\n");
return;
}

// The 8 vertices of the cube


const float vertices[8][3] = {
{-0.5f, -0.5f, -0.5f},
{0.5f, -0.5f, -0.5f},
{0.5f, 0.5f, -0.5f},
{-0.5f, 0.5f, -0.5f},
{-0.5f, -0.5f, 0.5f},
{0.5f, -0.5f, 0.5f},
{0.5f, 0.5f, 0.5f},
{-0.5f, 0.5f, 0.5f}
};

// The 12 triangles that make up the cube, defined by indices into the vertices array
const int triangles[12][3] = {
{1, 3, 4},
{1, 2, 3},
{2, 6, 7},
{2, 5, 6},
{5, 1, 4},
{5, 4, 8},
{4, 3, 8},
{3, 7, 8},
{3, 2, 7},
{2, 1, 5},
{8, 7, 6},
{8, 6, 5}
};

// Normals for each face of the cube


const float normals[6][

3] = {
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f}
};

// Writing to STL file


fprintf(file, "solid cube\n");

for (int i = 0; i < 12; i++) {


// Write the normal for this triangle
fprintf(file, "facet normal %f %f %f\n", normals[i / 2][0], normals[i / 2][1], normals[i / 2][2]);

fprintf(file, "outer loop\n");

// Write the vertices for this triangle


for (int j = 0; j < 3; j++) {
int vertex_index = triangles[i][j] - 1;
fprintf(file, "vertex %f %f %f\n", vertices[vertex_index][0], vertices[vertex_index][1],
vertices[vertex_index][2]);
}

fprintf(file, "endloop\n");
fprintf(file, "endfacet\n");
}

fprintf(file, "endsolid cube\n");

fclose(file);
}

int main() {
write_stl_file("cube.stl");
return 0;
}

This example demonstrates how to create a cube with a side length of 1


unit, centered at the origin, and how to write it to an STL file. The normals
are hard-coded in this example, but for more complex shapes, you would
typically calculate the normals based on the vertices of each triangle.
You can view the generated STL file using any 3D viewer software that
supports the STL format, such as Blender or MeshLab.

OceanofPDF.com
3.13.18.2 CREATING A 3D
SPHERE AS STL IN PLAIN
C
Before diving into the code, it's essential to understand the basics of the
STL (Stereolithography) file format and how a 3D sphere is represented in
this format. An STL file contains the details of a 3D model in the form of
multiple triangles, or facets, each represented by three vertices and a normal
vector. A 3D sphere can be represented as a collection of these triangular
facets.
Now, let's look at how to create a 3D sphere and store it in an STL file using
the C programming language.

C:

#include <stdio.h>
#include <math.h>

typedef struct {
float x, y, z;
} Vertex;

typedef struct {
Vertex normal;
Vertex v1, v2, v3;
} Facet;

void writeFacet(FILE *f, Facet facet) {


fwrite(&facet, sizeof(Facet), 1, f);
unsigned short attr = 0;
fwrite(&attr, sizeof(unsigned short), 1, f);
}

int main() {
FILE *f = fopen("sphere.stl", "wb");

char header[80] = "3D Sphere";


fwrite(header, sizeof(char), 80, f);

unsigned int numFacets = 0; // This will be updated later


fwrite(&numFacets, sizeof(unsigned int), 1, f);

int radius = 50; // radius of the sphere


int segments = 100; // number of segments
Facet facet;

for (int i = 0; i < segments; i++) {


for (int j = 0; j < segments; j++) {
float theta1 = i * (2.0f * M_PI / segments);
float theta2 = (i + 1) * (2.0f * M_PI / segments);
float phi1 = j * (M_PI / segments);
float phi2 = (j + 1) * (M_PI / segments);

facet.v1.x = radius * sinf(phi1) * cosf(theta1);


facet.v1.y = radius * sinf(phi1) * sinf(theta1);
facet.v1.z = radius * cosf(phi1);

facet.v2.x = radius * sinf(phi2) * cosf(theta2);


facet.v2.y = radius * sinf(phi2) * sinf(theta2);
facet.v2.z = radius * cosf(phi2);

facet.v3.x = radius * sinf(phi2) * cosf(theta1);


facet.v3.y = radius * sinf(phi2) * sinf(theta1);
facet.v3.z = radius * cosf(phi2);

// Assuming a unit sphere for simplicity. For an actual sphere, the normal would need to
be calculated.
facet.normal = facet.v1;

writeFacet(f, facet);
numFacets++;

facet.v2 = facet.v1;

facet.v1.x = radius * sinf(phi1) * cosf(theta2);


facet.v1.y = radius * sinf(phi1) * sinf(theta2);
facet.v1.z = radius * cosf(phi1);

facet.normal = facet.v1;

writeFacet(f, facet);
numFacets++;
}
}

// Update the number of facets


fseek(f, 80, SEEK_SET);
fwrite(&numFacets, sizeof(unsigned int), 1, f);

fclose(f);

return 0;
}

In this example, we first define the structures for a vertex and a facet. Then
we define a helper function to write a facet to a file. In the main function,
we create a file and initialize it with a header and a placeholder for the
number of facets.
We then iterate over the number of segments to calculate the vertices for
each facet based on the spherical coordinates and write each facet to the
file. After writing all the facets, we go back to the start of the file and
update the number of facets.
This is a simple example of how to create an STL file representing a 3D
sphere. In a more complex scenario, you might need to calculate the normal
vector based on the vertices and handle other details like texture and color
(if supported by your STL variant).

OceanofPDF.com
3.14 AUDIO FILES
PCM (Pulse Code Modulation)
PCM is a method used to digitally represent analog signals. In the context
of audio, it is the standard form for digital audio in computers and various
Blu-ray, DVD and CD formats, as well as other uses such as digital
telephone systems. A PCM stream is a digital representation of an analog
signal where the magnitude of the signal is sampled regularly at uniform
intervals, then quantized to a series of symbols in a digital code. PCM data
is raw, uncompressed, and lossless, but it also requires a lot of storage
space.

WAV (Waveform Audio File Format)


WAV is a common file format for storing an audio bitstream on PCs. It is an
application of the Resource Interchange File Format (RIFF) method for
storing data in "chunks", and thus is also close to the 8SVX and the AIFF
format used on Amiga and Macintosh computers, respectively. It is the main
format used on Windows systems for raw and typically uncompressed
audio. The usual bitstream encoding is the linear pulse-code modulation
(LPCM) format.

AAC (Advanced Audio Coding)


AAC is an audio coding standard for lossy digital audio compression.
Designed to be the successor of the MP3 format, AAC generally achieves
higher sound quality than MP3 at the same bit rate. AAC has been
standardized by ISO and IEC, as part of the MPEG-2 and MPEG-4
specifications. AAC is the default or standard audio format for iPhone,
iPod, iPad, Nintendo DSi, Nintendo 3DS, iTunes, DivX Plus Web Player,
PlayStation 3 and various Nokia Series 40 phones, among others.

FLAC (Free Lossless Audio Codec)


FLAC is an audio coding format for lossless compression of digital audio,
developed by the Xiph.Org Foundation, and is also the name of the free
software project producing the FLAC tools, the reference software package
that includes a codec implementation. Digital audio compressed by FLAC's
algorithm can typically be reduced to 50–60% of its original size and
decompress to an identical copy of the original audio data.

MP3
MP3 (MPEG-1 Audio Layer III or MPEG-2 Audio Layer III) is a coding
format for digital audio developed largely by the Fraunhofer Society in
Germany, with support from other digital scientists in the US and
elsewhere. Originally defined as the third audio format of the MPEG-1
standard, it was retained and further extended—defining additional bit-rates
and support for more audio channels—as the third audio format of the
subsequent MPEG-2 standard. MP3 (or mp3) as a file format commonly
designates files containing an elementary stream of MPEG-1 Audio or
MPEG-2 Audio encoded data, without other complexities of the MP3
standard. It is a widely adopted audio format recognized for its compression
efficiency and wide support across different devices.

OceanofPDF.com
3.14.1 CONVERTING
AUDIO FILES
OCEANOFPDF.COM
3.14.1.2 WAV TO FLAC
WITH LIBFLAC LIBRARY
FLAC, or Free Lossless Audio Codec, is an audio coding format for lossless
compression of digital audio. In this chapter, we will read an audio file,
apply FLAC compression, and save the output to a FLAC file. We will use
the FLAC library, which provides a simple and effective FLAC
compression algorithm.

C:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <FLAC/stream_encoder.h>

typedef struct {
uint16_t audioFormat;
uint16_t numChannels;
uint32_t sampleRate;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample;
} WavHeader;

// A function to read the WAV header


WavHeader readWavHeader(FILE* file) {
WavHeader header;
fseek(file, 20, SEEK_SET);
fread(&header.audioFormat, sizeof(header.audioFormat), 1, file);
fread(&header.numChannels, sizeof(header.numChannels), 1, file);
fread(&header.sampleRate, sizeof(header.sampleRate), 1, file);
fread(&header.byteRate, sizeof(header.byteRate), 1, file);
fread(&header.blockAlign, sizeof(header.blockAlign), 1, file);
fread(&header.bitsPerSample, sizeof(header.bitsPerSample), 1, file);
return header;
}

int main() {
// Open the input file
FILE* infile = fopen("sound.wav", "rb");
if (!infile) {
fprintf(stderr, "ERROR: Could not open input file\n");
return 1;
}

// Read the WAV header


WavHeader header = readWavHeader(infile);
if (header.audioFormat != 1) { // 1 = PCM
fprintf(stderr, "ERROR: Unsupported audio format\n");
return 1;
}

// Skip the WAV header


fseek(infile, 44, SEEK_SET);

// Create a FLAC encoder


FLAC__StreamEncoder* encoder = FLAC__stream_encoder_new();
if (!encoder) {
fprintf(stderr, "ERROR: Could not create FLAC encoder\n");
return 1;
}

// Set the encoder parameters


FLAC__stream_encoder_set_sample_rate(encoder, header.sampleRate);
FLAC__stream_encoder_set_channels(encoder, header.numChannels);
FLAC__stream_encoder_set_bits_per_sample(encoder, header.bitsPerSample);
// Initialize the encoder
if (FLAC__stream_encoder_init_file(encoder, "output.flac", NULL, NULL) !=
FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
fprintf(stderr, "ERROR: Could not initialize FLAC encoder\n");
return 1;
}

// Buffer for reading audio data


int16_t* buffer16 = malloc(sizeof(int16_t) * 1024 * header.numChannels);
// Buffer for converting audio data to 32-bit
FLAC__int32* buffer32 = malloc(sizeof(FLAC__int32) * 1024 * header.numChannels);

size_t read;
while ((read = fread(buffer16, sizeof(int16_t), 1024 * header.numChannels, infile)) > 0) {
// Convert the audio data to 32-bit
for (size_t i = 0; i < read; ++i) {
buffer32[i] = buffer16[i];
}

// Encode the audio data


if (!FLAC__stream_encoder_process_interleaved(encoder, buffer32,
read/header.numChannels)) {
fprintf(stderr, "ERROR: Could not encode audio data\n");
return 1;
}
}

// Finish the encoding process


FLAC__stream_encoder_finish(encoder);

// Clean up
free(buffer16);
free(buffer32);
fclose(infile);
FLAC__stream_encoder_delete(encoder);
return 0;
}

In this program, we first open the input file and create a FLAC encoder. We
set the encoder parameters to match the audio data, and initialize the
encoder.
We then read the audio data from the input file, encode it using the
`FLAC__stream_encoder_process_interleaved` function, and continue this
process until we have processed all the audio data in the input file.
Finally, we finish the encoding process, clean up by closing the input file
and deleting the FLAC encoder. The resulting FLAC file will contain the
compressed audio data.
To perform audio file conversion, such as FLAC to WAV, in C, you'll need
to use a library that can handle audio file I/O. One such library is libsndfile,
which can read and write a variety of audio file formats.

OceanofPDF.com
3.14.1.3 FLAC TO WAV
WITH LIBSNDFILE
LIBRARY
Here's a simple example of how you might use libsndfile to convert a FLAC
file to a WAV file:

C:

#include <stdio.h>
#include <stdlib.h>
#include <sndfile.h>

#define BUFFER_SIZE 1024

int main() {
SF_INFO input_info, output_info;
SNDFILE *input_file, *output_file;
double buffer[BUFFER_SIZE];

// Open the input file


input_file = sf_open("input.flac", SFM_READ, &input_info);
if (input_file == NULL) {
printf("Failed to open the file.\n");
exit(-1);
}

// Set the output file parameters


output_info.samplerate = input_info.samplerate;
output_info.channels = input_info.channels;
output_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
// Open the output file
output_file = sf_open("output.wav", SFM_WRITE, &output_info);
if (output_file == NULL) {
printf("Failed to open the file.\n");
exit(-1);
}

// Main conversion loop


while (1) {
sf_count_t count = sf_read_double(input_file, buffer, BUFFER_SIZE);
if (count == 0) {
break;
}

sf_write_double(output_file, buffer, count);


}

// Clean up
sf_close(input_file);
sf_close(output_file);

return 0;
}

This program reads an audio file, copies the audio data to a new file with a
different format, and writes the result to a new file.
Please note that you'll need to install the libsndfile library and link against it
to compile this program. You can do this by including `-lsndfile` in your gcc
command line.
This code is a simple example and does not include error checking. In a
real-world application, you should always check the return values of
functions and handle errors appropriately.

OceanofPDF.com
3.14.1.4 PCM TO AAC
WITH FAAC LIBRARY
Performing audio compression, such as AAC compression, in C requires the
use of a library that supports the AAC format. The FAAC (Freeware
Advanced Audio Coder) is a popular library for this purpose. Here's a
simple example of how you might use FAAC to compress PCM audio data
to AAC:

C:

#include <stdio.h>
#include <stdlib.h>
#include <faac.h>

#define SAMPLE_RATE 44100


#define NUM_CHANNELS 2
#define BIT_RATE 128000

int main() {
faacEncHandle encoder;
faacEncConfigurationPtr config;

// Initialize the encoder


unsigned long input_samples, max_output_bytes;
encoder = faacEncOpen(SAMPLE_RATE, NUM_CHANNELS, &input_samples,
&max_output_bytes);

// Configure the encoder


config = faacEncGetCurrentConfiguration(encoder);
config->inputFormat = FAAC_INPUT_16BIT;
config->outputFormat = 1;
config->bitRate = BIT_RATE / NUM_CHANNELS;
config->useTns = 1;
faacEncSetConfiguration(encoder, config);

// Allocate buffers
int16_t* input_buffer = (int16_t*)malloc(input_samples * sizeof(int16_t));
unsigned char* output_buffer = (unsigned char*)malloc(max_output_bytes);

// Open the input and output files


FILE* input_file = fopen("input.pcm", "rb");
FILE* output_file = fopen("output.aac", "wb");

// Main encoding loop


while (1) {
// Read PCM data from the input file
size_t num_samples = fread(input_buffer, sizeof(int16_t), input_samples, input_file);
if (num_samples == 0) {
break;
}

// Encode the PCM data to AAC


int num_bytes = faacEncEncode(encoder, input_buffer, num_samples, output_buffer,
max_output_bytes);

// Write the AAC data to the output file


fwrite(output_buffer, 1, num_bytes, output_file);
}

// Clean up
fclose(input_file);
fclose(output_file);
free(input_buffer);
free(output_buffer);
faacEncClose(encoder);
return 0;
}

This program reads PCM audio data from an input file, encodes it to AAC
using the FAAC library, and writes the result to an output file.
Please note that you'll need to install the FAAC library and link against it to
compile this program. You can do this by including `-lfaac` in your gcc
command line.
This code is a simple example and does not include error checking. In a
real-world application, you should always check the return values of
functions and handle errors appropriately.
Also, note that this program assumes the input PCM data is 16-bit stereo at
a sample rate of 44.1 kHz. If your input data is in a different format, you'll
need to adjust the parameters accordingly.

OceanofPDF.com
3.14.1.5 WAV TO MP3 WITH
LAME LIBRARY
MP3, or MPEG-1 Audio Layer III, is a coding format for digital audio. It
uses lossy data compression, meaning that it discards some of the audio
data to reduce the file size, while trying to keep the sound quality as high as
possible. In this chapter, we will read an audio file, apply MP3
compression, and save the output to an MP3 file. We will use the LAME
library, which provides a high-quality MP3 encoder.
We will write a function to perform the MP3 compression:

C:

#include <stdio.h>
#include <lame/lame.h>

#define SAMPLE_RATE 44100 // Sample rate in Hz


#define CHANNELS 2 // Number of audio channels
#define BIT_RATE 128 // Bit rate in kbps

int main() {
// Open the input file
FILE* infile = fopen("input.wav", "rb");
if (!infile) {
fprintf(stderr, "ERROR: Could not open input file\n");
return 1;
}

// Open the output file


FILE* outfile = fopen("output.mp3", "wb");
if (!outfile) {
fprintf(stderr, "ERROR: Could not open output file\n");
return 1;
}

// Create an MP3 encoder


lame_t lame = lame_init();
if (!lame) {
fprintf(stderr, "ERROR: Could not create MP3 encoder\n");
return 1;
}

// Set the encoder parameters


lame_set_in_samplerate(lame, SAMPLE_RATE);
lame_set_num_channels(lame, CHANNELS);
lame_set_brate(lame, BIT_RATE);
lame_init_params(lame);

// Read the audio data, encode it, and write it to the output file
short int buffer[1152 * 2];
unsigned char mp3buffer[1152 * 2];
int read, write;

do {
read = fread(buffer, 2 * sizeof(short int), 1152, infile);
if (read == 0) {
write = lame_encode_flush(lame, mp3buffer, sizeof(mp3buffer));
} else {
write = lame_encode_buffer_interleaved(lame, buffer, read, mp3buffer, sizeof(mp3buffer));
}
fwrite(mp3buffer, write, 1, outfile);
} while (read != 0);

// Clean up
fclose(infile);
fclose(outfile);
lame_close(lame);
return 0;
}

In this program, we first open the input and output files and create an MP3
encoder. We set the encoder parameters to match the audio data.
We then read the audio data from the input file, encode it using the
`lame_encode_buffer_interleaved` function, and write the encoded audio
data to the output file. We continue this process until we have processed all
the audio data in the input file.
Finally, we clean up by closing the input and output files and deleting the
MP3 encoder. The resulting MP3 file will contain the compressed audio
data.

OceanofPDF.com
3.14.1.6 MP3 TO WAV WITH
MPG123 AND SNDFILE
LIBRARY
In this chapter, we'll delve into the process of converting MP3 audio files
into WAV format using the C programming language. Our implementation
will take advantage of two popular open-source libraries, mpg123 and
libsndfile, which respectively facilitate reading MP3 files and writing WAV
files.
Before we get started, it is important to note that you need to have these
libraries installed on your system and properly linked to your C project. You
can download them from the following locations:
- mpg123: https://www.mpg123.de/
- libsndfile: http://www.mega-nerd.com/libsndfile/
Let's dive into the code:

C:

#include <stdio.h>
#include <mpg123.h>
#include <sndfile.h>

int main() {
mpg123_handle *mh;
unsigned char *buffer;
size_t buffer_size;
size_t done;
int err;

int channels, encoding;


long rate;
sf_count_t frames;
SF_INFO sfinfo;

// Initialize mpg123
err = mpg123_init();
if(err != MPG123_OK || (mh = mpg123_new(NULL, &err)) == NULL) {
fprintf(stderr, "mpg123 init error: %s\n", mpg123_plain_strerror(err));
return -1;
}

// Open the MP3 file


if(mpg123_open(mh, "input.mp3") != MPG123_OK ||
mpg123_getformat(mh, &rate, &channels, &encoding) != MPG123_OK) {
fprintf(stderr, "Trouble with mpg123: %s\n", mpg123_strerror(mh));
return -1;
}

// Setup output format


sfinfo.samplerate = rate;
sfinfo.channels = channels;
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;

// Open the output WAV file


SNDFILE* outfile = sf_open("output.wav", SFM_WRITE, &sfinfo);
if (!outfile) {
fprintf(stderr, "Error opening output WAV file\n");
return -1;
}

// Allocate buffer
mpg123_format_none(mh);
mpg123_format(mh, rate, channels, encoding);
buffer_size = mpg123_outblock(mh);
buffer = (unsigned char*) malloc(buffer_size * sizeof(unsigned char));
// Decoding and encoding loop
while(mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK) {
sf_write_raw(outfile, buffer, done);
}

// Cleanup
free(buffer);
mpg123_close(mh);
mpg123_delete(mh);
mpg123_exit();
sf_close(outfile);

return 0;
}

This code starts with the inclusion of necessary header files for our libraries
and the standard I/O library. Following the headers, we declare variables
that we'll need throughout the program. These include variables for
handling the MP3 file (using mpg123) and the output WAV file (using
libsndfile).
The main program flow is as follows:
1. Initialize mpg123: The `mpg123_init` function initializes the mpg123
library. If there's an error in initialization or creating a new mpg123 handle,
we report it and terminate the program.
2. Open the MP3 file: We use `mpg123_open` to open our input MP3 file
and `mpg123_getformat` to get the format of the audio data, including the
sample rate, number of channels, and encoding type.
3. Setup output format: We prepare an `SF_INFO` structure for
the output file. The sample rate and number of channels are set to match the
input file, and the format is set to 16-bit PCM WAV.
4. Open the output WAV file: We use `sf_open` to open the output WAV file
in write mode.
5. Allocate buffer: We set up a buffer to read data from the MP3 file. The
buffer size is determined by `mpg123_outblock`, which gives the maximum
block size that mpg123 can handle.
6. Decoding and encoding loop: We read data from the MP3 file into the
buffer using `mpg123_read`. Then, we write the buffer to the output WAV
file using `sf_write_raw`.
7. Cleanup: After the conversion process, we deallocate the buffer and close
the handles and libraries used.
In the next chapters, we will explore more complex audio processing tasks,
such as applying filters, adjusting volume, and adding effects. But for now,
you can use this simple converter as a starting point for your audio
processing journey in the C language.

OceanofPDF.com
3.14.1.7 PCM TO WAV AND
WAV TO PCM
To perform audio file conversion, such as PCM to WAV and WAV to PCM,
in C, you'll need to use a library that can handle audio file I/O. One such
library is libsndfile, which can read and write a variety of audio file
formats.
Here's a simple example of how you might use libsndfile to convert a PCM
file to a WAV file:

C:

#include <stdio.h>
#include <stdlib.h>
#include <sndfile.h>

#define BUFFER_SIZE 1024

int main() {
SF_INFO info;
SNDFILE *input_file, *output_file;
short buffer[BUFFER_SIZE];

// Set the input file parameters


info.samplerate = 44100;
info.channels = 2;
info.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;

// Open the input file


input_file = sf_open("input.pcm", SFM_READ, &info);
if (input_file == NULL) {
printf("Failed to open the file.\n");
exit(-1);
}

// Set the output file parameters


info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;

// Open the output file


output_file = sf_open("output.wav", SFM_WRITE, &info);
if (output_file == NULL) {
printf("Failed to open the file.\n");
exit(-1);
}

// Main conversion loop


while (1) {
sf_count_t count = sf_read_short(input_file, buffer, BUFFER_SIZE);
if (count == 0) {
break;
}

sf_write_short(output_file, buffer, count);


}

// Clean up
sf_close(input_file);
sf_close(output_file);

return 0;
}

This program reads a PCM audio file, copies the audio data to a new WAV
file.
For the reverse operation, WAV to PCM, you would simply swap the input
and output formats:
C:

// Set the input file parameters


info.samplerate = 44100;
info.channels = 2;
info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;

// Open the input file


input_file = sf_open("input.wav", SFM_READ, &info);
if (input_file == NULL) {
printf("Failed to open the file.\n");
exit(-1);
}

// Set the output file parameters


info.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;

// Open the output file


output_file = sf_open("output.pcm", SFM_WRITE, &info);
if (output_file == NULL) {
printf("Failed to open the file.\n");
exit(-1);
}

Please note that you'll need to install the libsndfile library and link against it
to compile this program. You can do this by including `-lsndfile` in your gcc
command line.
This code is a simple example and does not include error checking. In a
real-world application, you should always check the return values of
functions and handle errors appropriately.

OceanofPDF.com
3.14.1.8 GETTING
PROPERTIES OF WAV FILE
To get the parameters of audio files like PCM and WAV, you can use the
libsndfile library in C. This library provides functions to read the properties
of an audio file.
Here's a simple example of how you might use libsndfile to read the
properties of a WAV file:

C:

#include <stdio.h>
#include <stdlib.h>
#include <sndfile.h>

int main() {
SF_INFO info;
SNDFILE *file;

// Open the file


file = sf_open("input.wav", SFM_READ, &info);
if (file == NULL) {
printf("Failed to open the file.\n");
exit(-1);
}

// Print the properties


printf("Sample rate: %d\n", info.samplerate);
printf("Channels: %d\n", info.channels);
printf("Format: %08X\n", info.format);
// Clean up
sf_close(file);

return 0;
}

This program opens a WAV file and prints its sample rate, number of
channels, and format. The format is a bit mask that encodes both the file
format and the sample format. You can use the
SF_FORMAT_TYPEMASK, SF_FORMAT_SUBMASK, and
SF_FORMAT_ENDMASK masks to extract the individual components of
the format.

For a PCM file, you would need to know the sample rate, number of
channels, and sample format in advance, as the PCM format does not
include a header with this information. If you know these parameters, you
can set them in the SF_INFO structure before opening the file.
Please note that you'll need to install the libsndfile library and link against it
to compile this program. You can do this by including `-lsndfile` in your gcc
command line.
This code is a simple example and does not include error checking. In a
real-world application, you should always check the return values of
functions and handle errors appropriately.

OceanofPDF.com
3.14.2 DIGITAL SOUND
OPERATIONS
Welcome to the chapter on Digital Sound Operations in C. In this chapter,
we will delve into the fascinating world of sound and how we can
manipulate it using the C programming language.
Sound is a fundamental part of our daily lives, and its digital representation
opens up a myriad of possibilities for manipulation and analysis. From
simple tasks such as volume control and equalization to more complex
operations like noise reduction, echo cancellation, and sound synthesis,
digital sound operations are at the heart of many modern technologies.
Audio signal processing, a subset of signal processing, is specifically
concerned with the computational methods for intentionally altering sounds.
This involves a wide range of operations, including basic digital filtering
techniques, Fourier Transform, spectral analysis, and much more.
In this chapter, we will explore how to perform these operations using C, a
powerful and efficient programming language that is particularly well-
suited to this kind of low-level, performance-critical work. We will cover
the fundamental concepts, delve into various libraries and tools available in
C for audio processing, and walk through practical examples and
applications.
Whether you're looking to develop a music player, a voice recognition
system, a digital audio workstation, or simply want to better understand
how sound can be manipulated and transformed, this chapter will provide
you with the knowledge and skills you need.
So, let's dive in and explore the exciting world of digital sound operations
and audio signal processing in C!

OceanofPDF.com
3.14.2.1 NORMALIZATION
OF AUDIO
Normalization is the process of adjusting the volume of an audio file to a
standard level. This is useful when you want to ensure that all your audio
files play back at the same volume. In this chapter, we will read an audio
file, normalize its volume, and save the output to an MP3 file. For this, we
will use the libsndfile library to read the audio file, and the LAME library to
write the MP3 file.

C:

#include <stdio.h>
#include <stdlib.h>
#include <sndfile.h>
#include <lame/lame.h>
#include <math.h>

#define BUFFER_SIZE 8192

int main() {
// Open the input file
SF_INFO sfinfo;
SNDFILE* infile = sf_open("input.wav", SFM_READ, &sfinfo);
if (!infile) {
fprintf(stderr, "ERROR: Could not open input file\n");
return 1;
}

// Initialize LAME
lame_t lame = lame_init();
lame_set_num_channels(lame, sfinfo.channels); // Set the number of channels
lame_set_in_samplerate(lame, sfinfo.samplerate);
lame_set_VBR(lame, vbr_default);
lame_init_params(lame);

// Open the output file


FILE* outfile = fopen("output.mp3", "wb");
if (!outfile) {
fprintf(stderr, "ERROR: Could not open output file\n");
return 1;
}

// Buffer for MP3 data


unsigned char mp3buf[BUFFER_SIZE];

// Buffer for the audio data


float buffer[BUFFER_SIZE];
short int obuffer_left[BUFFER_SIZE];
short int obuffer_right[BUFFER_SIZE];

// Find the maximum absolute sample value for normalization


sf_count_t totalFrames = sfinfo.frames * sfinfo.channels;
float max = 0;
while (totalFrames > 0) {
sf_count_t frames = sf_read_float(infile, buffer, BUFFER_SIZE < totalFrames ?
BUFFER_SIZE : totalFrames);
for (int i = 0; i < frames; i++) {
if (fabs(buffer[i]) > max) {
max = fabs(buffer[i]);
}
}
totalFrames -= frames;
}

// Rewind the input file to start reading again


sf_seek(infile, 0, SEEK_SET);
// Read, normalize and encode the audio data
totalFrames = sfinfo.frames * sfinfo.channels;
while (totalFrames > 0) {
sf_count_t frames = sf_read_float(infile, buffer, BUFFER_SIZE < totalFrames ?
BUFFER_SIZE : totalFrames);

// Separate and normalize the audio data


for (int i = 0; i < frames; i++) {
short int sample = (short int) (buffer[i] / max * 32767); // Normalize

if (sfinfo.channels == 2) {
// For stereo audio, separate the channels
if (i % 2 == 0) {
obuffer_left[i / 2] = sample;
} else {
obuffer_right[i / 2] = sample;
}
} else {
// For mono audio
obuffer_left[i] = sample;
}
}

// Encode to MP3
int wrote;
if (sfinfo.channels == 2) {
wrote = lame_encode_buffer(lame, obuffer_left, obuffer_right, frames / 2, mp3buf,
BUFFER_SIZE);
} else {
wrote = lame_encode_buffer(lame, obuffer_left, NULL, frames, mp3buf, BUFFER_SIZE);
}

// Check for errors


if (wrote < 0) {
fprintf(stderr, "ERROR: Could not encode to MP3\n");
break;
}

// Write MP3 data to file


fwrite(mp3buf, sizeof(unsigned char), wrote, outfile);

totalFrames -= frames;
}

// Clean up
lame_close(lame);
sf_close(infile);
fclose(outfile);

return 0;
}

In this program, we first open the input file using the `sf_open` function
from the libsndfile library. We then initialize LAME and set its parameters
using the `lame_init`, `lame_set_in_samplerate`, `lame_set_VBR`, and
`lame_init_params` functions. We open the output file using the `fopen`
function.
We then enter a loop where we read audio data from the input file into a
buffer using the `sf_read_float` function. We find the maximum sample
value in the buffer, and normalize the audio data by dividing each sample in
the buffer by the maximum value. We convert the normalized audio data to
16-bit PCM format and write it to the output file using the
`lame_encode_buffer` function.
We continue this process until we have processed all the audio data in the
input file. Finally, we close LAME, the input file, and the output file using
the `lame_close`, `sf_close`, and `fclose` functions, respectively. These
steps ensure that all resources associated with LAME, the input file, and the
output file are properly released.
In order to ensure that all resources associated with LAME, the input file,
and the output file are properly released, we need to close each of these
resources.
1. LAME: We close LAME using the `lame_close` function. This function
takes as input the LAME instance that we want to close. It cleans up all the
internal structures that LAME has created during the encoding process. If
we don't call this function, we might have memory leaks in our program.
2. Input File: We close the input file using the `sf_close` function from the
libsndfile library. This function takes as input the file handle of the file that
we want to close. It closes the file and frees all resources associated with it.
If we don't call this function, we might have file descriptors that remain
open, which can lead to resource leaks.
3. Output File: We close the output file using the `fclose` function from the
standard C library. This function takes as input the file pointer of the file
that we want to close. It flushes any buffered data to the file, closes the file,
and frees the file pointer. If we don't call this function, we might have
buffered data that is not written to the file, and the file might remain open,
which can lead to data loss and resource leaks.
By calling these functions at the end of our program, we ensure that all
resources are properly released, and we prevent memory leaks, resource
leaks, and data loss.

OceanofPDF.com
3.14.2.2 AMPLITUDE
COMPRESSION OF AUDIO
Amplitude compression is a technique used in audio processing to reduce
the dynamic range of an audio signal. This can be useful in various
situations, such as when you want to make quiet sounds louder or prevent
loud sounds from clipping. In this chapter, we will read an audio file, apply
amplitude compression, and save the output to a WAV file. For this, we will
use the libsndfile library.

C:

#include <stdio.h>
#include <sndfile.h>
#include <math.h>

#define BUFFER_SIZE 8192


#define THRESHOLD 0.5
#define RATIO 4.0

int main() {
// Open the input file
SF_INFO sfinfo;
SNDFILE* infile = sf_open("input.wav", SFM_READ, &sfinfo);
if (!infile) {
fprintf(stderr, "ERROR: Could not open input file\n");
return 1;
}

// Open the output file


SNDFILE* outfile = sf_open("output.wav", SFM_WRITE, &sfinfo);
if (!outfile) {
fprintf(stderr, "ERROR: Could not open output file\n");
return 1;
}

// Apply amplitude compression to the audio data


float buffer[BUFFER_SIZE];
sf_count_t frames;
while ((frames = sf_read_float(infile, buffer, BUFFER_SIZE)) > 0) {
// Apply compression
for (int i = 0; i < frames; i++) {
float magnitude = fabs(buffer[i]);
if (magnitude > THRESHOLD) {
float db = log10(magnitude);
float compressed_db = THRESHOLD + (db - THRESHOLD) / RATIO;
buffer[i] = copysign(pow(10, compressed_db), buffer[i]);
}
}

// Write the compressed audio data to the output file


sf_write_float(outfile, buffer, frames);
}

// Clean up
sf_close(infile);
sf_close(outfile);

return 0;
}

In this program, we first open the input and output files using the `sf_open`
function from the libsndfile library. We then enter a loop where we read
audio data from the input file into a buffer using the `sf_read_float`
function. We apply amplitude compression to each sample in the buffer by
checking if its magnitude (absolute value) is above a certain threshold. If it
is, we compress its decibel value using a specified ratio and update the
sample value. We then write the compressed audio data to the output file
using the `sf_write_float` function. We continue this process until we have
processed all the audio data in the input file. Finally, we close the input and
output files using the `sf_close` function.

OceanofPDF.com
3.14.2.3 AUDIO REVERSAL
Audio reversal is a technique used in audio processing to reverse the audio
signal, making it play backwards. This can be useful in various situations,
such as when you want to create special effects or analyze audio signals. In
this chapter, we will read an audio file, apply audio reversal, and save the
output to a WAV file. For this, we will use the libsndfile library for reading
and writing audio files.

C:

#include <stdio.h>
#include <sndfile.h>
#include <math.h>
#include <stdlib.h>

#define BUFFER_SIZE 8192

int main() {
// Open the input file
SF_INFO sfinfo;
SNDFILE* infile = sf_open("input.wav", SFM_READ, &sfinfo);
if (!infile) {
fprintf(stderr, "ERROR: Could not open input file\n");
return 1;
}

// Allocate a buffer to hold the entire audio data


sf_count_t num_samples = sfinfo.frames * sfinfo.channels;
float* buffer = malloc(num_samples * sizeof(float));
if (!buffer) {
fprintf(stderr, "ERROR: Could not allocate buffer\n");
return 1;
}

// Read the audio data into the buffer


sf_count_t num_read = sf_read_float(infile, buffer, num_samples);
if (num_read != num_samples) {
fprintf(stderr, "ERROR: Could not read audio data\n");
return 1;
}

// Close the input file


sf_close(infile);

// Reverse the audio data


for (int i = 0; i < num_samples / 2; i++) {
float temp = buffer[i];
buffer[i] = buffer[num_samples - i - 1];
buffer[num_samples - i - 1] = temp;
}

// Open the output file


SNDFILE* outfile = sf_open("output.wav", SFM_WRITE, &sfinfo);
if (!outfile) {
fprintf(stderr, "ERROR: Could not open output file\n");
return 1;
}

// Write the reversed audio data to the output file


sf_count_t num_written = sf_write_float(outfile, buffer, num_samples);
if (num_written != num_samples) {
fprintf(stderr, "ERROR: Could not write audio data\n");
return 1;
}

// Close the output file


sf_close(outfile);
// Free the buffer
free(buffer);

return 0;
}

In this program, we first open the input file using the `sf_open` function
from the libsndfile library. We then allocate a buffer to hold the entire audio
data. We read the audio data into the buffer using the `sf_read_float`
function, and close the input file.
We then reverse the audio data by swapping each sample in the buffer with
the corresponding sample from the end of the buffer. This is done in a loop
that runs for half the number of samples in the buffer.
After reversing the audio data, we open the output file and write the
reversed audio data to it using the `sf_write_float` function. We then close
the output file and free the buffer.
This completes the process of applying audio reversal to an audio file in C
using libsndfile. The resulting audio file will have the same content as the
original, but played in reverse. This technique can be used to create a
variety of audio effects, and is a fundamental part of many digital audio
processing systems.

OceanofPDF.com
3.14.2.4 AUDIO MIXING
Audio mixing is the process of combining multiple sound signals into one
or more channels. In this chapter, we will read two audio files, mix them
together, and save the output to a WAV file. For this, we will use the
libsndfile library for reading and writing audio files.

C:

#include <stdio.h>
#include <sndfile.h>
#include <math.h>
#include <stdlib.h>

#define BUFFER_SIZE 8192

int main() {
// Open the input files
SF_INFO sfinfo1, sfinfo2;
SNDFILE* infile1 = sf_open("summer-walk-152722.wav", SFM_READ, &sfinfo1);
SNDFILE* infile2 = sf_open("input.wav", SFM_READ, &sfinfo2);
if (!infile1 || !infile2) {
fprintf(stderr, "ERROR: Could not open input files\n");
return 1;
}

// Check if the audio files have the same sample rate and number of channels
if (sfinfo1.samplerate != sfinfo2.samplerate || sfinfo1.channels != sfinfo2.channels) {
fprintf(stderr, "ERROR: Input files must have the same sample rate and number of channels\n");
return 1;
}
// Allocate buffers to hold the audio data
float* buffer1 = malloc(BUFFER_SIZE * sizeof(float));
float* buffer2 = malloc(BUFFER_SIZE * sizeof(float));
if (!buffer1 || !buffer2) {
fprintf(stderr, "ERROR: Could not allocate buffers\n");
return 1;
}

// Open the output file


SNDFILE* outfile = sf_open("output.wav", SFM_WRITE, &sfinfo1);
if (!outfile) {
fprintf(stderr, "ERROR: Could not open output file\n");
return 1;
}

// Process the audio data


sf_count_t frames;
while ((frames = sf_readf_float(infile1, buffer1, BUFFER_SIZE / sfinfo1.channels)) > 0) {
sf_readf_float(infile2, buffer2, frames);

// Mix the audio data


for (int i = 0; i < frames * sfinfo1.channels; i++) {
buffer1[i] = (buffer1[i] + buffer2[i]) / 2;
}

// Write the mixed audio data to the output file


sf_writef_float(outfile, buffer1, frames);
}

// Clean up
sf_close(infile1);
sf_close(infile2);
sf_close(outfile);
free(buffer1);
free(buffer2);
return 0;
}

In this program, we first open the input files using the `sf_open` function
from the libsndfile library. We then allocate buffers to hold the audio data.
We open the output file and start processing the audio data. For each block
of audio data, we read the same amount of data from both input files, mix
the audio data by averaging the corresponding samples, and write the mixed
audio data to the output file using the `sf_writef_float` function. We
continue this process until we have processed all the audio data in the input
files.
Finally, we clean up by closing the input and output files and freeing the
buffers.
This completes the process of mixing two audio files in C using libsndfile.
The resulting audio file will have the combined sound of the original audio
files. This technique can be used to create a variety of audio effects, and is a
fundamental part of many digital audio processing systems.
It's important to note that the mixing process used here is a simple
averaging of the audio samples. This might not always be the best approach,
especially when dealing with audio files of different volumes or dynamic
ranges. In such cases, more sophisticated mixing techniques might be
required, such as adjusting the gain of each audio file before mixing, or
using dynamic range compression to prevent clipping.

Also, this example assumes that the input audio files have the same sample
rate and number of channels. If this is not the case, you would need to
perform sample rate conversion and/or channel mixing to ensure that the
audio files can be mixed together correctly.
In the real world, audio mixing is often a complex process that involves not
only combining audio signals, but also adjusting their relative volumes,
equalization, panning, and other aspects to achieve the desired sound.
However, the basic concept of combining audio signals, as demonstrated in
this example, is a fundamental part of that process.

OceanofPDF.com
3.14.2.5 ECHO EFFECT
An echo is a reflection of sound that arrives at the listener with a delay after
the direct sound. To create an echo effect, we need to add a delayed version
of the signal to the original signal. In this chapter, we will read an audio
file, apply an echo effect, and save the output to a WAV file. We will use the
libsndfile library for reading and writing audio files.

C:

#include <stdio.h>
#include <sndfile.h>
#include <math.h>
#include <stdlib.h>

#define BUFFER_SIZE 8192


#define ECHO_DELAY 0.3 // Echo delay in seconds
#define ECHO_DECAY 0.6 // Echo decay factor

int main() {
// Open the input file
SF_INFO sfinfo;
SNDFILE* infile = sf_open("input.wav", SFM_READ, &sfinfo);
if (!infile) {
fprintf(stderr, "ERROR: Could not open input file\n");
return 1;
}

// Allocate buffers to hold the audio data and the echo


float* buffer = malloc(BUFFER_SIZE * sizeof(float));
float* echo = calloc(BUFFER_SIZE, sizeof(float));
if (!buffer || !echo) {
fprintf(stderr, "ERROR: Could not allocate buffers\n");
return 1;
}

// Calculate the echo delay in samples


int delay_samples = ECHO_DELAY * sfinfo.samplerate;

// Open the output file


SNDFILE* outfile = sf_open("output.wav", SFM_WRITE, &sfinfo);
if (!outfile) {
fprintf(stderr, "ERROR: Could not open output file\n");
return 1;
}

// Process the audio data


sf_count_t frames;
while ((frames = sf_readf_float(infile, buffer, BUFFER_SIZE / sfinfo.channels)) > 0) {
// Apply the echo effect
for (int i = 0; i < frames * sfinfo.channels; i++) {
int j = (i + delay_samples) % BUFFER_SIZE;
echo[j] = buffer[i] + ECHO_DECAY * echo[j];
buffer[i] = echo[j];
}

// Write the processed audio data to the output file


sf_writef_float(outfile, buffer, frames);
}

// Clean up
sf_close(infile);
sf_close(outfile);
free(buffer);
free(echo);

return 0;
}
In this program, we first open the input file using the `sf_open` function
from the libsndfile library. We then allocate buffers to hold the audio data
and the echo.
We calculate the echo delay in samples based on the sample rate of the
audio file and the desired echo delay in seconds.
We open the output file and start processing the audio data. For each block
of audio data, we apply the echo effect by adding a decayed version of the
delayed audio data to the current audio data, and write the processed audio
data to the output file using the `sf_writef_float` function. We continue this
process until we have processed all the audio data in the input file.
Finally, we clean up by closing the input and output files and freeing the
buffers.
This completes the process of applying an echo effect to an audio file in C
using libsndfile. The resulting audio file will have the echo effect applied to
the original audio. This technique can be used to create a variety of audio
effects, and is a fundamental part of many digital audio processing systems.

OceanofPDF.com
3.14.2.6 REVERB EFFECT
Reverberation, or reverb, is created when a sound or signal is reflected
causing numerous reflections to build up and then decay as the sound is
absorbed by the surfaces of objects in the space – which could include
furniture, people, and air. This is most noticeable when the sound source
stops but the reflections continue, decreasing in amplitude, until they reach
zero amplitude.
In this chapter, we will read an audio file, apply a simple reverb effect, and
save the output to a WAV file. We will use the libsndfile library for reading
and writing audio files.

C:

#include <stdio.h>
#include <sndfile.h>
#include <math.h>
#include <stdlib.h>

#define BUFFER_SIZE 8192


#define REVERB_TIME 0.5 // Reverb time in seconds
#define REVERB_DECAY 0.5 // Reverb decay factor

int main() {
// Open the input file
SF_INFO sfinfo;
SNDFILE* infile = sf_open("input.wav", SFM_READ, &sfinfo);
if (!infile) {
fprintf(stderr, "ERROR: Could not open input file\n");
return 1;
}
// Allocate buffers to hold the audio data and the reverb
float* buffer = malloc(BUFFER_SIZE * sizeof(float));
float* reverb = calloc(BUFFER_SIZE, sizeof(float));
if (!buffer || !reverb) {
fprintf(stderr, "ERROR: Could not allocate buffers\n");
return 1;
}

// Calculate the reverb delay in samples


int delay_samples = REVERB_TIME * sfinfo.samplerate;

// Open the output file


SNDFILE* outfile = sf_open("output.wav", SFM_WRITE, &sfinfo);
if (!outfile) {
fprintf(stderr, "ERROR: Could not open output file\n");
return 1;
}

// Process the audio data


sf_count_t frames;
while ((frames = sf_readf_float(infile, buffer, BUFFER_SIZE / sfinfo.channels)) > 0) {
// Apply the reverb effect
for (int i = 0; i < frames * sfinfo.channels; i++) {
int j = (i + delay_samples) % BUFFER_SIZE;
reverb[j] = buffer[i] + REVERB_DECAY * reverb[j];
buffer[i] = reverb[j];
}

// Write the processed audio data to the output file


sf_writef_float(outfile, buffer, frames);
}

// Clean up
sf_close(infile);
sf_close(outfile);
free(buffer);
free(reverb);

return 0;
}

In this program, we first open the input file using the `sf_open` function
from the libsndfile library. We then allocate buffers to hold the audio data
and the reverb.
We calculate the reverb delay in samples based on the sample rate of the
audio file and the desired reverb time in seconds.
We open the output file and start processing the audio data. For each block
of audio data, we apply the reverb effect by adding a decayed version of the
delayed audio data to the current audio data, and write the processed audio
data to the output file using the `sf_writef_float` function. We continue this
process until we have processed all the audio data in the input file.
Finally, we clean up by closing the input and output files and freeing the
buffers.
The reverb effect can be added to an audio file in C using the libsndfile
library. This process involves reading the audio file, applying the reverb
effect, and saving the output to a new audio file. The resulting audio file
will have the reverb effect applied to the original audio, creating a sense of
space and depth.
This technique can indeed be used to create a variety of audio effects. By
adjusting the parameters of the reverb effect, such as the reverb time and
decay factor, different types of reverb can be achieved. These can range
from a small room reverb to a large hall reverb, and everything in between.
Moreover, this technique is a fundamental part of many digital audio
processing systems. Reverb is a common effect used in music production,
film sound design, and other audio-related fields. By understanding how to
implement reverb in C, you can gain a deeper understanding of how these
systems work and how to create your own audio effects.

OceanofPDF.com
3.14.3 EXTRACTING
AUDIO FEATURES WITH
AUBIO LIBRARY
Audio feature extraction is a crucial part of many audio processing tasks,
such as music information retrieval, speech recognition, and audio
classification. In this chapter, we will read an audio file and extract some
basic audio features, such as pitch, tempo, and timbre. We will use the
Aubio library, which provides a variety of audio analysis functions.

C:

#include <stdio.h>
#include <aubio/aubio.h>

#define HOP_SIZE 512 // Hop size in samples


#define WINDOW_SIZE 1024 // Window size for FFT
#define MFCC_SIZE 13 // Number of MFCC coefficients
#define N_FILTERS 40 // Number of filters in the filterbank

int main() {
// Open the input file
char* filename = "input.wav";
uint_t samplerate = 0; // Let Aubio choose the sample rate
uint_t channels = 1; // Mono audio

// Create an Aubio source object


aubio_source_t* source = new_aubio_source(filename, samplerate, HOP_SIZE);
if (!source) {
fprintf(stderr, "ERROR: Could not create Aubio source\n");
return 1;
}

// Get the actual sample rate


samplerate = aubio_source_get_samplerate(source);

// Create Aubio pitch, tempo, and mfcc objects


aubio_pitch_t* pitch = new_aubio_pitch("default", WINDOW_SIZE, HOP_SIZE, samplerate);
aubio_tempo_t* tempo = new_aubio_tempo("default", WINDOW_SIZE, HOP_SIZE, samplerate);
aubio_mfcc_t* mfcc = new_aubio_mfcc(WINDOW_SIZE, N_FILTERS, MFCC_SIZE,
samplerate);

// Allocate buffers for the audio data and features


fvec_t* buffer = new_fvec(HOP_SIZE);
fvec_t* pitch_values = new_fvec(1);
fvec_t* tempo_values = new_fvec(1);
fvec_t* mfcc_values = new_fvec(MFCC_SIZE);

// Read the audio data and extract the features


uint_t read_samples = 0;
do {
aubio_source_do(source, buffer, &read_samples);
aubio_pitch_do(pitch, buffer, pitch_values);
aubio_tempo_do(tempo, buffer, tempo_values);
cvec_t* complex_buffer = new_cvec(WINDOW_SIZE);
aubio_pvoc_t* pv = new_aubio_pvoc(WINDOW_SIZE, HOP_SIZE);
aubio_pvoc_do(pv, buffer, complex_buffer);
aubio_mfcc_do(mfcc, complex_buffer, mfcc_values);
del_aubio_pvoc(pv);
del_cvec(complex_buffer);

// Print the feature values


printf("Pitch: %f, Tempo: %f, MFCC: [", pitch_values->data[0], tempo_values->data[0]);
for (uint_t i = 0; i < MFCC_SIZE; i++) {
printf("%f", mfcc_values->data[i]);
if (i < MFCC_SIZE - 1) {
printf(", ");
}
}
printf("]\n");
} while (read_samples == HOP_SIZE);

// Clean up
del_aubio_source(source);
del_aubio_pitch(pitch);
del_aubio_tempo(tempo);
del_aubio_mfcc(mfcc);
del_fvec(buffer);
del_fvec(pitch_values);
del_fvec(tempo_values);
del_fvec(mfcc_values);

return 0;
}

In this program, we start by opening the input file and creating an Aubio
source object. Following this, we generate Aubio objects for pitch, tempo,
and MFCC (Mel-frequency cepstral coefficients, which are a measure of
timbre).
We then allocate buffers to hold the audio data and the values of the
features. The audio data is read from the input file, and the features are
extracted using the `aubio_pitch_do`, `aubio_tempo_do`, and
`aubio_mfcc_do` functions.
Finally, the values of the extracted features are printed out. This process
continues until all the audio data in the input file has been processed.
This program demonstrates how to extract basic audio features from an
audio file in C using the Aubio library. The resulting feature values can be
used for a variety of audio analysis tasks, such as genre classification,
music recommendation, and beat detection.

- The Pitch is the perceived frequency of a sound. It's measured in Hertz


(Hz). The value of pitch depends on the dominant frequency present in the
audio at the current point in time.
- The Tempo is the speed or pace of a given piece of music. It is usually
expressed in Beats Per Minute (BPM). However, in your output, the tempo
values are consistently 0.000000, which could be a result of not having any
beat detected in the given window of the audio, or due to the specific tempo
detection method not functioning as expected for your audio input.
- The MFCC (Mel-frequency cepstral coefficients) is a type of spectral
representation used often in speech and music signal processing. It's an
array of values that describes the shape of the power spectrum of an audio
signal, providing a sort of 'fingerprint' of its spectral characteristics.

OceanofPDF.com
3.14.4 PLAYING AUDIO
WITH SDL LIBRARY
In this chapter, we will explore how to play sound in the C programming
language. We will demonstrate how to load an audio sound file and play it
using a library called SDL (Simple DirectMedia Layer), this code will allow
us to verify all the performed operations on audio files in an organoleptic
approche.

Step 1: Set up the SDL library


Before we can play sound in C, we need to set up the SDL library. SDL
provides a simple and cross-platform solution for multimedia operations,
including sound playback.
You can download the SDL library from the official website:
https://github.com/libsdl-org/SDL/releases
and follow the installation instructions specific to your operating system.

Step 2: Include the necessary headers and initialize SDL, Load and
play the audio sound file

C:

#include <stdio.h>
#include <SDL2/SDL.h>

int main() {
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
printf("SDL initialization failed: %s\n", SDL_GetError());
return 1;
}
SDL_AudioSpec wavSpec;
Uint8* wavBuffer;
Uint32 wavLength;

if (SDL_LoadWAV("sound.wav", &wavSpec, &wavBuffer, &wavLength) == NULL) {


printf("Failed to load sound.wav: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}

SDL_AudioDeviceID deviceId = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, 0);


if (deviceId == 0) {
printf("Failed to open audio device: %s\n", SDL_GetError());
SDL_FreeWAV(wavBuffer);
SDL_Quit();
return 1;
}

SDL_QueueAudio(deviceId, wavBuffer, wavLength);


SDL_PauseAudioDevice(deviceId, 0);
SDL_Delay(5000); // Play sound for 5 seconds

SDL_CloseAudioDevice(deviceId);
SDL_FreeWAV(wavBuffer);
SDL_Quit();
return 0;
}

In the above code, we first define the necessary variables: `wavSpec` to


hold the audio specification, `wavBuffer` to store the audio data, and
`wavLength` to store the length of the audio data.
Next, we use `SDL_LoadWAV` to load the sound file `"sound.wav"` into
the `wavSpec`, `wavBuffer`, and `wavLength` variables. If the loading fails,
we print an error message, free the memory using `SDL_FreeWAV`, quit
SDL using `SDL_Quit`, and return from the program.
We then open an audio device using `SDL_OpenAudioDevice` and obtain a
`deviceId`. If the device opening fails, we print an error message, free the
memory, quit SDL, and return from the program.
Next, we queue the audio data using `SDL_QueueAudio`, unpause the
audio device using `SDL_PauseAudioDevice`, and introduce a delay of 5
seconds using `SDL_Delay` to allow the sound to play.
Finally, we close the audio device using `SDL_CloseAudioDevice`, free the
memory, quit SDL, and return from the program.

Step 3: Compile and run the program


To compile the program, you need to link against the SDL library. Use the
following command:

bash:

gcc -o sound_program sound_program.c -lSDL2

Replace `sound_program` with your desired executable name, and


`sound_program.c` with the name of your source file.
Run the compiled program, and it should load and play the sound file
`"sound.wav"`.

Conclusion
In this chapter, we explored how to play sound in C language using the
SDL library. We covered the steps to initialize SDL, load an audio sound
file, and play it through an audio device. With this knowledge, you can
extend the code to handle different audio formats, implement controls, and
integrate sound functionality into your C programs.

OceanofPDF.com
3.15 PARALLEL
COMPUTING WITH
OPENCL
OpenCL, an acronym for Open Computing Language, is a framework that
has revolutionized the world of parallel computing. It is an open standard
for writing code that runs across heterogeneous platforms, including CPUs,
GPUs, DSPs, and other types of processors. This chapter delves into the
fascinating world of OpenCL, exploring its history, goals, methodologies,
and future directions.

Short History of OpenCL


OpenCL was conceived by Apple Inc. and subsequently developed and
maintained by the Khronos Group, a non-profit consortium dedicated to
creating open standard APIs. The initial idea was proposed by Apple in June
2008, and the first version, OpenCL 1.0, was ratified by the Khronos Group
in December 2009.
The development of OpenCL was driven by the increasing demand for a
unified, cross-platform solution for harnessing the power of multi-core
processors and graphics processing units (GPUs) for general-purpose
computing. Since its inception, OpenCL has undergone several revisions,
with each version introducing new features and improvements to enhance
performance and usability. As of my knowledge cutoff in September 2021,
the latest version is OpenCL 3.0, released in September 2020.

The Goal of OpenCL


The primary goal of OpenCL is to provide a standard interface for parallel
computing using task- and data-based parallelism. It aims to leverage the
computational power of heterogeneous platforms, allowing developers to
write efficient, portable code that can run on a wide range of hardware
architectures.
OpenCL seeks to democratize access to powerful computing resources. By
providing a unified programming model, it enables developers to harness
the power of high-performance computing without being tied to a specific
hardware vendor or architecture.

How OpenCL Achieves its Goals


OpenCL achieves its goals through a few key strategies. Firstly, it provides
a language (based on C99) for writing kernels (functions that run on
OpenCL devices), and APIs that are used to define and control the
platforms.
Secondly, OpenCL abstracts the hardware details, allowing code to be
written once and executed across various hardware platforms. This is
achieved through a layered approach, where the hardware is abstracted into
a global memory and a collection of compute units, each with its local
memory and a set of processing elements.
Finally, OpenCL provides fine-grained control over computational
resources. It allows developers to explicitly manage memory and optimize
data transfer, enabling them to fine-tune their applications for maximum
performance.

The Future of OpenCL


The future of OpenCL is geared towards expanding its capabilities and
making it more accessible to developers. The release of OpenCL 3.0 has
made all functionality beyond version 1.2 optional, allowing for greater
flexibility and broader hardware support.
Furthermore, the integration of OpenCL with Vulkan, another API from the
Khronos Group, is expected to provide a unified programming model for
compute and graphics tasks. This will allow developers to leverage the best
of both worlds, leading to more efficient and powerful applications.
In the long term, OpenCL is likely to continue evolving to keep pace with
the rapid advancements in hardware technology. The focus will remain on
enhancing performance, improving usability, and ensuring that OpenCL
remains the go-to solution for cross-platform, high-performance computing.

OceanofPDF.com
3.15.1 ENUMERATING
OPENCL DEVICES
In this chapter, we will explore how to enumerate OpenCL devices using C.
This is a crucial step in any OpenCL program as it allows us to identify the
available devices that can be used for computation.

C:

// Include the OpenCL header file


#include <CL/cl.h>

// Include the standard I/O header file for printf


#include <stdio.h>

int main() {
// Get the number of platforms
cl_uint numPlatforms;
clGetPlatformIDs(0, NULL, &numPlatforms);

// Allocate memory for the platforms


cl_platform_id *platforms = (cl_platform_id *)malloc(sizeof(cl_platform_id) * numPlatforms);

// Get the platform IDs


clGetPlatformIDs(numPlatforms, platforms, NULL);

// Loop over each platform


for (cl_uint i = 0; i < numPlatforms; i++) {
// Get the number of devices in the platform
cl_uint numDevices;
clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, 0, NULL, &numDevices);
// Allocate memory for the devices
cl_device_id *devices = (cl_device_id *)malloc(sizeof(cl_device_id) * numDevices);

// Get the device IDs


clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, numDevices, devices, NULL);

// Loop over each device


for (cl_uint j = 0; j < numDevices; j++) {
// Get the device name
char deviceName[1024];
clGetDeviceInfo(devices[j], CL_DEVICE_NAME, 1024, deviceName, NULL);

// Print the device name


printf("Device %d: %s\n", j, deviceName);
}

// Free the memory allocated for the devices


free(devices);
}

// Free the memory allocated for the platforms


free(platforms);

return 0;
}

Let's break down the code:


1. We include the OpenCL header file and the standard I/O header file for
`printf`.
2. In the `main` function, we first get the number of OpenCL platforms
available on the system.
3. We allocate memory for the platform IDs.
4. We get the platform IDs.
5. We loop over each platform and for each platform, we get the number of
devices.
6. We allocate memory for the device IDs.
7. We get the device IDs.
8. We loop over each device and for each device, we get the device name.
9. We print the device name using `printf`.
10. We free the memory allocated for the devices and the platforms. This is
important to prevent memory leaks.
This program will print the names of all OpenCL devices available on the
system.

OceanofPDF.com
3.15.2 ADDING NUMBERS
WITH OPENCL
In this chapter, we will walk through a simple OpenCL program written in
C that adds two numbers and prints the result using the `printf` function.
This will help us understand the basic structure of an OpenCL program and
how to interact with the OpenCL API.

C:

// Include the OpenCL header file


#include <CL/cl.h>

// Include the standard I/O header file for printf


#include <stdio.h>

// Define the OpenCL kernel as a constant character string


const char *kernelSource =
"__kernel void addNum(__global float* a, __global float* b, __global float* c) {"
" int id = get_global_id(0);" // Get the global thread ID
" c[id] = a[id] + b[id];" // Perform the addition
"}";

int main() {
// Define the data
float a[1] = {5.0f};
float b[1] = {7.0f};
float c[1] = {0.0f};

// Get the platform ID


cl_platform_id platformId = NULL;
clGetPlatformIDs(1, &platformId, NULL);

// Get the device ID


cl_device_id deviceId = NULL;
clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, &deviceId, NULL);

// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);

// Create a command queue


cl_command_queue queue = clCreateCommandQueue(context, deviceId, 0, NULL);

// Create the memory buffers


cl_mem aMem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, sizeof(float), a, NULL);
cl_mem bMem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, sizeof(float), b, NULL);
cl_mem cMem = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(float), NULL,
NULL);

// Create the program


cl_program program = clCreateProgramWithSource(context, 1, (const char **)&kernelSource,
NULL, NULL);

// Build the program


clBuildProgram(program, 1, &deviceId, NULL, NULL, NULL);

// Create the kernel


cl_kernel kernel = clCreateKernel(program, "addNum", NULL);

// Set the kernel arguments


clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&aMem);
clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&bMem);
clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&cMem);

// Define the global and local work sizes


size_t globalWorkSize[1] = {1};
size_t localWorkSize[1] = {1};

// Enqueue the kernel for execution


clEnqueueNDRangeKernel(queue, kernel, 1, NULL, globalWorkSize, localWorkSize, 0, NULL,
NULL);

// Read the result back into the c array


clEnqueueReadBuffer(queue, cMem, CL_TRUE, 0, sizeof(float), c, 0, NULL, NULL);

// Print the result


printf("The result is: %f\n", c[0]);

// Clean up
clReleaseMemObject(aMem);
clReleaseMemObject(bMem);
clReleaseMemObject(cMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);

return 0;
}

Let's break down the code:


1. We include the OpenCL header file and the standard I/O header file for
`printf`.
2. We define the OpenCL kernel as a constant character string. The kernel is
a function that is executed on the OpenCL device. It takes in two inputs, `a`
and `b`, and writes the result to `c`.
3. In the `main` function, we first define our data, which are arrays `a`, `b`,
and `c` with one element each.
4. We then get the platform ID and the device ID. The platform is the host
plus a collection of devices managed by the OpenCL framework. The
device is where the kernels are executed.
5. We create a context, which is an environment for the OpenCL program to
execute in.
6. We create a command queue, which is used to queue commands to be
executed on the device.
7. We create memory buffers for `a`, `b`, and `c`. These buffers will be used
to store the data on the device.
8. We create and build the OpenCL program. The program is created from
the source code of the kernel.
9. We create the kernel from the program.
10. We set the arguments for the kernel. The arguments are the memory
buffers we created earlier.
11. We define the global and local work sizes. The global work size is the
total number of work items (threads) that execute the kernel. The local work
size is the number of work items in a work group.
12. We enqueue the kernel for execution. This means we are adding it to the
command queue for it to be executed on the device.
13. We read the result back into the `c` array. This is done using a read
buffer command, which reads data from the buffer on the device to the host
memory.
14. We print the result using `printf`.
15. Finally, we clean up by releasing the memory objects, program, kernel,
command queue, and context. This is important to free up resources.

OceanofPDF.com
3.15.3 VECTOR ADDITION
WITH OPENCL
In this chapter, we will walk through a simple OpenCL program written in
C that performs vector addition. This is a fundamental operation in many
scientific and engineering applications and serves as a good starting point
for understanding how to use OpenCL for parallel computation.

C:

// Include the OpenCL header file


#include <CL/cl.h>

// Include the standard I/O header file for printf


#include <stdio.h>

// Define the OpenCL kernel as a constant character string


const char *kernelSource =
"__kernel void vecAdd(__global float* a, __global float* b, __global float* c, const unsigned
int n) {"
" int id = get_global_id(0);" // Get the global thread ID
" if (id < n) {" // Ensure we don't go out of bounds
" c[id] = a[id] + b[id];" // Perform the addition
" }"
"}";

int main() {
// Define the size of the vectors
unsigned int n = 1000000;

// Allocate memory for the vectors


float *a = (float *)malloc(sizeof(float) * n);
float *b = (float *)malloc(sizeof(float) * n);
float *c = (float *)malloc(sizeof(float) * n);

// Initialize the vectors


for (unsigned int i = 0; i < n; i++) {
a[i] = i;
b[i] = i;
}

// Get the platform ID


cl_platform_id platformId = NULL;
clGetPlatformIDs(1, &platformId, NULL);

// Get the device ID


cl_device_id deviceId = NULL;
clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, &deviceId, NULL);

// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);

// Create a command queue


cl_command_queue queue = clCreateCommandQueue(context, deviceId, 0, NULL);

// Create the memory buffers


cl_mem aMem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, sizeof(float) * n, a, NULL);
cl_mem bMem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, sizeof(float) * n, b, NULL);
cl_mem cMem = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(float) * n, NULL,
NULL);

// Create the program


cl_program program = clCreateProgramWithSource(context, 1, (const char **)&kernelSource,
NULL, NULL);

// Build the program


clBuildProgram(program, 1, &deviceId, NULL, NULL, NULL);
// Create the kernel
cl_kernel kernel = clCreateKernel(program, "vecAdd", NULL);

// Set the kernel arguments


clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&aMem);
clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&bMem);
clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&cMem);
clSetKernelArg(kernel, 3, sizeof(unsigned int), (void *)&n);

// Define the global work size


size_t globalWorkSize = n;

// Enqueue the kernel for execution


clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalWorkSize, NULL, 0, NULL, NULL);

// Read the result backinto the c array


clEnqueueReadBuffer(queue, cMem, CL_TRUE, 0, sizeof(float) * n, c, 0, NULL, NULL);

// Print the first 10 results


for (unsigned int i = 0; i < 10; i++) {
printf("c[%d] = %f\n", i, c[i]);
}

// Clean up
free(a);
free(b);
free(c);
clReleaseMemObject(aMem);
clReleaseMemObject(bMem);
clReleaseMemObject(cMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}

Let's break down the code:

1. We include the OpenCL header file and the standard I/O header
file for printf.
2. We define the OpenCL kernel as a constant character string. The
kernel is a function that is executed on the OpenCL device. It
takes in two input vectors, a and b, and an output vector c, and
adds the corresponding elements of a and b together, storing the
result in c.
3. In the main function, we first define the size of the vectors and
allocate memory for them. We then initialize the vectors with
some values.
4. We get the platform ID and the device ID, create a context and a
command queue, and create memory buffers for the vectors.
5. We create and build the OpenCL program from the source code
of the kernel, and create the kernel from the program.
6. We set the arguments for the kernel. The arguments are the
memory buffers we created earlier and the size of the vectors.
7. We define the global work size as the size of the vectors. This is
the total number of work items (threads) that will execute the
kernel.
8. We enqueue the kernel for execution. This means we are adding
it to the command queue for it to be executed on the device.
9. We read the result back into the c array. This is done using a read
buffer command, which reads data from the buffer on the device
to the host memory.
10. We print the first 10 results using printf.
11. Finally, we clean up by freeing the memory allocated for the
vectors and releasing the memory objects, program, kernel,
command queue, and context. This is important to free up
resources.
OceanofPDF.com
3.15.4 MATRIX
MULTIPLICATION WITH
OPENCL
In this chapter, we will walk through a simple OpenCL program written in
C that performs matrix multiplication. This is a fundamental operation in
many scientific and engineering applications and serves as a good starting
point for understanding how to use OpenCL for parallel computation.

C:

// Include the OpenCL header file


#include <CL/cl.h>

// Include the standard I/O header file for printf


#include <stdio.h>

// Define the OpenCL kernel as a constant character string


const char *kernelSource =
"__kernel void matMul(__global float* A, __global float* B, __global float* C, const unsigned
int N) {"
" int row = get_global_id(0);" // Get the row index
" int col = get_global_id(1);" // Get the column index
" float sum = 0.0f;" // Initialize the sum
" for (int i = 0; i < N; i++) {" // Loop over each element in the row/column
" sum += A[row * N + i] * B[i * N + col];" // Multiply and accumulate
" }"
" C[row * N + col] = sum;" // Write the result to the output matrix
"}";
int main() {
// Define the size of the matrices (N x N)
unsigned int N = 10000;

// Allocate memory for the matrices


float *A = (float *)malloc(sizeof(float) * N * N);
float *B = (float *)malloc(sizeof(float) * N * N);
float *C = (float *)malloc(sizeof(float) * N * N);

// Initialize the matrices


for (unsigned int i = 0; i < N * N; i++) {
A[i] = i;
B[i] = i;
}

// Get the platform ID


cl_platform_id platformId = NULL;
clGetPlatformIDs(1, &platformId, NULL);

// Get the device ID


cl_device_id deviceId = NULL;
clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, &deviceId, NULL);

// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);

// Create a command queue


cl_command_queue queue = clCreateCommandQueue(context, deviceId, 0, NULL);

// Create the memory buffers


cl_mem AMem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, sizeof(float) * N * N, A, NULL);
cl_mem BMem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, sizeof(float) * N * N, B, NULL);
cl_mem CMem = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(float) * N * N,
NULL, NULL);
// Create the program
cl_program program = clCreateProgramWithSource(context, 1, (const char **)&kernelSource,
NULL, NULL);

// Build the program


clBuildProgram(program, 1, &deviceId, NULL, NULL, NULL);

// Create the kernel


cl_kernel kernel = clCreateKernel(program, "matMul", NULL);

// Set the kernel arguments


clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&AMem);
clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&BMem);
clSetKernelArg(kernel, 2, sizeof(cl_mem), (void

*)&CMem);
clSetKernelArg(kernel, 3, sizeof(unsigned int), (void *)&N);

// Define the global work size


size_t globalWorkSize[2] = {N, N};

// Enqueue the kernel for execution


clEnqueueNDRangeKernel(queue, kernel, 2, NULL, globalWorkSize, NULL, 0, NULL, NULL);

// Read the result back into the C array


clEnqueueReadBuffer(queue, CMem, CL_TRUE, 0, sizeof(float) * N * N, C, 0, NULL, NULL);

// Print the first 10 results


for (unsigned int i = 0; i < 10; i++) {
printf("C[%d] = %f\n", i, C[i]);
}

// Clean up
free(A);
free(B);
free(C);
clReleaseMemObject(AMem);
clReleaseMemObject(BMem);
clReleaseMemObject(CMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);

return 0;
}

Let's break down the code:


1. We include the OpenCL header file and the standard I/O header file for
`printf`.
2. We define the OpenCL kernel as a constant character string. The kernel is
a function that is executed on the OpenCL device. It takes in two input
matrices, `A` and `B`, and an output matrix `C`, and multiplies `A` and `B`
together, storing the result in `C`.
3. In the `main` function, we first define the size of the matrices and
allocate memory for them. We then initialize the matrices with some values.
4. We get the platform ID and the device ID, create a context and a
command queue, and create memory buffers for the matrices.
5. We create and build the OpenCL program from the source code of the
kernel, and create the kernel from the program.
6. We set the arguments for the kernel. The arguments are the memory
buffers we created earlier and the size of the matrices.
7. We define the global work size as the size of the matrices. This is the
total number of work items (threads) that will execute the kernel.
8. We enqueue the kernel for execution. This means we are adding it to the
command queue for it to be executed on the device.
9. We read the result back into the `C` array. This is done using a read
buffer command, which reads data from the buffer on the device to the host
memory.
10. We print the first 10 results using `printf`.
11. Finally, we clean up by freeing the memory allocated for the matrices
and releasing the memory objects, program, kernel, command queue, and
context. This is important to free up resources.

OceanofPDF.com
3.15.5 MONTE CARLO
SIMULATION WITH
OPENCL
In this chapter, we will walk through a simple OpenCL program written in
C that performs a Monte Carlo simulation. Specifically, we will estimate the
value of Pi using the Monte Carlo method.

C:

// Include the OpenCL header file


#include <CL/cl.h>

// Include the standard I/O header file for printf


#include <stdio.h>

// Define the OpenCL kernel as a constant character string


const char *kernelSource =
"__kernel void monteCarloPi(__global uint* seed, __global uint* count, const uint iterations) {"
" int id = get_global_id(0);" // Get the global ID
" uint localCount = 0;" // Initialize the local count
" float x, y;" // Declare x and y coordinates
" for (int i = 0; i < iterations; i++) {" // Loop over the number of iterations
" seed[id] = seed[id] * 1103515245 + 12345;" // Generate a new seed
" x = (float)seed[id] / (float)UINT_MAX;" // Generate a random x coordinate
" seed[id] = seed[id] * 1103515245 + 12345;" // Generate a new seed
" y = (float)seed[id] / (float)UINT_MAX;" // Generate a random y coordinate
" if (x * x + y * y < 1.0f) localCount++;" // If the point is inside the circle, increment the count
" }"
" count[id] = localCount;" // Write the local count to the global count array
"}";

int main() {
// Define the number of work items and the number of iterations per work item
unsigned int numWorkItems = 1000;
unsigned int iterationsPerWorkItem = 1000000;

// Allocate memory for the seed and count arrays


unsigned int *seed = (unsigned int *)malloc(sizeof(unsigned int) * numWorkItems);
unsigned int *count = (unsigned int *)malloc(sizeof(unsigned int) * numWorkItems);

// Initialize the seed array with random values


for (unsigned int i = 0; i < numWorkItems; i++) {
seed[i] = rand();
}

// Get the platform ID


cl_platform_id platformId = NULL;
clGetPlatformIDs(1, &platformId, NULL);

// Get the device ID


cl_device_id deviceId = NULL;
clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, &deviceId, NULL);

// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);

// Create a command queue


cl_command_queue queue = clCreateCommandQueue(context, deviceId, 0, NULL);

// Create the memory buffers


cl_mem seedMem = clCreateBuffer(context, CL_MEM_READ_WRITE |
CL_MEM_COPY_HOST_PTR, sizeof(unsigned int) * numWorkItems, seed, NULL);
cl_mem countMem = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(unsigned int) *
numWorkItems, NULL, NULL);
// Create the program
cl_program program = clCreateProgramWithSource(context, 1, (const char **)&kernelSource,
NULL, NULL);

// Build the program


clBuildProgram(program, 1, &deviceId, NULL, NULL, NULL);

// Create the kernel


cl_kernel kernel = clCreateKernel(program, "monteCarloPi", NULL);

// Set the kernel arguments


clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&seedMem);
clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&countMem);
clSetKernelArg(kernel, 2, sizeof(unsigned int), (void *)&iterationsPerWorkItem);

// Define the global work size


size_t globalWorkSize = numWorkItems;

// Enqueue the kernel for execution


clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalWorkSize, NULL, 0, NULL, NULL);

// Read the result back into the count array


clEnqueueReadBuffer(queue, countMem, CL_TRUE, 0, sizeof(unsigned int) * numWorkItems,
count, 0, NULL, NULL);

// Calculate the estimated value of Pi


unsigned int total = 0;
for (unsigned int i = 0; i < numWorkItems; i++) {
total += count[i];
}
float piEstimate = 4.0f * (float)total / (float)(numWorkItems * iterationsPerWorkItem);

// Print the estimated value of Pi


printf("Estimated value of Pi: %f\n", piEstimate);

// Clean up
free(seed);
free(count);
clReleaseMemObject(seedMem);
clReleaseMemObject(countMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);

return 0;
}

Let's break down the code:


1. We include the OpenCL header file and the standard I/O header file for
`printf`.
2. We define the OpenCL kernel as a constant character string. The kernel is
a function that is executed on the OpenCL device. It takes in an input seed
array and an output count array, and performs a Monte Carlo simulation to
estimate the value of Pi.
3. In the `main` function, we first define the number of work items and the
number of iterations per work item, and allocate memory for the seed and
count arrays. We then initialize the seed array with random values.
4. We get the platform ID and the device ID, create a context and a
command queue, and create memory buffers for the seed and count arrays.
5. We create and build the OpenCL program from the source code of the
kernel, and create the kernel from the program.
6. We set the arguments for the kernel. The arguments are the memory
buffers we created earlier and the number of iterations per work item.
7. We define the global work size as the number of work items. This is the
total number of work items (threads) that will execute the kernel.
8. We enqueue the kernel for execution. This means we are adding it to the
command queue for it to be executed on the device.
9. We read the result back into the count array. This is done using a read
buffer command, which reads data from the buffer on the device to the host
memory.
10. We calculate the estimated value of Pi by summing up the counts and
dividing by the total number of iterations, and print the estimated value of
Pi using `printf`.
11. Finally, we clean up by freeing the memory allocated for the seed and
count arrays and releasing the memory objects, program, kernel, command
queue, and context. This is important to free up resources.

OceanofPDF.com
3.15.6 COMPLETE N-BODY
SIMULATION WITH
OPENCL
In this chapter, we will walk through a complete OpenCL program written
in C that performs an N-body simulation. An N-body simulation
numerically approximates the evolution of a system of bodies in which each
body continuously interacts with every other body. In our case, we'll
simulate gravitational attraction between bodies.
Please note that this is a simplified example and does not include
optimizations that would be necessary for larger simulations.

C:

// Include the OpenCL header file


#include <CL/cl.h>

// Include the standard I/O and standard lib header files


#include <stdio.h>
#include <stdlib.h>

typedef struct {
float x;
float y;
float z;
float w;
} float4;

// Define the OpenCL kernel as a constant character string


const char *kernelSource =
"__kernel void nbody(__global float4* pos, __global float4* vel, const float dt, const float
softeningSquared, const int numBodies) {"
" int i = get_global_id(0);" // Get the global ID
" float4 myPos = pos[i];" // Get the position of the current body
" float4 acc = (float4)(0.0f, 0.0f, 0.0f, 0.0f);" // Initialize the acceleration
" for (int j = 0; j < numBodies; j++) {" // Loop over all bodies
" float4 pos2 = pos[j];" // Get the position of the other body
" float4 r = pos2 - myPos;" // Calculate the displacement vector
" float distSqr = r.x * r.x + r.y * r.y + r.z * r.z + softeningSquared;" // Calculate the distance
squared
" float invDist = 1.0f / sqrt(distSqr);" // Calculate the inverse distance
" float invDistCube = invDist * invDist * invDist;" // Calculate the inverse distance cubed
" float s = pos2.w * invDistCube;" // Calculate the scale factor
" acc += s * r;" // Accumulate the acceleration
" }"
" float4 oldVel = vel[i];" // Get the velocity of the current body
" oldVel += acc * dt;" // Update the velocity
" myPos += oldVel * dt;" // Update the position
" pos[i] = myPos;" // Write the position back to the global array
" vel[i] = oldVel;" // Write the velocity back to the global array
"}";

int main() {
// Define the number of bodies and the time step
int numBodies = 1000;
float dt = 0.01f;
float softeningSquared = 0.00125f;

// Allocate memory for the position and velocity arrays


float4 *pos = (float4 *)malloc(sizeof(float4) * numBodies);
float4 *vel = (float4 *)malloc(sizeof(float4) * numBodies);

// Initialize the position and velocity arrays


for (int i = 0; i < numBodies; i++) {
pos[i] = (float4){(float)rand()/RAND_MAX, (float)rand()/RAND_MAX,
(float)rand()/RAND_MAX, 1.0f};
vel[i] = (float4){0.0f, 0.0f, 0.0f, 0.0f};
}

// Get the platform ID


cl_platform_id platformId = NULL;
clGetPlatformIDs(1, &platformId,

NULL);

// Get the device ID


cl_device_id deviceId = NULL;
clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, &deviceId, NULL);

// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);

// Create a command queue


cl_command_queue queue = clCreateCommandQueue(context, deviceId, 0, NULL);

// Create the memory buffers


cl_mem posMem = clCreateBuffer(context, CL_MEM_READ_WRITE |
CL_MEM_COPY_HOST_PTR, sizeof(float4) * numBodies, pos, NULL);
cl_mem velMem = clCreateBuffer(context, CL_MEM_READ_WRITE |
CL_MEM_COPY_HOST_PTR, sizeof(float4) * numBodies, vel, NULL);

// Create the program


cl_program program = clCreateProgramWithSource(context, 1, (const char **)&kernelSource,
NULL, NULL);

// Build the program


clBuildProgram(program, 1, &deviceId, NULL, NULL, NULL);

// Create the kernel


cl_kernel kernel = clCreateKernel(program, "nbody", NULL);
// Set the kernel arguments
clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&posMem);
clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&velMem);
clSetKernelArg(kernel, 2, sizeof(float), (void *)&dt);
clSetKernelArg(kernel, 3, sizeof(float), (void *)&softeningSquared);
clSetKernelArg(kernel, 4, sizeof(int), (void *)&numBodies);

// Define the global work size


size_t globalWorkSize = numBodies;

// Enqueue the kernel for execution


clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalWorkSize, NULL, 0, NULL, NULL);

// Read the result back into the pos and vel arrays
clEnqueueReadBuffer(queue, posMem, CL_TRUE, 0, sizeof(float4) * numBodies, pos, 0, NULL,
NULL);
clEnqueueReadBuffer(queue, velMem, CL_TRUE, 0, sizeof(float4) * numBodies, vel, 0, NULL,
NULL);

// Print the final positions and velocities


for (int i = 0; i < numBodies; i++) {
printf("Body %d position: (%f, %f, %f)\n", i, pos[i].x, pos[i].y, pos[i].z);
printf("Body %d velocity: (%f, %f, %f)\n", i, vel[i].x, vel[i].y, vel[i].z);
}

// Clean up
free(pos);
free(vel);
clReleaseMemObject(posMem);
clReleaseMemObject(velMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}

Let's break down the code:


1. We include the OpenCL header file and the standard I/O and standard lib
header files for `printf` and `rand`.
2. We define the OpenCL kernel as a constant character string. The kernel is
a function that is executed on the OpenCL device. It takes in an input
position array and an output velocity array, and performs an N-body
simulation.
3. In the `main` function, we first define the number of bodies and the time
step, and allocate memory for the position and velocity arrays. We then
initialize the position and velocity arrays with random positions and zero
velocities.
4. We get the platform ID and the device ID, create a context and a
command queue, and create memory buffers for the position and velocity
arrays.
5. We create and build the OpenCL program from the source code of the
kernel, and create the kernel from the program.
6. We set the arguments for the kernel. The arguments are the memory
buffers we created earlier, the time step, the softening factor, and the
number of bodies.
7. We define the global work size as the number of bodies. This is the total
number of work items (threads) that will execute the kernel.
8. We enqueue the kernel for execution. This means we are adding it to the
command queue for it to be executed on the device.
9. We read the result back into the position and velocity arrays. This is done
using a read buffer command, which reads data from the buffer on the
device to the host memory.
10. We print the final positions and velocities of the bodies using `printf`.
11. Finally, we clean up by freeing the memory allocated for the position
and velocity arrays and releasing the memory objects, program, kernel,
command queue, and context. This is important to free up resources.
Please note that this is a simplified example and does not include
optimizations that would be necessary for larger simulations. Also, the
initialization of the position and velocity arrays and the printing of the final
positions and velocities are not shown in this example. You would need to
add this code to create a complete N-body simulation.
In a real-world application, you would also need to loop over the kernel
execution to simulate the motion of the bodies over time. You would
typically do this inside a loop that increments the time step at each iteration.
The N-body problem is a classic problem in physics and is often used as a
benchmark for computational physics and graphics applications. It is a
computationally intensive problem that benefits greatly from the parallel
processing capabilities of GPUs.

OceanofPDF.com
3.15.7 PRIME NUMBERS
WITH OPENCL
In this chapter, we will walk through a complete OpenCL program written
in C that checks for prime numbers. A prime number is a natural number
greater than 1 that has no positive divisors other than 1 and itself.
Please note that this is a simplified example and does not include
optimizations that would be necessary for larger number ranges.

C:

// Include the OpenCL header file


#include <CL/cl.h>

// Include the standard I/O and standard lib header files


#include <stdio.h>
#include <stdlib.h>

// Define the OpenCL kernel as a constant character string


const char *kernelSource =
"__kernel void primeNumbers(__global int* input, __global int* output, const int count) {"
" int i = get_global_id(0);" // Get the global ID
" if (i < count) {"
" int number = input[i];"
" int isPrime = 1;" // Assume the number is prime
" if (number <= 1) {"
" isPrime = 0;" // Numbers less than or equal to 1 are not prime
" } else {"
" for (int j = 2; j * j <= number; j++) {"
" if (number % j == 0) {"
" isPrime = 0; break;" // If the number is divisible by any number other than 1 and itself,
it's not prime
" }"
" }"
" }"
" output[i] = isPrime;" // Write the result back to the global array
" }"
"}";

int main() {
// Define the count of numbers
int count = 100;

// Allocate memory for the input and output arrays


int *input = (int *)malloc(sizeof(int) * count);
int *output = (int *)malloc(sizeof(int) * count);

// Initialize the input array with numbers from 1 to count


for (int i = 0; i < count; i++) {
input[i] = i + 1;
}

// Get the platform ID


cl_platform_id platformId = NULL;
clGetPlatformIDs(1, &platformId, NULL);

// Get the device ID


cl_device_id deviceId = NULL;
clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, &deviceId, NULL);

// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);

// Create a command queue


cl_command_queue queue = clCreateCommandQueue(context, deviceId, 0, NULL);

// Create the memory buffers


cl_mem inputMem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, sizeof(int) * count, input, NULL);
cl_mem outputMem = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(int) * count,
NULL, NULL);

// Create the program


cl_program program = clCreateProgramWithSource(context, 1, (const char **)&kernelSource,
NULL, NULL);

// Build the program


clBuildProgram(program, 1, &deviceId, NULL, NULL, NULL);

// Create the kernel


cl_kernel kernel = clCreateKernel(program, "primeNumbers", NULL);

// Set the kernel arguments


clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&inputMem);
clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&outputMem);
clSetKernelArg(kernel, 2, sizeof(int), (void *)&count);

// Define the global work size


size_t globalWorkSize = count;

// Enqueue the kernel for execution


clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalWorkSize, NULL, 0, NULL, NULL);

// Read the result back into the output array


clEnqueueReadBuffer(queue, outputMem, CL_TRUE, 0, sizeof(int) * count, output, 0, NULL,
NULL);

// Print the prime numbers


for (int i = 0; i < count; i++) {
if (output[i] == 1) {
printf("%d is a prime number.\n", input[i]);
}
}
// Clean up
free(input);
free(output);
clReleaseMemObject(inputMem);
clReleaseMemObject(outputMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);

return 0;
}

Let's break down the code:


1. We include the OpenCL header file and the standard I/O and standard lib
header files for `printf` and `malloc`.
2. We define the OpenCL kernel as a constant character string. The kernel is
a function that is executed on the OpenCL device. It takes in an input array
of numbers and an output array, and checks if each number is prime.
3. In the `main` function, we first define the count of numbers and allocate
memory for the input and output arrays. We then initialize the input array
with numbers from 1 to count.
4. We get the platform ID and the device ID, create a context and a
command queue, and create memory buffers for the input and output arrays.
5. We create and build the OpenCL program from the source code of the
kernel, and create the kernel from the program.
6. We set the arguments for the kernel. The arguments are the memory
buffers we created earlier and the count of numbers.
7. We define the global work size as the count of numbers. This is the total
number of work items (threads) that will execute the kernel.
8. We enqueue the kernel for execution. This means we are adding it to the
command queue for it to be executed on the device.
9. We read the result back into the output array. This is done using a read
buffer command, which reads data from the buffer on the device to the host
memory.
10. We print the prime numbers using `printf`.
11. Finally, we clean up by freeing the memory allocated for the input and
output arrays and releasing the memory objects, program, kernel, command
queue, and context. This is important to free up resources.
Please note that this is a simplified example and does not include
optimizations that would be necessary for larger number ranges. Also, the
printing of the prime numbers is done on the host side after the result is read
back from the device. In a real-world application, you might want to handle
the results directly on the device to avoid the overhead of transferring data
back to the host.

OceanofPDF.com
3.15.8 IMAGE
PROCESSING WITH
OPENCL
In this chapter, we will demonstrate how to perform a simple graphics
operation using OpenCL in the C language. The task at hand will be to take
an image, invert its colors using OpenCL, and write the result to a new
image file.

We will be using the STB Image Library for loading and saving images in
our application. To accomplish this, you need to include the "stb_image.h"
and "stb_image_write.h" headers in your project. These are header-only
libraries that you can download from the [STB library GitHub page]
(https://github.com/nothings/stb).

The Code
Here is the complete C code that performs this operation:

C:

#include <stdio.h>
#include <stdlib.h>
#include <CL/cl.h>
#include <math.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
// OpenCL kernel which invert the image colors
const char *source =
"__kernel void invert( \n"
" __global unsigned char* input, \n"
" __global unsigned char* output, \n"
" const unsigned int count) \n"
"{ \n"
" int i = get_global_id(0); \n"
" if (i < count) \n"
" output[i] = 255 - input[i]; \n"
"} \n";

int main() {
// load image
int width, height, bpp;
unsigned char* image = stbi_load("input.png", &width, &height, &bpp, 3);

if(image == NULL) {
printf("Error in loading the image\n");
exit(1);
}

size_t img_size = width * height * bpp;

// setup device, context, command queue...


cl_platform_id platform_id;
cl_device_id device_id;
cl_uint ret_num_devices;
cl_uint ret_num_platforms;
cl_int ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
ret = clGetDeviceIDs( platform_id, CL_DEVICE_TYPE_DEFAULT, 1, &device_id,
&ret_num_devices);

cl_context context = clCreateContext( NULL, 1, &device_id, NULL, NULL, &ret);


cl_command_queue command_queue = clCreateCommandQueue(context, device_id, 0, &ret);
// create memory buffer
cl_mem input_mem = clCreateBuffer(context, CL_MEM_READ_ONLY |
CL_MEM_COPY_HOST_PTR, img_size, image, &ret);
unsigned char* output = malloc(sizeof(unsigned char) * img_size);
cl_mem output_mem = clCreateBuffer(context, CL_MEM_WRITE_ONLY, img_size, NULL,
&ret);

// create kernel program from source


cl_program program = clCreateProgramWithSource(context, 1, (const char **)&source, NULL,
&ret);
ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
cl_kernel kernel = clCreateKernel(program, "invert", &ret);

// set kernel arguments


ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&input_mem);
ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&output_mem);
unsigned int count = img_size;
ret = clSetKernelArg(kernel, 2, sizeof(unsigned int), (void *)&count);

// execute the kernel


size_t global_item_size = img_size;
size_t local_item_size = 1;
ret = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, &global_item_size,
&local_item_size, 0, NULL, NULL);

// transfer result back to host


ret = clEnqueueReadBuffer(command_queue, output_mem, CL_TRUE, 0, img_size, output, 0,
NULL, NULL);

// cleanup
ret = clFlush(command_queue);
ret = clFinish(command_queue);
ret = clReleaseKernel(kernel);
ret = clReleaseProgram(program);
ret = clReleaseMemObject(input_mem);
ret = clReleaseMemObject(output_mem);
ret = clReleaseCommandQueue(command_queue);
ret = clReleaseContext(context);

// save the output image


stbi_write_png("output.png", width, height, bpp, output, bpp * width);

free(output);
stbi_image_free(image);

return 0;
}

This program first loads an image "input.png" and gets its width, height,
and bytes-per-pixel (bpp). Then, it sets up the OpenCL platform and device,
creates an OpenCL context, and a command queue.
It creates memory buffers in the device for the input and output images. It
then creates an OpenCL program from a source string, which defines a
kernel that inverts the colors of the image. This program is built and the
kernel is created from the program.
The kernel arguments are set to the input and output buffers and the size of
the image in bytes. The kernel is then enqueued to the command queue,
along with the number of work items, which is equal to the size of the
image in bytes.
After the kernel has finished execution, the result is read back into the host
memory from the output buffer. Finally, the program cleans up by releasing
the kernel, program, memory buffers, command queue, and context, and
writes the output image to "output.png".
This simple graphics operation demonstrates the power and flexibility of
OpenCL for image processing tasks. In the following chapters, we will
explore more advanced techniques and use cases of OpenCL in graphics
programming.

OceanofPDF.com
3.16 PARALLEL
COMPUTING WITH CUDA
FROM NVIDIA
CUDA, which stands for Compute Unified Device Architecture, is a
parallel computing platform and application programming interface (API)
model created by NVIDIA. It allows software developers to use a CUDA-
enabled graphics processing unit (GPU) for general purpose processing, an
approach termed GPGPU (General-Purpose computing on Graphics
Processing Units).

A Brief History of CUDA


CUDA was first introduced by NVIDIA in 2006 with the release of the
GeForce 8800 GTX, the first GPU to support CUDA. The introduction of
CUDA marked a significant shift in the way developers could interact with
GPUs. Prior to CUDA, GPUs were largely seen as rendering engines for
graphics display. CUDA allowed developers to leverage the parallel
processing power of NVIDIA GPUs for general computing tasks, not just
graphics rendering.
Over the years, NVIDIA has continued to develop and improve CUDA,
adding new features and improving performance with each new version.
Today, CUDA is used by millions of developers around the world and is a
key component of many high-performance computing (HPC) systems.

The Goal of CUDA


The primary goal of CUDA is to provide a platform that allows developers
to take advantage of the parallel processing power of NVIDIA GPUs. By
doing so, CUDA aims to significantly increase computational performance
by harnessing the power of the GPU.
GPUs are particularly well-suited to perform operations on large blocks of
data in parallel, making them ideal for a wide range of computationally
intensive tasks. CUDA provides a C-like programming model that makes it
easier for developers to write software that can perform these parallel
operations on a GPU.

How CUDA Achieves Its Goal


CUDA achieves its goal by providing a comprehensive toolkit for
developers that includes a compiler, libraries, and debugging and
optimization tools. The CUDA programming model allows developers to
write code that is executed across many parallel threads. This code is run on
the GPU, which can handle thousands of threads simultaneously, leading to
significant performance improvements for parallelizable tasks.
CUDA also provides a memory hierarchy that allows developers to manage
data locality and optimize memory access patterns, further enhancing
performance.

The Future of CUDA


As we look to the future, CUDA is poised to continue playing a critical role
in the field of high-performance computing. NVIDIA continues to innovate
and add new features to CUDA, making it easier for developers to harness
the power of their GPUs.
Moreover, as the demand for computational power continues to grow -
driven by developments in fields like artificial intelligence, machine
learning, and data science - the importance of efficient parallel computing
frameworks like CUDA is likely to grow as well.
In conclusion, CUDA represents a significant advancement in the field of
parallel computing. By providing a platform that allows developers to easily
leverage the power of GPUs, CUDA has opened up new possibilities for
high-performance computing and has set the stage for the next generation of
computational innovations.

OceanofPDF.com
3.16.1 ADDING NUMBERS
USING CUDA
CUDA (Compute Unified Device Architecture) is a parallel computing
platform and application programming interface (API) model created by
NVIDIA. In this chapter, we'll use CUDA to perform simple addition of two
numbers. We'll then use the `printf` method in C to print the result.

Step1: Declare the headers

C:

#include <stdio.h>
#include <assert.h>
#include <cuda.h>
#include <cuda_runtime.h>

Step 2: Define the CUDA Kernel


A kernel is a function that is run on the GPU. In our case, it will perform the
actual addition.

C:

// CUDA Kernel function to add the elements of two arrays on the GPU
__global__ void add(int n, float *x, float *y)
{
// calculate the unique thread index within the grid
int index = threadIdx.x;
int stride = blockDim.x;

// perform the addition if the index is less than n


for (int i = index; i < n; i += stride)
y[i] = x[i] + y[i];
}

In this kernel, `threadIdx` and `blockDim` are built-in variables provided by


CUDA, and they contain the thread index within the block and the block
dimensions, respectively. `stride` is used to ensure that the CUDA kernel
can correctly parallelize the computation over the CUDA threads.

Step 3: Write the Main Function


The main function is the driver of the code, and it runs on the CPU. Here is
the complete main function:

C:

__global__ void add(int n, float *x, float *y);

int main(void)
{
// set the size of the array (1 in this case)
int N = 1;

float *x, *y;

// Allocate Unified Memory – accessible from CPU or GPU


cudaMallocManaged(&x, N*sizeof(float));
cudaMallocManaged(&y, N*sizeof(float));
// initialize x and y arrays on the host
x[0] = 2.0f;
y[0] = 5.0f;

// Run kernel on 1M elements on the GPU


add<<<1, 1>>>(N, x, y);

// Wait for GPU to finish before accessing on host


cudaDeviceSynchronize();

// Print the result


printf("Result: %f\n", y[0]);

// Free memory
cudaFree(x);
cudaFree(y);

return 0;
}

The main function starts by setting the size of the array (1 in this case) and
allocating Unified Memory for `x` and `y`, which can be accessed either by
the CPU or the GPU.
Next, it initializes `x` and `y` on the host, and then runs the kernel `add` on
the GPU with `N` elements.
Once the kernel has finished running, it uses `cudaDeviceSynchronize` to
wait for the GPU to finish before accessing the result on the host.
Finally, it prints the result, frees the allocated memory with `cudaFree`, and
then returns 0 to indicate successful completion.
This simple example demonstrates how CUDA can be used to perform
computations on the GPU and then transfer the results back to the CPU.
This is a fundamental aspect of CUDA programming and forms the basis of
more complex CUDA applications.

OceanofPDF.com
3.16.2 PRIME NUMBERS
USING CUDA
In this chapter, we will walk through a complete CUDA program written in
C that checks for prime numbers. A prime number is a natural number
greater than 1 that has no positive divisors other than 1 and itself.
Please note that this is a simplified example and does not include
optimizations that would be necessary for larger number ranges.

C:

// Include the CUDA runtime header


#include <cuda_runtime.h>

// Include the standard I/O and standard lib header files


#include <stdio.h>
#include <stdlib.h>

// Define the CUDA kernel


__global__ void primeNumbers(int* input, int* output, int count) {
int i = blockIdx.x * blockDim.x + threadIdx.x; // Calculate the global index
if (i < count) {
int number = input[i];
int isPrime = 1; // Assume the number is prime
if (number <= 1) {
isPrime = 0; // Numbers less than or equal to 1 are not prime
} else {
for (int j = 2; j * j <= number; j++) {
if (number % j == 0) {
isPrime = 0; // If the number is divisible by any number other than 1 and itself, it's not
prime
break;
}
}
}
output[i] = isPrime; // Write the result back to the global array
}
}

int main() {
// Define the count of numbers
int count = 100;

// Allocate memory for the input and output arrays


int *input = (int *)malloc(sizeof(int) * count);
int *output = (int *)malloc(sizeof(int) * count);

// Initialize the input array with numbers from 1 to count


for (int i = 0; i < count; i++) {
input[i] = i + 1;
}

// Allocate device memory for the input and output arrays


int *d_input, *d_output;
cudaMalloc((void **)&d_input, sizeof(int) * count);
cudaMalloc((void **)&d_output, sizeof(int) * count);

// Copy the input array from host to device


cudaMemcpy(d_input, input, sizeof(int) * count, cudaMemcpyHostToDevice);

// Define the execution configuration


dim3 blockDim(256); // Number of threads per block
dim3 gridDim((count + blockDim.x - 1) / blockDim.x); // Number of blocks in the grid

// Launch the kernel


primeNumbers<<<gridDim, blockDim>>>(d_input, d_output, count);
// Copy the output array from device to host
cudaMemcpy(output, d_output, sizeof(int) * count, cudaMemcpyDeviceToHost);

// Print the prime numbers


for (int i = 0; i < count; i++) {
if (output[i] == 1) {
printf("%d is a prime number.\n", input[i]);
}
}

// Free the device memory


cudaFree(d_input);
cudaFree(d_output);

// Free the host memory


free(input);
free(output);

return 0;
}

Let's break down the code:


1. We include the CUDA runtime header and the standard I/O and standard
lib header files for `printf` and `malloc`.
2. We define the CUDA kernel. The kernel is a function that is executed on
the CUDA device. It takes in an input array of numbers and an output array,
and checks if each number is prime.
3. In the `main` function, we first define the count of numbers and allocate
memory for the input and output arrays. We then initialize the input array
with numbers from 1 to count.
4. We allocate device memory for the input and output arrays and copy the
input array from host to device.
5. We define the execution configuration. The `blockDim` is the number of
threads per block, and the `gridDim` is the number of blocks in the grid.
The number of threads per block is typically a multiple of the warp size (32
threads in CUDA), and the number of blocks is calculated to cover all the
data.
6. We launch the kernel. The `<<<gridDim, blockDim>>>` syntax is used
to specify the execution configuration.
7. We copy the output array from device to host. This is done using a
cudaMemcpy function, which copies data between the host and the device.
8. We print the prime numbers using `printf`.
9. Finally, we free the device and host memory. This is important to prevent
memory leaks.
Please note that this is a simplified example and does not include error
checking or optimizations that would be necessary for larger number
ranges. Also, the printing of the prime numbers is done on the host side
after the result is copied back from the device. In a real-world application,
you might want to handle the results directly on the device to avoid the
overhead of transferring data back to the host.

OceanofPDF.com
3.16.3 MATRIX
MULTIPLICATION USING
CUDA
CUDA (Compute Unified Device Architecture) is a parallel computing
platform and application programming interface model created by NVIDIA.
It allows software developers to use a CUDA-enabled graphics processing
unit (GPU) for general purpose processing. In this chapter, we will
demonstrate how to use CUDA to perform matrix multiplication in C.

C:

#include <stdio.h>
#include <assert.h>
#include <cuda.h>
#include <cuda_runtime.h>

__global__ void matrixMulCUDA(float *C, float *A, float *B, int N)


{
int idx = threadIdx.x;
int idy = threadIdx.y;
float sum = 0;

for (int i = 0; i < N; i++)


sum += A[idy*N + i] * B[i*N + idx];

C[idy*N + idx] = sum;


}

__global__ void matrixMulCUDA(float *C, float *A, float *B, int N);
int main(void)
{
int N = 1 << 5; // define size of the matrix, 32 x 32
float *A, *B, *C;
float *d_A, *d_B, *d_C;

// Allocate memory on the CPU (host)


A = (float *)malloc(N*N*sizeof(float));
B = (float *)malloc(N*N*sizeof(float));
C = (float *)malloc(N*N*sizeof(float));

// Initialize matrices A and B


for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
{
A[i * N + j] = (float) rand() / RAND_MAX;
B[i * N + j] = (float) rand() / RAND_MAX;
}

// Allocate memory on the GPU (device)


cudaMalloc((void **)&d_A, N*N*sizeof(float));
cudaMalloc((void **)&d_B, N*N*sizeof(float));
cudaMalloc((void **)&d_C, N*N*sizeof(float));

// Copy matrices A and B from the host (CPU) to the device (GPU)
cudaMemcpy(d_A, A, N*N*sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(d_B, B, N*N*sizeof(float), cudaMemcpyHostToDevice);

// Define the number of blocks and threads/block


dim3 threadsPerBlock(N, N);
dim3 numBlocks(1, 1);

matrixMulCUDA<<<numBlocks, threadsPerBlock>>>(d_C, d_A, d_B, N);

// Wait for GPU to finish before accessing on host


cudaDeviceSynchronize();
// Copy the result back to the host (CPU)
cudaMemcpy(C, d_C, N*N*sizeof(float), cudaMemcpyDeviceToHost);

// Print the result matrix C


for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
printf("%f ", C[i*N + j]);
printf("\n");
}

// Free device memory


cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);

// Free host memory


free(A);
free(B);
free(C);

return 0;
}

The host code starts by allocating memory on the host for matrices A, B,
and C using the `malloc` function, and initializes matrices A and B.
Then, it allocates memory on the GPU for the device copies of the matrices,
`d_A`, `d_B`, and `d_C`, using the `cudaMalloc` function. It then copies
matrices A and B from the host to the device using `cudaMemcpy`.
Next, it defines the block size (`threadsPerBlock`) and the grid size
(`numBlocks`), and launches the CUDA kernel with the `<<<numBlocks,
threadsPerBlock>>>` syntax.
After the kernel finishes execution, it uses `cudaDeviceSynchronize` to
ensure that the device has finished all executions before proceeding. It then
copies the result matrix C back from the device to the host, prints the
elements of the matrix, and finally, frees all the allocated memory.
This program performs matrix multiplication on the GPU and is a simple
example of how CUDA can be used for high-performance computation in
C. The CUDA programming model allows for easy parallelization of code,
and this program takes advantage of this by performing computations on
many threads simultaneously. This can significantly speed up code
execution for large matrices.

OceanofPDF.com
3.16.4 IMAGE
PROCESSING USING
CUDA
In this chapter, we will show how to perform a simple graphics operation
using NVIDIA's CUDA in the C language. The task is to take an image,
invert its colors using CUDA, and save the result to a new image file.
Similar to the OpenCL chapter, we will be using the STB Image Library for
loading and saving images. The "stb_image.h" and "stb_image_write.h"
header files should be included in your project.

The Code
Here is the complete C code that performs the inversion operation using
CUDA:

C:

#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

// CUDA kernel to invert the image colors


__global__ void invert(unsigned char* input, unsigned char* output, int img_size) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < img_size) {
output[i] = 255 - input[i];
}
}

int main() {
// load image
int width, height, bpp;
unsigned char* image = stbi_load("input.png", &width, &height, &bpp, 3);

if(image == NULL) {
printf("Error in loading the image\n");
exit(1);
}

size_t img_size = width * height * bpp;

// allocate device memory


unsigned char *d_input, *d_output;
cudaMalloc((void**)&d_input, img_size);
cudaMalloc((void**)&d_output, img_size);

// copy input image to device


cudaMemcpy(d_input, image, img_size, cudaMemcpyHostToDevice);

// determine grid and block size


int blockSize = 1024;
int numBlocks = (img_size + blockSize - 1) / blockSize;

// launch the kernel


invert<<<numBlocks, blockSize>>>(d_input, d_output, img_size);

// copy output image back to host


unsigned char* output = (unsigned char*)malloc(img_size);
cudaMemcpy(output, d_output, img_size, cudaMemcpyDeviceToHost);
// cleanup
cudaFree(d_input);
cudaFree(d_output);

// save the output image


stbi_write_png("output.png", width, height, bpp, output, bpp * width);

free(output);
stbi_image_free(image);

return 0;
}

This program starts by loading an image from the file "input.png" and
obtaining its dimensions and color depth (bytes per pixel). If the image
cannot be loaded, it outputs an error message and terminates.
Next, it allocates space in the device memory for the input and output
images. The input image is then copied to the device memory.
The program determines the number of CUDA blocks needed to process the
entire image, with each block processing up to 1024 pixels (this number can
be adjusted depending on the device capabilities).
The `invert` CUDA kernel is then launched. This kernel computes the index
of the pixel it needs to process based on its block and thread indices. If this
index is within the bounds of the image, it inverts the color of the pixel.
After the kernel execution, the output image is copied back to the host
memory. The device memory is freed, and the output image is written to the
file "output.png".
This simple graphics operation demonstrates the power and flexibility of
CUDA for image processing tasks. In the following chapters, we will delve
deeper into more advanced techniques and use cases of CUDA in graphics
programming.

OceanofPDF.com
3.17 CUDA VS OPENCL
OpenCL and CUDA are both frameworks for programming GPUs, but they
have some key differences:
1. Vendor Support:
- CUDA is a proprietary framework developed by NVIDIA. It's
specifically designed for NVIDIA GPUs. This means that if you write a
program in CUDA, it will only run on NVIDIA hardware.
- OpenCL (Open Computing Language) is an open standard managed by
the Khronos Group. It's designed to work across different types of
hardware, including GPUs from different manufacturers (like NVIDIA,
AMD, and Intel), CPUs, and even other types of processors like Digital
Signal Processors (DSPs) and Field-Programmable Gate Arrays (FPGAs).
2. Ease of Use:
- CUDA is often considered easier to use and learn. It has a rich
ecosystem of libraries and tools, and its syntax is very similar to C/C++,
which makes it more accessible for developers familiar with these
languages.
- OpenCL, on the other hand, can be more complex due to its generality.
It needs to support a wide range of hardware, which can make the
programming model more complicated.
3. Performance:
- In terms of raw performance, both CUDA and OpenCL can deliver
similar results. The performance largely depends on how well the code is
optimized.
- However, since CUDA is specifically designed for NVIDIA hardware,
developers might be able to squeeze out a bit more performance on
NVIDIA GPUs by using some CUDA-specific features.
4. Portability:
- OpenCL code is portable across different hardware platforms, which is
a big advantage if you want your code to run on different types of devices.
- CUDA code, being proprietary to NVIDIA, is not portable and will
only run on NVIDIA GPUs.
5. Community and Support:
- CUDA, being older and more established, has a larger user community
and more extensive resources available online. NVIDIA also provides good
support and documentation for CUDA.
- OpenCL, while it also has a growing community, doesn't have as many
resources available. However, it's supported by multiple vendors, which can
be an advantage in certain situations.
In summary, the choice between OpenCL and CUDA often comes down to
your specific needs. If you're targeting NVIDIA hardware and want an
easier learning curve, CUDA might be the better choice. If you need your
code to be portable across different hardware platforms, then OpenCL
would be the way to go.

OceanofPDF.com
3.18 CHECKING
INTEGRITY OF DATA
Ensuring data integrity is crucial in many applications, as it ensures that
data remains unchanged and uncorrupted during storage, transmission, or
processing. In this chapter, we will explore different techniques for
checking data integrity using hash functions. We will discuss CRC (Cyclic
Redundancy Check), cryptographic hash functions, and digital signatures.
These techniques offer varying levels of security and are used in different
scenarios based on the requirements of the system.

CRC (Cyclic Redundancy Check)


CRC is a widely used technique for detecting errors or changes in data
during transmission or storage. It employs a mathematical algorithm to
generate a fixed-size checksum or hash value based on the input data. The
generated CRC is compared with the CRC calculated at the receiving end to
verify data integrity. CRC is efficient and commonly used in applications
where error detection is the primary concern, such as network
communication and data storage. However, it does not provide strong
security against intentional tampering.

Cryptographic Hash Functions


Cryptographic hash functions are specifically designed to provide data
integrity and security. They generate fixed-size hash values or digests based
on the input data, ensuring that any slight change in the input produces a
significantly different output. Cryptographic hash functions, such as MD5,
SHA-1, SHA-256, and SHA-3, are commonly used in applications like
digital signatures, password storage, and data verification. They are
computationally secure and resistant to tampering, making them suitable for
protecting against accidental or intentional data modification.
Digital Signatures
Digital signatures combine the data integrity provided by cryptographic
hash functions with the concept of public-key cryptography. In this process,
a hash of the data is created using a cryptographic hash function, and the
hash value is encrypted with the private key of the sender. The encrypted
hash, along with the data, forms the digital signature. The receiver can then
decrypt the signature using the sender's public key and verify the integrity
and authenticity of the data. Digital signatures provide strong guarantees of
data integrity, non-repudiation, and authentication, making them essential in
secure communications and document verification.

Conclusion:
Checking data integrity is vital for maintaining the reliability and security
of information. CRC, cryptographic hash functions, and digital signatures
are powerful techniques for detecting errors, ensuring data integrity, and
protecting against tampering. While CRC is commonly used for error
detection, cryptographic hash functions and digital signatures provide
stronger security measures. Choosing the appropriate technique depends on
the specific requirements and level of security needed for the application at
hand. Understanding these techniques enables us to safeguard data integrity
in various domains, ranging from data storage and transmission to secure
communication and verification processes.

OceanofPDF.com
3.18.1 CYCLIC
REDUNDANCY CHECK
OCEANOFPDF.COM
3.18.1.1 CYCLIC
REDUNDANCY CHECK
(CKSUM) IN PLAIN C
Cyclic Redundancy Check (CRC) is a method used in computing to ensure
the integrity of data as it is transmitted or stored. CRC generates a
checksum (a short, fixed-size binary sequence) for each block of data, and
this checksum is transmitted or stored with the data. The receiving system
generates a new checksum for the received data and compares it to the
received checksum. If the two checksums match, the data is assumed to be
error-free.
One way to implement CRC is by using the `cksum` command, which
computes a CRC checksum for an input stream. The algorithm operates on
binary data, treating it as a large polynomial in binary notation. The
particular implementation of `cksum` in this example uses a specific
polynomial, `0x04C11DB7`, defined by the bits of the polynomial number.
Let's take a closer look at the provided code:

C:

#include <stdio.h>
#include <stdint.h>

uint32_t crc_tab[256];
uint32_t crc;
uint32_t length = 0;

This initial block of code starts by defining a 256-element array `crc_tab`


that will hold precomputed CRC values, a `crc` variable that will hold the
ongoing CRC value, and a `length` variable that will keep track of the
length of the data processed.

C:

void init(void) {
uint32_t i, j, r;

for (i = 0; i < 256; i++) {


r = i << 24;
for (j = 0; j < 8; j++)
r = r & 0x80000000 ? (r << 1) ^ 0x04C11DB7 : r << 1;
crc_tab[i] = r;
}
}

The `init` function initializes the `crc_tab` array by filling it with CRC
values. Each byte-sized value is left-shifted to form a 32-bit value, which is
then used to generate the CRC value using a specific polynomial. This
process is performed eight times for each byte-sized value, simulating eight
shifts and XORs that would occur in processing a byte of data.

C:

void update(uint8_t *buf, size_t len) {


length += len;
while (len--) {
crc = (crc << 8) ^ crc_tab[((crc >> 24) ^ *buf++) & 0xFF];
}
}

The `update` function updates the CRC value and length of data processed.
It takes a buffer of data and its length as arguments. For each byte in the
buffer, it shifts the existing CRC value left by eight bits (making space for
the next byte), and then XORs it with the precomputed CRC value from
`crc_tab` that corresponds to the byte being processed. This is done for all
bytes in the buffer.

C:

void finalize(void) {
size_t i;

for (i = length; i > 0; i >>= 8)


update((uint8_t *)&i, 1);
crc = ~crc;
}

The `finalize` function finalizes the CRC value after all data has been
processed. This involves processing the length of the data itself as if it was
part of the data, and then inverting the bits of the final CRC value.

C:

int main(int argc, char *argv[]) {


FILE *fp;
uint8_t buf[BUFSIZ];
size_t n;

if (argc < 2) {
printf("Usage: %s filename\n", argv[0]);
return 1;
}

fp = fopen(argv[1], "rb");
if (!fp) {
perror("File opening failed");
return 1;
}

init();
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
update(buf, n);
finalize();
printf("%u %u %s\n", crc, length, argv[1]);

fclose(fp);
return 0;
}

The `main` function opens the file provided as a command line argument,
reads its content into a buffer, and then updates the CRC value using the
`update` function. This process is repeated until the entire file is processed.
Once done, it finalizes the CRC value with the `finalize` function and prints
the final CRC value, the length of the data, and the file name.
This implementation provides a low-overhead method to verify the integrity
of data. By precomputing CRC values, it can quickly process large amounts
of data. Furthermore, by including the length of the data in the CRC, it also
protects against attacks that involve rearranging the data.

OceanofPDF.com
3.18.1.2 CRC32 IN PLAIN C
Cyclic Redundancy Check (CRC) is a popular error-detection technique
used in digital networks and storage devices to ensure data integrity. One of
the variations of CRC is CRC32, which generates a 32-bit hash (checksum)
for a given data set. This technique is commonly used in networking
protocols such as Ethernet and in file formats like ZIP, due to its efficient
error detection capability.
This chapter presents a complete C code example for calculating the
CRC32 checksum of a file, similar to the `crc32` utility in many systems.

C:

i#include <stdio.h>
#include <stdint.h>

#define POLYNOMIAL 0xEDB88320

uint32_t crc_tab[256];
uint32_t crc;
uint32_t length = 0;

void init(void) {
uint32_t i, j, r;

for (i = 0; i < 256; i++) {


r = i << 24;
for (j = 0; j < 8; j++)
r = r & 0x80000000 ? (r << 1) ^ 0x04C11DB7 : r << 1;
crc_tab[i] = r;
}
}
void update(uint8_t *buf, size_t len) {
length += len;
while (len--) {
crc = (crc << 8) ^ crc_tab[((crc >> 24) ^ *buf++) & 0xFF];
}
}

void finalize(void) {
size_t i;

for (i = length; i > 0; i >>= 8)


update((uint8_t *)&i, 1);
crc = ~crc;
}

uint32_t crc_table[256];

void generate_table() {
uint32_t crc;
for (int i = 0; i < 256; i++) {
crc = i;
for (int j = 0; j < 8; j++) {
if (crc & 1)
crc = (crc >> 1) ^ POLYNOMIAL;
else
crc = crc >> 1;
}
crc_table[i] = crc;
}
}

uint32_t crc32(FILE *file) {


uint32_t crc = 0xFFFFFFFF;
unsigned char buffer[1024];
size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
for (size_t i = 0; i < bytesRead; i++)
crc = (crc >> 8) ^ crc_table[(crc & 0xFF) ^ buffer[i]];
}

return ~crc;
}

int main(int argc, char *argv[]) {


if (argc != 2) {
printf("Usage: %s <file>\n", argv[0]);
return 1;
}

FILE *file = fopen(argv[1], "rb");


if (!file) {
perror("Failed to open file");
return 1;
}

generate_table();
uint32_t crc = crc32(file);
printf("CRC32 Checksum: %08x\n", crc);

fclose(file);
return 0;
}

In this program, we first define a `POLYNOMIAL` constant that will be


used in generating a lookup table of CRC32 checksums. This is the
polynomial used by CRC32 (`0xEDB88320`).
The `generate_table` function fills the `crc_table` array with precalculated
checksums for all 256 possible byte values. This is done to speed up the
CRC calculation process, since we can simply look up the checksum in this
table instead of recalculating it for each byte of data.
The `crc32` function is where the actual checksum calculation happens. It
reads the file in chunks of 1024 bytes (or fewer, if there are less than 1024
bytes left in the file), and for each byte, it updates the CRC checksum using
the lookup table.
Finally, in the `main` function, we open the specified file, generate the
lookup table, calculate the checksum, and print it. If anything goes wrong
(like the file not being specified or failing to open), the program prints an
error message and exits with a status code of 1.
This code provides a basic implementation of the CRC32 checksum
calculation. Please note that it assumes the system uses little-endian byte
order; if you're using a big-endian system, you would need to modify the
code to correctly calculate the checksum.
`cksum` and `crc32` are both checksum algorithms used to verify the
integrity of data, but they work in slightly different ways and are used in
different contexts.

cksum
`cksum` is a command used in Unix-like operating systems that generates a
CRC (Cyclic Redundancy Check) checksum, which is used to detect errors
in data. It is based on polynomial division and treats the data as a large
polynomial in binary notation. `cksum` generates a 32-bit integer from an
input stream and is typically used to ensure that files or data blocks have
not been altered unintentionally.
The `cksum` command typically produces two outputs: a CRC checksum,
and the byte length of the input. This makes `cksum` particularly useful in
scenarios where you need to verify both the content and the length of data.
This also means that two data blocks with the same content but different
lengths will produce different `cksum` outputs.

crc32
`crc32`, on the other hand, is a specific implementation of the CRC method
that generates a 32-bit checksum, as its name suggests. It uses a specific
polynomial (represented by the hexadecimal number `0x04C11DB7`), and
is commonly used in digital networks, error detection, and file integrity
checks.
`crc32` is widely used in many protocols such as Ethernet and in file
formats such as PNG and ZIP. It is designed to be fast and efficient while
still providing a good level of error detection. Unlike `cksum`, `crc32` does
not include the length of the data in its calculation. Therefore, two data
blocks with the same content but different lengths can produce the same
`crc32` output.

When to use cksum vs crc32


Which one to use - `cksum` or `crc32` - depends on your specific needs:
1. If you want to verify both the contents and the length of a file or data
block, and you're working in a Unix-like environment, then `cksum` may be
the more appropriate tool.
2. If you're implementing a network protocol, or need to work with existing
protocols or file formats that use `crc32`, then you should use `crc32`.
3. If you're working in an environment where `crc32` is not available,
`cksum` may be a suitable alternative.
4. If performance is a concern, `crc32` is usually faster than `cksum`.
Remember that while both of these methods can detect common types of
errors, neither of them are suitable for protecting against intentional
tampering or forgery. For those scenarios, cryptographic hash functions or
digital signatures should be used.

OceanofPDF.com
3.18.2 CRYPTOGRAPHIC
HASH FUNCTIONS
Cryptographic hash functions play a critical role in ensuring data integrity
and security in various applications. They transform an input (or 'message')
into a fixed-size string of bytes, typically a message digest. A small change
in the message will (ideally) produce such a drastic change in the output
that the new hash value appears uncorrelated with the old hash value. Here,
we explore some of the most widely used cryptographic hash functions.

OceanofPDF.com
3.18.2.1 MD5 IN PLAIN C
The MD5 (Message Digest Algorithm 5) is a widely used cryptographic
hash function that produces a 128-bit (16-byte) hash value. It was designed
by Ronald Rivest in 1991 to replace an earlier hash function, MD4.
MD5 processes data in 512-bit blocks, dividing them into 16 words of 32
bits each. The output is a digest of 16 bytes representing the hash value.
MD5 was originally designed to be used as a cryptographic hash function
for ensuring data integrity. Its common uses included storing sensitive
information like passwords, checking data integrity in network protocols,
and creating digital signatures.
However, as early as the mid-1990s, vulnerabilities were discovered in the
algorithm that could lead to hash collisions - different inputs producing the
same hash output. This is a critical flaw in any cryptographic hash function,
as it allows for potential misrepresentation of data and security breaches.
By the early 2000s, these vulnerabilities were exploited to create different
inputs with the same MD5 hash, rendering MD5 unsuitable for further
security use.
Here is example of implementation:

C:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

// leftrotate function definition


#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))

// function prototypes
void to_bytes(uint32_t val, uint8_t *bytes);
uint32_t to_int32(const uint8_t *bytes);
void md5(uint8_t *initial_msg, size_t initial_len, uint8_t *digest);

// Constants are the integer part of the sines of integers (in radians) * 2^32.
const uint32_t k[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee ,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 ,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be ,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 ,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa ,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 ,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed ,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a ,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c ,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 ,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 ,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 ,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 ,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 ,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 ,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 };

// r specifies the per-round shift amounts


const uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};

void to_bytes(uint32_t val, uint8_t *bytes) {


bytes[0] = (uint8_t) val;
bytes[1] = (uint8_t) (val >> 8);
bytes[2] = (uint8_t) (val >> 16);
bytes[3] = (uint8_t) (val >> 24);
}
uint32_t to_int32(const uint8_t *bytes) {
return (uint32_t) bytes[0]
| ((uint32_t) bytes[1] << 8)
| ((uint32_t) bytes[2] << 16)
| ((uint32_t) bytes[3] << 24);
}

void md5(uint8_t *initial_msg, size_t initial_len, uint8_t *digest) {

// These vars will contain the hash


uint32_t h0, h1, h2, h3;

// Message (to prepare)


uint8_t *msg = NULL;

size_t new_len, offset;


uint32_t w[16];
uint32_t a, b, c, d, i, f, g, temp;

// Initialize variables - simple count in nibbles:


h0 = 0x67452301;
h1 = 0xefcdab89;
h2 = 0x98badcfe;
h3 = 0x10325476;

//Pre-processing:
//append "1" bit to message
//append "0" bits until message length in bits ≡ 448 (mod 512)
//append length mod (2^64) to message

for (new_len = initial_len + 1; new_len % (512/8) != 448/8; new_len++)


;

msg = (uint8_t*)malloc(new_len + 8);


memcpy(msg, initial_msg, initial_len);
msg[initial_len] = 0x80; // append the "1" bit; most significant bit is "first"
for (offset = initial_len + 1; offset < new_len; offset++)
msg[offset] = 0; // append "0" bits

// append the len in bits at the end of the buffer.


to_bytes(initial_len*8, msg + new_len);
// initial_len>>29 == initial_len*8>>32, but avoids overflow.
to_bytes(initial_len>>29, msg + new_len + 4);

// Process the message in successive 512-bit chunks:


//for each 512-bit chunk of message:
for(offset=0; offset<new_len; offset += (512/8)) {

// break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15


for (i = 0; i < 16; i++)
w[i] = to_int32(msg + offset + i*4);

// Initialize hash value for this chunk:


a = h0;
b = h1;
c = h2;
d = h3;

// Main loop:
for(i = 0; i<64; i++) {

if (i < 16) {
f = (b & c) | ((~b) & d);
g = i;
} else if (i < 32) {
f = (d & b) | ((~d) & c);
g = (5*i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3*i + 5) % 16; } else if (i < 64) {
f = c ^ (b | (~d));
g = (7*i) % 16;
}

temp = d;
d = c;
c = b;
b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]);
a = temp;

// Add this chunk's hash to result so far:


h0 += a;
h1 += b;
h2 += c;
h3 += d;

// cleanup
free(msg);

// var char digest[16] := h0 append h1 append h2 append h3 //(Output is in little-endian)


uint8_t *p;

p=(uint8_t *)&h0;
memcpy(digest, p, sizeof(uint32_t));
p=(uint8_t *)&h1;
memcpy(digest+4, p, sizeof(uint32_t));
p=(uint8_t *)&h2;
memcpy(digest+8, p, sizeof(uint32_t));
p=(uint8_t *)&h3;
memcpy(digest+12, p, sizeof(uint32_t));

} // end of md5 function


int main(int argc, char **argv) {
FILE *file;
uint8_t *buffer;
uint8_t result[16];
unsigned long fileLen;

// Check for correct argument count


if(argc != 2) {
printf("Usage: %s filename\n", argv[0]);
return 1;
}

// Open file
file = fopen(argv[1], "rb");
if (!file) {
printf("Unable to open file %s", argv[1]);
return 1;
}

// Get file length


fseek(file, 0, SEEK_END);
fileLen=ftell(file);
fseek(file, 0, SEEK_SET);

// Allocate memory
buffer=(uint8_t *)malloc(fileLen+1);
if (!buffer) {
fprintf(stderr, "Memory error!");
fclose(file);
return 1;
}

// Read file contents into buffer


fread(buffer, fileLen, 1, file);
fclose(file);
// compute the hash
md5(buffer, fileLen, result);

// print it
printf("Hash: ");
for (int i = 0; i < 16; i++)
printf("%2.2x", result[i]);
printf("\n");

free(buffer);
return 0;
}

OceanofPDF.com
3.18.2.2 SHA256 IN PLAIN
C
Secure Hash Algorithm 2 (SHA-2) is a set of cryptographic hash functions
designed by the National Security Agency (NSA) and published in 2001 by
the National Institute of Standards and Technology (NIST). SHA-2 includes
significant changes from its predecessor, SHA-1. The SHA-2 family
consists of six hash functions with digests that are 224, 256, 384, 512,
512/224, and 512/256 bits: SHA-224, SHA-256, SHA-384, SHA-512,
SHA-512/224, and SHA-512/256.
SHA-2 has become widely adopted in various security applications and
protocols, including TLS and SSL, PGP, SSH, IPsec, and Bitcoin. Of all the
functions in the SHA-2 family, SHA-256 is the most commonly used. It
provides adequate security for most applications and is required by some
standards and protocols.
However, because SHA-2 shares the same structure and mathematical
operations as SHA-1, experts consider that an attack on SHA-1's structure
might eventually lead to attacks on SHA-2.
Here is example of implementation:

C:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
uint32_t state[8];
uint8_t buffer[64];
size_t bufferLength;
uint64_t bitLength[2]; // to support messages up to 2^128 - 1 bits
} SHA256_CTX;

const uint32_t k[64] = {


0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5
ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19
bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f98
8da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x1429
2967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x927
22c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106
aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e
6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc6717
8f2
};

void sha256_transform(uint32_t state[8], const uint8_t data[64]) {


uint32_t a, b, c, d, e, f, g, h, i, t1, t2, m[64];

for (i = 0, a = 0; a < 16; ++a, i += 4)


m[a] = (data[i] << 24) | (data[i + 1] << 16) | (data[i + 2] << 8) | (data[i + 3]);
for ( ; a < 64; ++a)
m[a] = (m[a - 16] + ((m[a - 15] >> 7 | m[a - 15] << 25) ^ (m[a - 15] >> 18 | m[a - 15] << 14) ^
(m[a - 15] >> 3)) + m[a - 7] + ((m[a - 2] >> 17 | m[a - 2] << 15) ^ (m[a - 2] >> 19 | m[a - 2] << 13) ^
(m[a - 2] >> 10)));

a = state[0]; b = state[1]; c = state[2]; d = state[3];


e = state[4]; f = state[5]; g = state[6]; h = state[7];
for (i = 0; i < 64; ++i) {
t1 = h + ((e >> 6 | e << 26) ^ (e >> 11 | e << 21) ^ (e >> 25 | e << 7)) + ((e & f) ^ (~e & g)) +
k[i] + m[i];
t2 = ((a >> 2 | a << 30) ^ (a >> 13 | a << 19) ^ (a >> 22 | a << 10)) + ((a & b) ^ (a & c) ^ (b &
c));
h = g; g = f; f = e; e = d + t1; d = c; c = b; b = a; a = t1 + t2;
}

state[0] += a; state[1] += b; state[2] += c; state[3] += d;


state[4] += e; state[5] += f; state[6] += g; state[7] += h;
}

void sha256_init(SHA256_CTX *ctx) {


ctx->state[0] = 0x6a09e667; ctx->state[1] = 0xbb67ae85; ctx->state[2] = 0x3c6ef372; ctx-
>state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f; ctx->state[5] = 0x9b05688c; ctx->state[6] = 0x1f83d9ab; ctx-
>state[7] = 0x5be0cd19;
ctx->bufferLength = 0;
ctx->bitLength[0] = 0;
ctx->bitLength[1] = 0;
}

void sha256_update(SHA256_CTX *ctx, const void *data, size_t len) {


ctx->bitLength[0] += len << 3;
if (ctx->bitLength[0] < (len << 3))
ctx->bitLength[1]++;
ctx->bitLength[1] += len >> 29;
for (; len; --len, ++data) {
ctx->buffer[ctx->bufferLength++] = *(uint8_t*)data;
if (ctx->bufferLength == 64) {
sha256_transform(ctx->state, ctx->buffer);
ctx->bufferLength = 0;
}
}
}
void sha256_final(uint8_t hash[32], SHA256_CTX *ctx) {
ctx->buffer[ctx->bufferLength++] = 0x80; // Append '1' bit to message

// If the current message length is such that there isn't enough room
// left in the buffer to store the 64-bit message length, we pad the
// current block with zeros, process it, and start a new block.
if (ctx->bufferLength > 56) { // Not enough room for length (8 bytes), fill it with zeros and
process
while (ctx->bufferLength < 64)
ctx->buffer[ctx->bufferLength++] = 0x00;
sha256_transform(ctx->state, ctx->buffer);
ctx->bufferLength = 0;
}

while (ctx->bufferLength < 56) // Pad the rest of the block to leave 8 bytes at the end
ctx->buffer[ctx->bufferLength++] = 0x00;

// Append message length in bits and process


for (uint8_t i = 0; i < 8; i++) {
ctx->buffer[63 - i] = ctx->bitLength[0] >> (i * 8);
if(i < 2) { // only take the lower 16 bits of ctx->bitLength[1]
ctx->buffer[55 - i] = ctx->bitLength[1] >> ((i + 6) * 8);
}
}
sha256_transform(ctx->state, ctx->buffer);

// Write the final hash


for (uint8_t i = 0; i < 8; i++) {
hash[i * 4 + 0] = (uint8_t)(ctx->state[i] >> 24);
hash[i * 4 + 1] = (uint8_t)(ctx->state[i] >> 16);
hash[i * 4 + 2] = (uint8_t)(ctx->state[i] >> 8);
hash[i * 4 + 3] = (uint8_t)ctx->state[i];
}
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <file>\n", argv[0]);
return 1;
}

FILE *file = fopen(argv[1], "rb");


if (!file) {
perror("File opening failed");
return EXIT_FAILURE;
}

// Initialize SHA-256 context


SHA256_CTX ctx;
sha256_init(&ctx);

// Read the file and update the hash


char buffer[1024];
size_t bytesRead = 0;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
sha256_update(&ctx, buffer, bytesRead);
}

if (ferror(file)) {
perror("File read failed");
fclose(file);
return EXIT_FAILURE;
}

fclose(file);

// Compute the final hash and print it


uint8_t hash[32];
sha256_final(hash, &ctx);
for (size_t i = 0; i < sizeof(hash); ++i) {
printf("%02x", hash[i]);
}

putchar('\n');

return 0;
}

OceanofPDF.com
3.18.2.3 SHA256 WITH
OPENSSL
For the purposes of this chapter, we'll use the OpenSSL library, which
provides a rich set of features for hashing files. OpenSSL supports
numerous hashing algorithms like MD5, SHA-1, SHA-256, SHA-512 and
many others. We will be using SHA-256 for our demonstration.

In case you haven't installed OpenSSL, use the following command for
installation:

bash:

sudo apt-get install libssl-dev

Here is a simple example of how to use the OpenSSL library to hash a file.

C:

#include <stdio.h>
#include <openssl/sha.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int hash_file(char *filepath) {


unsigned char buffer[BUFFER_SIZE];
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;

int file = open(filepath, O_RDONLY);


if (file < 0) {
perror("Failed to open file");
return -1;
}

SHA256_Init(&sha256);
ssize_t bytesRead;

while ((bytesRead = read(file, buffer, BUFFER_SIZE)) > 0) {


SHA256_Update(&sha256, buffer, bytesRead);
}

if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}

SHA256_Final(hash, &sha256);
close(file);

// Print the hash in hexadecimal format


for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
printf("%02x", hash[i]);

return 0;
}

int main(int argc, char **argv) {


if (argc < 2) {
printf("Usage: %s <file>\n", argv[0]);
return -1;
}
hash_file(argv[1]);

return 0;
}

Let's break down this code to understand each part:


1. We start by including necessary header files. The OpenSSL header files
(`openssl/sha.h`) are required for the SHA-256 algorithm. We also include
the `fcntl.h` header file for file handling functions and macros.
2. We define `BUFFER_SIZE` to the value 1024. This value is used for
reading the file in chunks, which is crucial when dealing with large files
that can't be loaded into memory all at once.
3. The `hash_file` function is where the file hashing happens. We start by
opening the file with `open` function in read-only mode. If the file doesn't
exist or can't be opened, the function will return -1 and print an error
message.
4. We then initialize our SHA-256 context with `SHA256_Init`. This
prepares the context to be used in the hashing operations.
5. We start reading the file in a loop, chunk by chunk, with `read` function.
The `read` function returns the number of bytes read, which we store in
`bytesRead`.
6. For each chunk read, we update our SHA-256 context with
`SHA256_Update`. This function hashes the input data and adds it to the
existing hash in the context.
7. If we encounter a read error, we print an error message, close the file, and
return -1.
8. After all the data has been read and hashed, we finalize the hash with
`SHA256_Final`. This function processes the last block of data, performs
any necessary padding, and computes the final hash.
9. We then print out the hash in hexadecimal format. This is the common
format for viewing hashes.
10. In `main`, we check that a filename has been provided on the command
line. If not, we print a usage message and return -1.
11. If a filename is provided, we call `hash_file` to hash the file.
This code handles large files by reading them in small chunks, and it uses
OpenSSL to create a hash of the entire file.

OceanofPDF.com
3.18.2.4 SHA3-256 WITH
OPENSSL
In this chapter, we will discuss hashing a large file using the SHA3-256
algorithm provided by the OpenSSL library. SHA3-256 is a member of the
Secure Hash Algorithm 3 (SHA-3) family, which offers a higher level of
security compared to older versions.

The steps to install OpenSSL remain the same as outlined in the previous
chapter:

bash:

sudo apt-get install libssl-dev

The code to hash a large file using SHA3-256 is similar to the previous
example but we will need to replace `SHA256_CTX`, `SHA256_Init`,
`SHA256_Update` and `SHA256_Final` with `EVP_MD_CTX`,
`EVP_DigestInit`, `EVP_DigestUpdate` and `EVP_DigestFinal_ex`
respectively:

C:

#include <stdio.h>
#include <openssl/evp.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_SIZE 1024


int hash_file(char *filepath) {
unsigned char buffer[BUFFER_SIZE];
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
EVP_MD_CTX *mdctx;

const EVP_MD* md = EVP_sha3_256();

int file = open(filepath, O_RDONLY);


if (file < 0) {
perror("Failed to open file");
return -1;
}

if((mdctx = EVP_MD_CTX_new()) == NULL) {


perror("Failed to create a new EVP_MD_CTX");
return -1;
}

EVP_DigestInit(mdctx, md);
ssize_t bytesRead;

while ((bytesRead = read(file, buffer, BUFFER_SIZE)) > 0) {


EVP_DigestUpdate(mdctx, buffer, bytesRead);
}

if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}

EVP_DigestFinal_ex(mdctx, hash, &hash_len);


EVP_MD_CTX_free(mdctx);
close(file);
// Print the hash in hexadecimal format
for (int i = 0; i < hash_len; i++)
printf("%02x", hash[i]);

return 0;
}

int main(int argc, char **argv) {


if (argc < 2) {
printf("Usage: %s <file>\n", argv[0]);
return -1;
}

hash_file(argv[1]);

return 0;
}

Let's explain the changes from the previous code:


1. We include `openssl/evp.h` instead of `openssl/sha.h`. EVP is OpenSSL's
high-level interface to cryptographic functions. It provides a consistent
interface to various algorithms, including SHA3.
2. We replace `SHA256_CTX` with `EVP_MD_CTX`. This is the context
struct for OpenSSL's high-level hash functions.
3. We get the `EVP_MD` struct for SHA3-256 using `EVP_sha3_256()`.
This struct holds function pointers and other information needed by the
EVP functions.
4. We replace `SHA256_Init` with `EVP_DigestInit`. The `EVP_DigestInit`
function sets up `mdctx` to use the hash/digest method `md`.
5. We replace `SHA256_Update` with `EVP_DigestUpdate`. This function
hashes `bytesRead` bytes of data at `buffer` and adds it to `mdctx`.
6. We replace `SHA256_Final` with `EVP_DigestFinal_ex`. This function
retrieves the digest value from `mdctx` and stores it in `hash`.
7. We also added `EVP_MD_CTX_new` and `EVP_MD_CTX_free` to
allocate and free the context `mdctx`.
8. We changed the `hash` array size to `EVP_MAX_MD_SIZE`, because
the size of a SHA3-256 hash might not be the same as a SHA-256 hash.
With these changes, you can hash large files using SHA3-256 in C.

OceanofPDF.com
3.18.2.5 BLAKE2 WITH B2
BLAKE2 is a cryptographic hash function that is faster than MD5, SHA-1,
SHA-2, and SHA-3, yet is at least as secure as the latest standard SHA-3. In
this chapter, we will learn how to use the BLAKE2 hashing algorithm
provided by the libb2 library in C to hash a large file.
You can install libb2 from source or via a package manager. Here is the
command to install libb2 in Debian and Ubuntu based systems:

bash:
sudo apt-get install libb2-dev

The process of reading and hashing a large file using BLAKE2 is similar to
our previous examples, but with some key differences. The code is as
follows:

C:

#include <stdio.h>
#include <blake2.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int hash_file(char *filepath) {


unsigned char buffer[BUFFER_SIZE];
unsigned char hash[BLAKE2B_OUTBYTES];
blake2b_state S[1];
int file = open(filepath, O_RDONLY);
if (file < 0) {
perror("Failed to open file");
return -1;
}

blake2b_init(S, BLAKE2B_OUTBYTES);
ssize_t bytesRead;

while ((bytesRead = read(file, buffer, BUFFER_SIZE)) > 0) {


blake2b_update(S, buffer, bytesRead);
}

if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}

blake2b_final(S, hash, BLAKE2B_OUTBYTES);


close(file);

// Print the hash in hexadecimal format


for (int i = 0; i < BLAKE2B_OUTBYTES; i++)
printf("%02x", hash[i]);

return 0;
}

int main(int argc, char **argv) {


if (argc < 2) {
printf("Usage: %s <file>\n", argv[0]);
return -1;
}

hash_file(argv[1]);
return 0;
}

Now, let's explain the parts of the code:


1. We include `blake2.h` instead of the OpenSSL headers. This header file
contains the function prototypes and macros for the BLAKE2 algorithm as
implemented in the libb2 library.
2. The `hash` array and the argument to `blake2b_init` and `blake2b_final`
have been changed to `BLAKE2B_OUTBYTES`. This is the length of a
BLAKE2b hash output in bytes.
3. We replace `SHA256_CTX` and `EVP_MD_CTX` with `blake2b_state`.
This struct is the equivalent context for the BLAKE2b algorithm.
4. We use `blake2b_init`, `blake2b_update`, and `blake2b_final` for
initializing the BLAKE2b context, updating the context with data, and
finalizing the hash, respectively.
With this code, you can hash large files using the BLAKE2 algorithm in C.
It's a similar process to the SHA-256 and SHA3-256 examples, but using a
different library and hash algorithm.

OceanofPDF.com
3.18.2.6 WHIRLPOOL WITH
OPENSSL
Whirlpool is a cryptographic hash function that outputs a 512-bit hash
value. It was designed by Vincent Rijmen (co-creator of the Advanced
Encryption Standard) and Paulo Barreto.
In this chapter, we'll use the OpenSSL library again, as it provides a built-in
implementation of the Whirlpool hash function.
First, make sure OpenSSL is installed on your system. If not, you can install
it with the following command:

bash:
sudo apt-get install libssl-dev

Here's a C program that hashes a large file using the Whirlpool algorithm:

C:

#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int hash_file(char *filepath) {


unsigned char buffer[BUFFER_SIZE];
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
EVP_MD_CTX *mdctx;

const EVP_MD* md = EVP_whirlpool();

int file = open(filepath, O_RDONLY);


if (file < 0) {
perror("Failed to open file");
return -1;
}

if((mdctx = EVP_MD_CTX_new()) == NULL) {


perror("Failed to create a new EVP_MD_CTX");
return -1;
}

EVP_DigestInit(mdctx, md);
ssize_t bytesRead;

while ((bytesRead = read(file, buffer, BUFFER_SIZE)) > 0) {


EVP_DigestUpdate(mdctx, buffer, bytesRead);
}

if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}

EVP_DigestFinal_ex(mdctx, hash, &hash_len);


EVP_MD_CTX_free(mdctx);
close(file);

// Print the hash in hexadecimal format


for (int i = 0; i < hash_len; i++)
printf("%02x", hash[i]);
return 0;
}

int main(int argc, char **argv) {


if (argc < 2) {
printf("Usage: %s <file>\n", argv[0]);
return -1;
}

hash_file(argv[1]);

return 0;
}

This code is quite similar to our SHA3-256 example, with a key difference
being the hash algorithm. Let's go through the parts related to Whirlpool:
1. `EVP_MD* md = EVP_whirlpool();` sets up to use the Whirlpool hash
function.
2. `EVP_DigestInit(mdctx, md);` initializes the `mdctx` context to use the
Whirlpool hash function.
The rest of the code is identical to the previous examples - it reads the file
in chunks, updates the hash context with each chunk, and finally retrieves
and prints the hash.

OceanofPDF.com
3.18.2.7 RIPEMD-160 WITH
OPENSSL
RIPEMD-160 is a cryptographic hash function that produces a 160-bit hash
value, and is a strong hash function often used in various security
applications and protocols. RIPEMD-160 is a part of the OpenSSL library,
so we'll use OpenSSL in our C program to generate a RIPEMD-160 hash.
Ensure that OpenSSL is installed on your system by running the following
command:

bash:
sudo apt-get install libssl-dev

Here's a simple C program that hashes a file using RIPEMD-160:

C:

#include <stdio.h>
#include <openssl/evp.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int hash_file(char *filepath) {


unsigned char buffer[BUFFER_SIZE];
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
EVP_MD_CTX *mdctx;
const EVP_MD* md = EVP_ripemd160();

int file = open(filepath, O_RDONLY);


if (file < 0) {
perror("Failed to open file");
return -1;
}

if((mdctx = EVP_MD_CTX_new()) == NULL) {


perror("Failed to create a new EVP_MD_CTX");
return -1;
}

EVP_DigestInit(mdctx, md);
ssize_t bytesRead;

while ((bytesRead = read(file, buffer, BUFFER_SIZE)) > 0) {


EVP_DigestUpdate(mdctx, buffer, bytesRead);
}

if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}

EVP_DigestFinal_ex(mdctx, hash, &hash_len);


EVP_MD_CTX_free(mdctx);
close(file);

// Print the hash in hexadecimal format


for (int i = 0; i < hash_len; i++)
printf("%02x", hash[i]);

return 0;
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("Usage: %s <file>\n", argv[0]);
return -1;
}

hash_file(argv[1]);

return 0;
}

This code operates similarly to the Whirlpool example, with the primary
difference being the hash algorithm. Let's breakdown the lines involving
RIPEMD-160:
1. `EVP_MD* md = EVP_ripemd160();` sets up to use the RIPEMD-160
hash function.
2. `EVP_DigestInit(mdctx, md);` initializes the `mdctx` context to use the
RIPEMD-160 hash function.
Rest of the code is identical to our previous examples, where the code reads
the file in chunks, updates the hash context with each chunk, and finally
retrieves and prints the hash.

OceanofPDF.com
3.19 ENSURING PRIVACY
THROUGH ASYMMETRIC
ENCRYPTION
In an era of digital interconnectedness, privacy has emerged as a
foundational necessity, central to personal security, professional integrity,
and the continued growth of free societies. The explosive proliferation of
online communication channels and the digitization of virtually every
aspect of our lives has brought about a compelling need to secure these
interactions from potential compromise. One such area, where the demand
for security is paramount, is messaging.
Secure messaging transcends traditional email, text messaging, or chat
applications, offering a layer of protection that keeps our communications
safe from prying eyes. It serves as the modern equivalent of the sealed
envelope, a digital safeguard that allows us to share sensitive information
with confidence, in the certainty that only the intended recipient can access
the contents of our messages.
The concept of secure messaging may seem complex, involving a blend of
mathematical principles, computer science knowledge, and intricate
algorithms. However, its basis lies in the principles of cryptography, a
discipline that has been used for thousands of years to keep secrets safe,
from ancient Egyptian hieroglyphs to wartime coded messages.
One fundamental cryptographic method that underpins secure messaging is
Public Key Encryption, also known as Asymmetric Encryption. This system
is predicated on a pair of keys, one private and one public. While the public
key can be shared widely, used to encrypt messages by anyone who wishes
to communicate securely with the key owner, the private key is jealously
guarded, the sole means by which the encrypted messages can be decrypted
and read.
This chapter will delve into the workings of secure messaging, with an
emphasis on how Public Key Encryption plays an integral role in its
operation. We will explore various practical examples and real-world
applications of this technology, showing how it contributes to a more secure
digital environment for everyone. By understanding how secure messaging
works, you will be better equipped to protect your own digital
communications and navigate the ever-evolving terrain of cyberspace safely
and confidentially.
Whether you are a business professional concerned about protecting trade
secrets, a public figure ensuring your private communications remain so, or
simply an individual navigating the digital landscape, understanding secure
messaging is a vital part of the equation. As we journey into this fascinating
world of codes and ciphers, you'll learn more than just technicalities - you'll
come to appreciate the importance and power of secure messaging in our
digital age.

OceanofPDF.com
3.19.1 UNRAVELLING THE
PROCESS OF SECURE
MESSAGING
Secure messaging serves as a protective barrier, assuring the confidentiality
of our digital communications. The magic behind this layer of protection is
a blend of mathematical and computer science principles working
harmoniously together in the form of encryption and decryption.

The Encryption Process


Encryption is the initial and critical step in secure messaging. This process
transmutes the original, plain-text message into an indecipherable mix of
characters, only interpretable by the intended recipient. Here are the steps
involved in the encryption process:
1. Message Generation: The sender generates a message that needs to be
sent securely.
2. Message Encryption: The sender then employs the receiver's public key
(acquired from their certificate) to encrypt the message. Notably, this isn't a
direct process. The sender first creates a random symmetric key and
Intialization Vector, uses it to encrypt the message (since symmetric
encryption is more efficient), and then encrypts this symmetric key using
the receiver's public key. This process, often termed "key wrapping,"
ensures the optimal blend of speed and security.
3. Message Transmission: The encrypted message, often paired with the
wrapped symmetric key (and Intialization Vector: IV), is then transmitted to
the receiver.

The Decryption Process


Upon reaching the receiver, the message must then be deciphered or
decrypted. The steps involved in the decryption process are as follows:
1. Message Reception: The receiver obtains the encrypted message.
2. Symmetric Key Decryption: The receiver uses their private key to
decrypt the symmetric key (and Intialization Vector: IV).
3. Message Decryption: The receiver then employs the decrypted
symmetric key to interpret the actual message.
The above method, involving two different keys (public and private), is
commonly known as Public Key Encryption or Asymmetric Encryption.
The efficiency of symmetric encryption and the security and convenience of
public key encryption are ingeniously merged in this process.
It's crucial to remember that this explanation of the encryption/decryption
process is somewhat simplified for clarity. Real-world applications include
additional steps and considerations like the use of initialization vectors for
symmetric encryption, padding for public key encryption, secure random
number generation for the symmetric key, and so on.

Beyond Confidentiality
While the process we've explored ensures confidentiality (keeping the
message secret), it doesn't provide authenticity or integrity. It neither
protects against tampering nor verifies the sender's identity. To achieve
these, other elements such as a digital signature or a Message
Authentication Code (MAC) are needed. Frequently, protocols like
SSL/TLS are employed, offering a comprehensive solution providing
confidentiality, integrity, and authenticity, thereby fortifying the secure
messaging process.
By understanding these fundamental processes, we gain insight into the
underpinnings of secure messaging, enabling us to better appreciate and
navigate the world of secure digital communication. In the following
sections, we will delve deeper into these processes, elucidating the intricate
components and principles that make secure messaging possible.

OceanofPDF.com
3.19.2 INTRODUCTION TO
OPENSSL COMMAND-
LINE TOOL
First, generate a pair of keys. You could use the RSA algorithm for this:

bash:

# Generate a new private key


openssl genpkey -algorithm RSA -out private_key.pem

# Extract the public key


openssl rsa -pubout -in private_key.pem -out public_key.pem

Now, let's say you have a message in a file named `message.txt` that you
want to encrypt.

Encrypt the message:

bash:

# Generate a random 256-bit AES key


openssl rand -out key.bin 32

# Encrypt the message using the AES key


openssl enc -aes-256-cbc -salt -in message.txt -out message.enc -pass file:./key.bin

# Encrypt the AES key using the recipient's public key


openssl rsautl -encrypt -inkey public_key.pem -pubin -in key.bin -out key.bin.enc
At this point, you can send `message.enc` (the encrypted message) and
`key.bin.enc` (the encrypted AES key) to the recipient.

Decrypt the message:


On the receiving side, you'd do the opposite to decrypt the message:

bash:

# Decrypt the AES key using the recipient's private key


openssl rsautl -decrypt -inkey private_key.pem -in key.bin.enc -out key.bin

# Decrypt the message using the AES key


openssl enc -aes-256-cbc -d -in message.enc -out message.txt -pass file:./key.bin

Now, `message.txt` contains the original plaintext message.


Remember, this is a simple example. In real life, there are more
considerations, such as secure handling and storage of keys, using different
keys for each message, and more.

OceanofPDF.com
3.19.3 SIMPLIFYING
SECURE MESSAGING: A
FOCUS ON CORE
FUNCTIONS IN C
To foster a robust understanding of secure messaging, sometimes it's
beneficial to pare down the process to its core functions. By focusing on
these fundamental aspects, we can better grasp the underlying principles of
the encryption and decryption processes, forming a solid foundation from
which to explore the more intricate details. Let's examine these key
operations that are pivotal in implementing secure messaging:

OceanofPDF.com
3.19.3.1 KEYS
GENERATION
The first step in establishing a secure messaging protocol is key generation,
creating the necessary tools to encrypt and decrypt your messages. This
fundamental process involves generating both asymmetric and symmetric
keys.

Asymmetric and Symmetric Keys


- Asymmetric Keys: A pair of keys (private and public) are created. These
keys work in tandem - one for encryption (public), and the other for
decryption (private).
- Symmetric Key: A single key, such as an AES key, is generated for
symmetric encryption, where the same key is used for both encryption and
decryption.

3.19.3.1.1 Generating Asymmetric


Key pairs
The OpenSSL library provides a comprehensive set of functions to generate
key pairs, perform encryption and decryption, and handle digital
certificates. For illustrative purposes, we'll use an example of how you can
generate a new RSA key pair in C using OpenSSL:

C:

#include <stdio.h>
#include <stdlib.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>

int generate_key() {
int ret = 0;
RSA *r = NULL;
BIGNUM *bignum = NULL;
BIO *bio_public = NULL, *bio_private = NULL;

// Create a big number object.


bignum = BN_new();
ret = BN_set_word(bignum, RSA_F4); // RSA_F4 is a public exponent.

// Generate the RSA key pair.


r = RSA_new();
ret = RSA_generate_key_ex(r, 2048, bignum, NULL);
if(ret != 1) {
printf("Failed to generate key pair.\n");
goto free_all;
}

// Write the private key to a file.


bio_private = BIO_new_file("private.pem", "w+");
ret = PEM_write_bio_RSAPrivateKey(bio_private, r, NULL, NULL, 0, NULL, NULL);
if(ret != 1) {
printf("Failed to write private key to a file.\n");
goto free_all;
}

// Write the public key to a file.


bio_public = BIO_new_file("public.pem", "w+");
ret = PEM_write_bio_RSA_PUBKEY(bio_public, r);
if(ret != 1) {
printf("Failed to write public key to a file.\n");
goto free_all;
}
free_all:
BIO_free_all(bio_public);
BIO_free_all(bio_private);
RSA_free(r);
BN_free(bignum);

return (ret == 1) ? EXIT_SUCCESS : EXIT_FAILURE;


}

int main() {
return generate_key();
}

This program will generate a 2048-bit RSA key pair and write the keys to
"private.pem" and "public.pem" files in PEM format. If something goes
wrong, it will print an error message and return an error status.
When compiling this code, ensure you link against the OpenSSL library.
For example, you could use `gcc myprog.c -o myprog -lcrypto` where
"myprog.c" is the name of your C file.
Important Note: This code example is simplified for educational purposes
and doesn't check for errors in some places where a real-world application
would require. In actual implementations, more robust error checking and
appropriate error handling are critical.

OceanofPDF.com
3.19.3.2 VERIFYING THE
KEY PAIR
To verify if your key pair (private.pem and public.pem) was generated
properly, you can perform the following steps:

1. Check file existence: Ensure that both private.pem and public.pem files
exist in the specified location.
2. Key Pair Match: Verify that the public key corresponds to the private key.
This can be done by comparing the modulus values of both keys.
Here's an example using OpenSSL command-line tools:

bash:

openssl rsa -pubin -in public.pem -modulus -noout


openssl rsa -in private.pem -modulus -noout

Compare the output of these two commands. If the modulus values match,
it indicates that the keys are a valid pair.
3. Encryption and Decryption: You can also verify the key pair by
encrypting a message with the public key and decrypting it with the private
key. If the decrypted message matches the original message, it indicates that
the key pair is valid.
Here's an example using OpenSSL command-line tools:

bash:
echo "This is a test message" > message.txt
openssl rsautl -encrypt -inkey public.pem -pubin -in message.txt -out encrypted.txt
openssl rsautl -decrypt -inkey private.pem -in encrypted.txt -out decrypted.txt

Compare the contents of `decrypted.txt` with the original message. If they


are the same, it suggests that the key pair is correct.
By following these steps, you can verify if your key pair was generated
properly and is functioning as expected.

3.19.3.2.1 Generating a Symmetric


AES Key
In our pursuit of understanding secure messaging, we've looked at
generating asymmetric keys. Now, we delve into the generation of
symmetric keys. Specifically, we will explore the creation of a 256-bit (32
bytes) symmetric AES key using OpenSSL's random number generator.
In contrast to asymmetric encryption, symmetric encryption uses a single
key for both the encryption and decryption processes. This key must remain
confidential between the parties involved. Here's a basic example of how
you can generate such a key.

C:

#include <stdio.h>
#include <openssl/rand.h>

int generate_symmetric_key() {
unsigned char key[32];
if (RAND_bytes(key, sizeof(key)) != 1) {
printf("Failed to generate random key.\n");
return EXIT_FAILURE;
}
printf("Generated AES key:\n");
for (size_t i = 0; i < sizeof(key); ++i) {
printf("%02x", key[i]);
}
printf("\n");

return EXIT_SUCCESS;
}

int main() {
return generate_symmetric_key();
}

This program will generate a 256-bit random AES key and print it to the
console as a hexadecimal string. If something goes wrong (e.g., if the
random number generator fails), it will print an error message and return an
error status.
Compile this code with the OpenSSL library linked using the command
`gcc myprog.c -o myprog -lcrypto` where "myprog.c" is the name of your C
file.
Important Note: This example is a simplified illustration meant solely for
educational purposes. In an actual application, you would need to handle
the key with greater caution. For instance, securely storing the key and not
displaying it on the console are measures that would typically be taken to
ensure the key's security.

OceanofPDF.com
3.19.3.3 ENCRYPTION
Encryption is the act of converting the original message into a format that
can only be understood by the intended recipient.

3.19.3.3.1 Symmetric Encryption


with AES-256-CBC Cipher
Having previously dealt with key generation, we now turn our attention to
the encryption process. In this chapter, we will examine a simple example
of encrypting a message using the AES-256-CBC cipher facilitated by the
OpenSSL library.

Message Encryption
The process of transforming plaintext into an unreadable ciphertext using a
specific algorithm and key is known as encryption. In this section, we will
detail how to encrypt a plaintext message using the AES-256-CBC cipher.

C:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/rand.h>

int encrypt_message() {
// Message to be encrypted
unsigned char plaintext[] = "Hello, world!";
// Generate a 256-bit key
unsigned char key[32];
if (RAND_bytes(key, sizeof(key)) != 1) {
printf("Failed to generate random key.\n");
return EXIT_FAILURE;
}

// Generate a random IV
unsigned char iv[16];
if (RAND_bytes(iv, sizeof(iv)) != 1) {
printf("Failed to generate random IV.\n");
return EXIT_FAILURE;
}

// Buffer for the ciphertext


unsigned char ciphertext[128];
int ciphertext_len;

// Create and initialise the context


EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
printf("Failed to create context.\n");
return EXIT_FAILURE;
}

// Initialise the encryption operation


if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) {
printf("Failed to initialise encryption.\n");
EVP_CIPHER_CTX_free(ctx);
return EXIT_FAILURE;
}

// Provide the message to be encrypted, and obtain the encrypted output


if (EVP_EncryptUpdate(ctx, ciphertext, &ciphertext_len, plaintext, strlen((char *)plaintext)) != 1)
{
printf("Failed to encrypt.\n");
EVP_CIPHER_CTX_free(ctx);
return EXIT_FAILURE;
}

// Finalise the encryption


int len;
if (EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &len) != 1) {
printf("Failed to finalise encryption.\n");
EVP_CIPHER_CTX_free(ctx);
return EXIT_FAILURE;
}
ciphertext_len += len;

// Clean up
EVP_CIPHER_CTX_free(ctx);

printf("Ciphertext is:\n");
for (int i = 0; i < ciphertext_len; i++)
printf("%02x", ciphertext[i]);
printf("\n");

return EXIT_SUCCESS;
}

int main() {
return encrypt_message();
}

This program will generate a random AES key and IV, encrypt a plaintext
message using AES-256-CBC, and print the resulting ciphertext as a
hexadecimal string.
Compile the code with the OpenSSL library linked using `gcc myprog.c -o
myprog -lcrypto`, where "myprog.c" is the name of your C file.
Important Note: This example assumes that the plaintext fits into a single
block. In an actual application, you would need to accommodate larger
plaintexts by invoking `EVP_EncryptUpdate()` multiple times.
Additionally, you would need to incorporate proper error handling and
secure handling of keys, IVs, and other sensitive data. Remember, both the
IV and the ciphertext must be transmitted to the recipient for decryption.

3.19.3.3.2 Asymmetric Encryption


of Symmetric Key
This chapter extends our exploration of encryption, particularly focusing on
the asymmetric encryption of a symmetric key with a recipient's public key.
This concept, known as key wrapping, is a common method of securely
transferring a symmetric key over an insecure network. We'll use the RSA
public key cryptography functions provided by the OpenSSL library for this
purpose.

Asymmetric Key Encryption


The key steps in this process include generating a random symmetric key,
reading the recipient's public key from a file, encrypting the symmetric key,
and then writing the encrypted key to another file.

C:

#include <stdio.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>

int encrypt_symmetric_key() {
// Generate a 256-bit key
unsigned char key[32];
if (RAND_bytes(key, sizeof(key)) != 1) {
printf("Failed to generate random key.\n");
return EXIT_FAILURE;
}

// Read the recipient's public key


FILE *pubkey_file = fopen("public.pem", "rb");
if (!pubkey_file) {
printf("Failed to open public key file.\n");
return EXIT_FAILURE;
}
RSA *pubkey = PEM_read_RSA_PUBKEY(pubkey_file, NULL, NULL, NULL);
fclose(pubkey_file);
if (!pubkey) {
printf("Failed to read public key.\n");
return EXIT_FAILURE;
}

// Encrypt the symmetric key


unsigned char encrypted_key[RSA_size(pubkey)];
int encrypted_key_len = RSA_public_encrypt(sizeof(key), key, encrypted_key, pubkey,
RSA_PKCS1_OAEP_PADDING);
RSA_free(pubkey);
if (encrypted_key_len == -1) {
printf("Failed to encrypt key.\n");
return EXIT_FAILURE;
}

// Write the encrypted key to a file


FILE *out_file = fopen("encrypted_key.bin", "wb");
if (!out_file) {
printf("Failed to open output file.\n");
return EXIT_FAILURE;
}
fwrite(encrypted_key, 1, encrypted_key_len, out_file);
fclose(out_file);

printf("Encrypted key written to encrypted_key.bin\n");

return EXIT_SUCCESS;
}

int main() {
return encrypt_symmetric_key();
}

When executed, the program generates a random AES key, reads the
recipient's RSA public key from the file "public.pem", encrypts the AES
key using RSA-OAEP, and writes the encrypted key to the file
"encrypted_key.bin".
Remember to compile the code with the OpenSSL library linked, using a
command like `gcc myprog.c -o myprog -lcrypto`, where "myprog.c" is
your C file name.
Note: This is a simplified example created for educational purposes. In real-
world applications, you should implement secure key handling, include
comprehensive error checking, and appropriately handle any arising errors.

OceanofPDF.com
3.19.3.4 DECRYPTION
Decryption is the reverse process of encryption, converting the encoded
message back into its original format.

3.19.3.4.1 Decryption of Symmetric


Key Using RSA
In this chapter, we delve into the decryption process of a symmetric key that
has been encrypted with a recipient's public key. For this, we will be using
OpenSSL's RSA private key cryptography functions.

Symmetric Key Decryption


The decryption process involves reading the encrypted key from a file,
reading the recipient's private key, decrypting the symmetric key, and then
printing the decrypted key.

C:

#include <stdio.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>

int decrypt_symmetric_key() {
// Read the encrypted key from a file
FILE *in_file = fopen("encrypted_key.bin", "rb");
if (!in_file) {
printf("Failed to open input file.\n");
return EXIT_FAILURE;
}
fseek(in_file, 0, SEEK_END);
long encrypted_key_len = ftell(in_file);
fseek(in_file, 0, SEEK_SET);
unsigned char encrypted_key[encrypted_key_len];
fread(encrypted_key, 1, encrypted_key_len, in_file);
fclose(in_file);

// Read the recipient's private key


FILE *privkey_file = fopen("private.pem", "rb");
if (!privkey_file) {
printf("Failed to open private key file.\n");
return EXIT_FAILURE;
}
RSA *privkey = PEM_read_RSAPrivateKey(privkey_file, NULL, NULL, NULL);
fclose(privkey_file);
if (!privkey) {
printf("Failed to read private key.\n");
return EXIT_FAILURE;
}

// Decrypt the symmetric key


unsigned char key[RSA_size(privkey)];
int key_len = RSA_private_decrypt(encrypted_key_len, encrypted_key, key, privkey,
RSA_PKCS1_OAEP_PADDING);
RSA_free(privkey);
if (key_len == -1) {
printf("Failed to decrypt key.\n");
return EXIT_FAILURE;
}

printf("Decrypted key is:\n");


for (int i = 0; i < key_len; i++)
printf("%02x", key[i]);
printf("\n");
return EXIT_SUCCESS;
}

int main() {
return decrypt_symmetric_key();
}

This program reads an encrypted AES key from the file


"encrypted_key.bin", reads the recipient's RSA private key from
"private.pem", decrypts the AES key using RSA-OAEP, and prints the
decrypted key as a hexadecimal string.
The code needs to be compiled with the OpenSSL library linked. You can
do this by running a command like `gcc myprog.c -o myprog -lcrypto`,
where "myprog.c" is your C file name.
Note: This example is simplified and primarily intended for educational
purposes. In real-world applications, you should practice secure key
handling, include comprehensive error checking, and manage errors
correctly. It's crucial not to print the decrypted key to stdout in a real-world
scenario for security reasons.

3.19.3.4.2 Decrypting a Message


with AES-256-CBC Cipher
In this chapter, we will cover how to decrypt a message encrypted with the
AES-256-CBC cipher using the OpenSSL library.

C:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int decrypt_message() {
// In a real application, you would obtain these from elsewhere
unsigned char key[32]; // The symmetric key
unsigned char iv[16]; // The IV
unsigned char ciphertext[] = {0x00, 0x01, 0x02, 0x03, ...}; // The ciphertext to decrypt
int ciphertext_len = sizeof(ciphertext);

// Buffer for the plaintext


unsigned char plaintext[128];
int plaintext_len;

// Create and initialise the context


EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
printf("Failed to create context.\n");
return EXIT_FAILURE;
}

// Initialise the decryption operation


if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) {
printf("Failed to initialise decryption.\n");
EVP_CIPHER_CTX_free(ctx);
return EXIT_FAILURE;
}

// Provide the ciphertext to be decrypted, and obtain the plaintext output


if (EVP_DecryptUpdate(ctx, plaintext, &plaintext_len, ciphertext, ciphertext_len) != 1) {
printf("Failed to decrypt.\n");
EVP_CIPHER_CTX_free(ctx);
return EXIT_FAILURE;
}

// Finalise the decryption


int len;
if (EVP_DecryptFinal_ex(ctx, plaintext + plaintext_len, &len) != 1) {
printf("Failed to finalise decryption.\n");
EVP_CIPHER_CTX_free(ctx);
return EXIT_FAILURE;
}
plaintext_len += len;

// Clean up
EVP_CIPHER_CTX_free(ctx);

// Add null terminator to the plaintext


plaintext[plaintext_len] = 0;

printf("Plaintext is: %s\n", plaintext);

return EXIT_SUCCESS;
}

int main() {
return decrypt_message();
}

This decrypt_message function creates a context, initializes the decryption


process, provides the predefined ciphertext message to be decrypted using
AES-256-CBC, obtains the plaintext output, finalizes the decryption, and
cleans up the context. It also adds a null terminator to the plaintext before
printing the plaintext.

Compiling and Running the Program


To compile the code, you should link the OpenSSL library using a
command like `gcc myprog.c -o myprog -lcrypto`.
Note: This example is simplified and intended for educational purposes. In
a real application, you will need to handle larger ciphertexts by calling
`EVP_DecryptUpdate()` multiple times. You should also include proper
error handling and secure management of keys, IVs, and other sensitive
data. The key and IV must be identical to those used for encryption. Instead
of hardcoding the key, IV, and ciphertext, you should obtain them from
secure sources or user input in real-world scenarios.
OceanofPDF.com
3.19.3.5 DATA TRANSFER
Transferring the encrypted data securely to the recipient is the final piece of
the puzzle.
Transmission and Reception: The encrypted message and the encrypted
symmetric key (and the Initialization Vector: IV) are sent to the recipient.
The recipient uses their private key and the symmetric key with the IV to
decrypt the message and read it.
An important detail to bear in mind is that the IV does not need to be
encrypted before transmission, unlike the key. The IV isn't a secret value.
Its main role is to inject randomness into the process and ensure that even
identical plaintexts give rise to different ciphertexts when they are
encrypted.
That said, the IV still needs to be securely transmitted to the recipient,
because if an attacker interferes with the IV, it could potentially disrupt the
decryption process and compromise the integrity of the decrypted plaintext.
The IV can be appended to the ciphertext or transmitted separately, as long
as the recipient can accurately associate it with the appropriate ciphertext.
Upon receipt of the encrypted data, the recipient should decrypt the
symmetric key and the IV (if the IV is encrypted) with their private key.
With the symmetric key and the IV at their disposal, they can then decrypt
the ciphertext and recover the original message.
By breaking down secure messaging into these key operations, we can
obtain a simplified yet informative perspective on the processes of
encryption and decryption. It's important to remember, though, that this
breakdown is primarily educational and omits many of the additional steps
and factors that a real-world application would involve, some of which we
have already explored in previous chapters. As we delve deeper into the
world of secure messaging, these nuances will become increasingly
apparent, deepening our understanding of the intricate dance between
security and functionality.
OceanofPDF.com
3.20 SECURE
COMMUNICATIONS WITH
SSL AND TLS
1. Understanding Secure Connections
A secure connection is a communication link that exists between two points
on a network. This connection is safeguarded by encryption, which
scrambles information that passes through the connection into a format that
can only be decoded using the correct decryption key. This prevents
unauthorized third parties from intercepting and understanding the content
of the communication, thereby ensuring the data's privacy and integrity.

2. The Role of SSL (Secure Sockets Layer)


Secure Sockets Layer, or SSL, is a cryptographic protocol that provides
communication security over a computer network. This protocol employs
asymmetric cryptography for key exchange, symmetric encryption for
preserving confidentiality, and message authentication codes for
maintaining message integrity.
SSL was previously the standard protocol used for secure connections. You
can easily identify websites that use SSL because their URLs start with
"https://" instead of the standard "http://".

3. Transition to TLS (Transport Layer Security)


Transport Layer Security (TLS) is the successor to SSL and represents
another cryptographic protocol that is used to establish secure internet
communication. TLS provides secure data transmission between a server
and a client (like a web server and a browser), or between servers.
Like SSL, TLS uses encryption to scramble data, ensuring it cannot be
modified or tampered with during transmission. However, TLS offers a
higher level of security than SSL and has become the industry standard for
secure web communications.

4. The Concept of Secure Handshake


The secure handshake forms the initial part of the communication where
two parties establish the terms of their conversation. In the context of
SSL/TLS, a handshake protocol is implemented before any data is
transmitted. This protocol involves several steps:
1. The client (like a web browser) sends a "client hello" message to
the server, indicating the versions of SSL/TLS and cipher suites it
supports, along with a random number.
2. The server responds with a "server hello" message, choosing the
highest level of SSL/TLS and cipher suite that both the client and
server support, and provides its own random number. The server also
sends its digital certificate for verification.
3. The client verifies the server's digital certificate with a Certificate
Authority and sends a pre-master secret encrypted with the server's
public key.
4. Both the server and the client use the pre-master secret and random
numbers to compute a shared secret, termed the master secret.
5. The client sends a "finished" message, encrypted with the master
secret.
6. The server decrypts the "finished" message using the master secret
and sends its own "finished" message, encrypted with the master
secret.
7. The client decrypts the server's "finished" message using the master
secret. If this process is successful, the handshake is complete.

Once the handshake is successfully completed, both parties have


authenticated each other and agreed on a shared secret for encryption.
Consequently, all subsequent data transmitted between the server and client
is encrypted with this shared secret, thus maintaining the data's
confidentiality and security throughout the communication process.

OceanofPDF.com
3.20.1 SECURE
HANDSHAKE
IMPLEMENTATION USING
OPENSSL
In this chapter, we delve into how to establish a secure connection, also
known as a "secure handshake," using the OpenSSL library in the C
programming language. This process is fundamental to ensuring secure data
communication between the client and server.
To begin, let's review the C code for performing a secure handshake with a
server, using the server "www.google.com" as an example:

C:

#include <stdio.h>
#include <stdlib.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>

void handleFailure() {
ERR_print_errors_fp(stderr);
exit(1);
}

int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {


char buf[256];
X509 *err_cert;
int err, depth;
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);

X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);

/* Catch a too long certificate chain */


if (depth > 4) {
preverify_ok = 0;
err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
X509_STORE_CTX_set_error(ctx, err);
}
if (!preverify_ok) {
printf("verify error:num=%d:%s\n", err,
X509_verify_cert_error_string(err));
printf("depth=%d:%s\n", depth, buf);
}

return preverify_ok;
}

int main() {
const char *hostname = "www.google.com";
const char *port = "443";

// Initialize the OpenSSL library


SSL_library_init();
SSL_load_error_strings();

// Create a new SSL context


SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method());
if (ssl_ctx == NULL) handleFailure();

// Load the trusted CA certificates


if (!SSL_CTX_load_verify_locations(ssl_ctx, "/etc/ssl/certs/ca-certificates.crt", NULL)) {
handleFailure();
}
// Configure the context to verify the server's certificate
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);

// Create a new SSL connection


SSL *ssl_conn = SSL_new(ssl_ctx);
if (ssl_conn == NULL) handleFailure();

// Create a BIO object associated with the SSL object


BIO *bio = BIO_new_ssl_connect(ssl_ctx);
if (bio == NULL) {
fprintf(stderr, "BIO_new_ssl_connect failed\n");
handleFailure();
}

BIO_get_ssl(bio, &ssl_conn); // Get the SSL object from the BIO object

// Set the SNI hostname


if (!SSL_set_tlsext_host_name(ssl_conn, hostname)) {
fprintf(stderr, "Hostname configuration failed\n");
handleFailure();
}

// Set the hostname and port


BIO_set_conn_hostname(bio, hostname);
BIO_set_conn_port(bio, port);

// Perform the SSL/TLS handshake


if (BIO_do_connect(bio) <= 0) handleFailure();

// Verify the server's certificate


long verify_result = SSL_get_verify_result(ssl_conn);
if (verify_result != X509_V_OK) {
fprintf(stderr, "Certificate verification error: %ld\n", verify_result);
handleFailure();
}

// Verify the server's hostname


X509* cert = SSL_get_peer_certificate(ssl_conn);
if(cert != NULL) {
if(X509_check_host(cert, hostname, 0, 0, 0) != 1) {
fprintf(stderr, "Hostname verification failed\n");
handleFailure();
}
X509_free(cert);
} else {
fprintf(stderr, "No certificate was presented by the server\n");
handleFailure();
}

printf("SSL connection using %s\n", SSL_get_cipher(ssl_conn));

// Clean up
BIO_free_all(bio);
SSL_CTX_free(ssl_ctx);

return 0;
}

This code comprises the following main components:


1. Libraries: The program begins by including necessary OpenSSL headers,
such as `ssl.h`, `err.h`, and `x509v3.h`. These headers allow the program to
use the SSL and X509 functionalities of OpenSSL and handle errors
respectively.
2. Error Handling: `handleFailure()` is a function defined to handle any
errors that may occur in the program. It prints the error to the standard error
output and terminates the program.
3. Certificate Verification Callback: `verify_callback()` is a function used
during the handshake process. It gets called at each step of the certificate
chain verification to allow the application to handle verification failures or
to accept a certificate manually.
4. Main Function: The `main()` function initiates the OpenSSL library,
creates a new SSL context, configures the SSL context to verify the server's
certificate, establishes the SSL connection, verifies the server's certificate,
verifies the server's hostname, and then cleans up the SSL context and
connection.
In summary, this chapter has outlined the C code necessary to perform a
secure handshake using the OpenSSL library. Understanding and
implementing such code is crucial in ensuring secure data communication, a
cornerstone of modern internet interactions.

OceanofPDF.com
3.20.2 COMMUNICATING
AND PROCESSING
SERVER RESPONSE WITH
OPENSSL
In this chapter, we'll continue our exploration of OpenSSL's capabilities by
demonstrating how to communicate with the server and subsequently
process the server's response. These steps typically occur after you've
successfully established an SSL connection, also known as an SSL
handshake, which was the focus of our preceding chapter.
To initiate communication with a server, we can use a straightforward
HTTP GET request. In the C programming language, we can structure this
request as follows:

C:

char request[200];
sprintf(request, "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", hostname);
if (BIO_write(bio, request, strlen(request)) <= 0) {
// Handle the error...
}

In this code, we're creating a string that constitutes an HTTP GET request.
The request is then sent to the server using the `BIO_write` function, which
writes data into the given BIO (Basic Input/Output) structure.

Once we've sent the request, we can print the response from the server
using the `BIO_read` function. `BIO_read` reads data from the provided
BIO structure, in this case, the response from the server.

Here's how you can use `BIO_read` to print the server's response:

C:

char response[1024];
int len = 0;

do {
len = BIO_read(bio, response, sizeof(response) - 1);
if(len > 0) {
response[len] = '\0';
printf("%s", response);
} else if(!BIO_should_retry(bio)) {
break; // Stop if there's no more data
}
} while(len > 0 || BIO_should_retry(bio));

In this code block, we're reading the server's response into a buffer and then
printing it to the standard output. If `BIO_read` returns a value greater than
0, it means that the operation has succeeded, and we can print the response.
If there's no data left to read, and no need to retry, we break out of the loop.
Please note that the methods employed above are rudimentary and designed
for educational purposes. They don't account for the processing of HTTP
headers or for chunked transfer encoding. In a real-world application
scenario, you'd typically use a robust HTTP client library capable of
handling these aspects in a comprehensive manner. By learning these
foundational principles, however, you can better understand how these
libraries operate under the hood and become better equipped to troubleshoot
issues that might arise.

OceanofPDF.com
3.20.3 IMPLEMENTING A
SECURE SERVER USING
OPENSSL
This chapter will guide you through the process of implementing a secure
server listening on port 443, with the help of OpenSSL library. It is assumed
that you already have a server certificate and a private key at your disposal.
If that is not the case, you can generate a self-signed certificate with
OpenSSL's command-line tools.
Let's begin with the full code listing:

C:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

int create_socket(int port) {


int s;
struct sockaddr_in addr;

addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}

if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {


perror("Unable to bind");
exit(EXIT_FAILURE);
}

if (listen(s, 1) < 0) {
perror("Unable to listen");
exit(EXIT_FAILURE);
}

return s;
}

void init_openssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}

void cleanup_openssl() {
EVP_cleanup();
}

SSL_CTX *create_context() {
const SSL_METHOD *method;
SSL_CTX *ctx;

method = SSLv23_server_method();

ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}

return ctx;
}

void configure_context(SSL_CTX *ctx) {


/* Set the key and cert */
if (SSL_CTX_use_certificate_file(ctx, "server2.crt", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}

if (SSL_CTX_use_PrivateKey_file(ctx, "server2.key", SSL_FILETYPE_PEM) <= 0 ) {


ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}

if (!SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
}

int main(int argc, char **argv) {


int sock;
SSL_CTX *ctx;

init_openssl();
ctx = create_context();

configure_context(ctx);

sock = create_socket(443);

/* Handle connections */
while(1) {
struct sockaddr_in addr;
uint len = sizeof(addr);
SSL *ssl;
const char reply[] = "test\n";

int client = accept(sock, (struct sockaddr*)&addr, &len);


if (client < 0) {
perror("Unable to accept");
exit(EXIT_FAILURE);
}

ssl = SSL_new(ctx);
SSL_set_fd(ssl, client);

if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
}
else {
SSL_write(ssl, reply, strlen(reply));
}

SSL_shutdown(ssl);
SSL_free(ssl);
close(client);
}

close(sock);
SSL_CTX_free(ctx);
cleanup_openssl();
}

This server listens on port 443 and accepts incoming connections. Upon a
client's connection, it conducts the SSL handshake and sends a test message
to the client.
It utilizes a self-signed certificate (`server.crt`) and the corresponding
private key (`server.key`). These files are expected to be in the same
directory as the server program.
It's crucial to understand that this is a basic implementation. In real-world
applications, more thorough error checking and handling should be
implemented. The server should also have the capability to manage multiple
connections concurrently, usually achieved using threading or asynchronous
I/O. In a production environment, certificates from a trusted Certificate
Authority (CA) should be used, as opposed to a self-signed certificate.
Remember, running a server on port 443 typically necessitates administrator
(root) privileges, given that it is a privileged port number (below 1024).
Therefore, it's important to comprehend the security implications before
running your own program as root.

The code should be compiled linking the OpenSSL libraries, as shown:

bash:

gcc server.c -lssl -lcrypto -o server

You may need to install the OpenSSL development package if it's not
already installed (e.g., `libssl-dev` on Ubuntu).
The code provided in this chapter serves as an example of a basic HTTPS
server. However, in a real-world scenario, an HTTPS server would also
need to manage HTTP requests and responses. This includes parsing the
HTTP protocol and creating suitable responses. You might consider using a
library or framework to assist with this, such as libevent with its
bufferevent_ssl support, or higher-level web frameworks like kore.io.
Before deploying an HTTPS server in a production environment, it's critical
to meticulously consider security. Effectively securing an HTTPS server
covers many aspects, including, but not limited to, using strong encryption,
keeping your software up to date, and routinely reviewing your security
practices. If you're not experienced in secure server configuration, consider
using a well-established web server like Apache or Nginx. These have
secure default configurations and are well-documented, providing you with
a safer and more stable start.
Generating self-signed certificates
You can generate a self-signed certificate and a private key using
OpenSSL's command line tools. Here's how you can do it:

bash:

openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes

Here's what these options do:


- `req`: This is the command for creating a new certificate signing request
(CSR).
- `-x509`: This option tells OpenSSL to create a self-signed certificate
instead of a CSR.
- `-newkey rsa:4096`: This option creates a new certificate and a new key at
the same time. `rsa:4096` tells OpenSSL to generate an RSA key that's 4096
bits long.
- `-keyout server.key`: This tells OpenSSL where to save the private key.
- `-out server.crt`: This tells OpenSSL where to save the self-signed
certificate.
- `-days 365`: This option sets the length of time that the certificate is valid.
A setting of 365 means that the certificate is valid for one year.
- `-nodes`: This option tells OpenSSL to skip the option to secure our
certificate with a passphrase. We need to include this because otherwise
Apache will ask us for the password each time the server is started.

When you run this command, OpenSSL will ask you to enter some
information about your server. The most important part is the "Common
Name". Enter your server's domain name here or, if you're just testing this
on your local system, you can use `localhost`.

The command will generate a private key (`server.key`) and a self-signed


certificate (`server.crt`). You can use these files with your secure server.
Remember, this will generate a self-signed certificate. This kind of
certificate will not be trusted by web browsers (they will show a warning to
users). For a production environment, you should consider getting a
certificate from a trusted certificate authority.

Adding self-signed certificate to trust store


To add a self-signed certificate to a client's trust store on Linux, you need to
follow these steps:
1. Copy your certificate: You need to copy your `server.crt` file to the
`/usr/local/share/ca-certificates/` directory on the client machine. You can
do this using `scp`, `rsync`, or any other method you prefer. If you do not
have access to `/usr/local/share/ca-certificates/`, you may need to use `sudo`
or switch to the root user:

bash:

sudo cp server.crt /usr/local/share/ca-certificates/

Note that the certificate file must end with the `.crt` extension. The `update-
ca-certificates` command will only recognize files that end with `.crt`.
2. Update the CA certificate store: Debian provides a command that
automatically updates the CA certificate store. After you have copied the
file, run the following command:

bash:

sudo update-ca-certificates

This command will automatically add the certificates to the `/etc/ssl/certs/`


directory and rebuild the certificate cache.
3. Verify your certificate: You can check that your certificate has been
added correctly:

bash:

ls /etc/ssl/certs/ | grep server

Replace `server` with the name of your certificate file (without the `.crt`
extension). This command should return your certificate file and a symbolic
link to it.
After these steps, your self-signed certificate is in the trust store of the
client machine. Now, when the client establishes a secure connection with
your server, it will trust the server's certificate.
Remember that this will only work for the specific client you've added the
certificate to. Other clients will still reject the server's certificate unless you
add it to their trust stores as well.

Removing self-signed certificate from trust store


If you need to remove the self-signed certificate from the trust store, you
can do so by following these steps:
1. Remove the certificate: Delete the `server.crt` file from the
`/usr/local/share/ca-certificates/` directory on the client machine:

bash:
sudo rm /usr/local/share/ca-certificates/server.crt

2. Update the CA certificate store: Again, Debian provides a command that


automatically updates the CA certificate store. After you have deleted the
file, run the following command:

bash:
sudo update-ca-certificates --fresh
The `--fresh` option will make `update-ca-certificates` to rebuild the
certificate store from scratch. This will ensure that the removed certificate
will not be included in the store.
After these steps, your self-signed certificate is removed from the trust store
of the client machine. The client will no longer trust the server's certificate.
Remember, you need to do this operation on each client where you have
added the certificate in their trust store.

OceanofPDF.com
3.20.4 BIDIRECTIONAL
COMMUNICATION IN A
SECURE SERVER USING
OPENSSL
Building upon our previous chapter, "Implementing a Secure Server Using
OpenSSL in C," this supplementary chapter will guide you on how to
enhance our server's functionality to handle bidirectional communication,
meaning both reading from and writing to the client. We've already laid the
foundation for writing to the client; now, we will delve into how to read
client messages using the OpenSSL library.

Reading Messages from the Client


To read data from the client, we employ the `SSL_read` function provided
by OpenSSL. This function reads data from an established SSL/TLS
connection, which means it's not only reading data, but also handling the
decryption process in the background.
Let's expand our main loop within the server code to handle the reading of
client messages:

C:
/* Handle connections */
while(1) {
struct sockaddr_in addr;
uint len = sizeof(addr);
SSL *ssl;
int client = accept(sock, (struct sockaddr*)&addr, &len);
if (client < 0) {
perror("Unable to accept");
exit(EXIT_FAILURE);
}

ssl = SSL_new(ctx);
SSL_set_fd(ssl, client);

if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
}
else {
char buffer[1024];
int bytes;
do {
memset(buffer, 0, sizeof(buffer));
bytes = SSL_read(ssl, buffer, sizeof(buffer));
if(bytes > 0) {
printf("Message received: %s\n", buffer);
}
} while(bytes > 0);

/* Existing code to write to the client */


const char reply[] = "test\n";
SSL_write(ssl, reply, strlen(reply));
}

SSL_shutdown(ssl);
SSL_free(ssl);
close(client);
}
In this block, we introduced a buffer to store the incoming data and a
variable `bytes` to hold the result of the `SSL_read` operation. If the
`SSL_read` function reads data successfully, we print the received message
to the console.
Remember that in a real-world application, you would likely handle
incoming data more comprehensively. This might involve parsing the data
according to the protocol you're implementing, dealing with potential read
errors, or employing non-blocking I/O or multiple threads to cater to
multiple clients simultaneously.

Recap on Writing Messages to the Client


In the previous chapter, we already covered how to write a message to the
client. It's executed using the `SSL_write` function from the OpenSSL
library. This function not only writes data but also takes care of the
encryption process. Here's a brief recap of the write operation:

C:

const char reply[] = "test\n";


SSL_write(ssl, reply, strlen(reply));

The `SSL_write` function takes in three parameters: an SSL pointer, the


data to be sent, and the length of the data. It will return the number of bytes
written. If an error occurs, it will return a negative value, which should be
handled properly in a robust application.
This chapter aimed to enhance the server implementation to support
bidirectional communication, enabling it to read messages from the client
while maintaining the ability to write messages to the client. As you
progress in secure server development, consider exploring ways to improve
your server's performance and security. Remember, providing a quality
service to your clients involves regular updates, strong encryption, and
good security practices.

OceanofPDF.com
3.20 SECURING
SENSITIVE DATA IN
MEMORY
Storing sensitive data such as passwords and keys in memory can pose a
significant security risk, particularly if an attacker gains access to the
memory of your program. This chapter outlines several strategies to
minimize this risk when programming in C.

Avoid Storing Sensitive Data When Possible


The most effective way to safeguard sensitive data is to avoid storing it
altogether. For instance, when validating a password, you could hash the
input and compare it to a stored hash, thereby eliminating the need to store
the actual password.
This example uses the OpenBSD implementation of bcrypt, which is
available on many Unix-like systems.

C:

#include <stdio.h>
#include <bcrypt.h>

// Function to hash a password and store the hash


void store_password_hash(const char *password, char *stored_hash) {
// Generate a salt. The '10' is the log2 of the number of rounds of hashing to apply
char salt[BCRYPT_HASHSIZE];
bcrypt_gensalt(10, salt);

// Generate the hash


bcrypt_hashpass(password, salt, stored_hash, BCRYPT_HASHSIZE);
}

// Function to check a password against a stored hash


int check_password(const char *password, const char *stored_hash) {
char hash[BCRYPT_HASHSIZE];
bcrypt_hashpass(password, stored_hash, hash, BCRYPT_HASHSIZE);

// Compare the hashes


if (strcmp(hash, stored_hash) == 0) {
return 1; // Password is correct
} else {
return 0; // Password is incorrect
}
}

int main() {
char stored_hash[BCRYPT_HASHSIZE];

// Store the hash of the password


store_password_hash("my_password", stored_hash);

// Check a correct password


if (check_password("my_password", stored_hash)) {
printf("Password is correct\n");
} else {
printf("Password is incorrect\n");
}

// Check an incorrect password


if (check_password("wrong_password", stored_hash)) {
printf("Password is correct\n");
} else {
printf("Password is incorrect\n");
}
return 0;
}

In this example, the `bcrypt_gensalt` function generates a salt, which is


used by the `bcrypt_hashpass` function to hash the password. The salt is
included in the resulting hash, so it can be used to check passwords later.
When checking a password, the `bcrypt_hashpass` function hashes the input
password with the salt from the stored hash, and the result is compared to
the stored hash.
This is a very basic example and real-world password handling might
involve additional measures, such as securely handling the user input and
protecting the stored hashes.

Use Secure Memory Functions


Certain libraries offer functions for secure memory storage. For example,
the OpenSSL library provides the `OPENSSL_cleanse` function, which
securely erases memory.
This function is designed to avoid being optimized out by the compiler,
which can happen with `memset`.
Here's an example of how you might use `OPENSSL_cleanse`:

C:

#include <stdio.h>
#include <string.h>
#include <openssl/crypto.h>

int main() {
char sensitive_data[] = "This is some sensitive data";

printf("Before cleanse: %s\n", sensitive_data);

// Use OPENSSL_cleanse to securely wipe the memory


OPENSSL_cleanse(sensitive_data, sizeof(sensitive_data));
// Now the memory should be wiped
printf("After cleanse: %s\n", sensitive_data);

return 0;
}

In this example, `OPENSSL_cleanse` is used to wipe the memory that was


used to store a sensitive string. After the call to `OPENSSL_cleanse`, the
memory is filled with zeros and the sensitive data is no longer in memory.
Note that this is a very simple example and real-world usage might be more
complex. For example, you might need to ensure that the sensitive data isn't
copied or moved around in memory before it's wiped. Also, keep in mind
that `OPENSSL_cleanse` only wipes the memory in your program's address
space, so it won't help if an attacker has access to your system's physical
memory or swap space.

Overwrite Sensitive Data


Once you have finished using sensitive data, overwrite it with new data.
This can help prevent the sensitive data from remaining in memory. In C,
you can use the `memset` function for this purpose. However, be aware that
some compilers might optimize out calls to `memset` if they determine that
the data is not used afterwards. To prevent this, you can create a secure
version of `memset` that the compiler can't optimize out.
Here's a simple example using the `memset` function:

C:

#include <stdio.h>
#include <string.h>

int main() {
char sensitive_data[] = "This is some sensitive data";

printf("Before memset: %s\n", sensitive_data);


// Use memset to overwrite the memory
memset(sensitive_data, 0, sizeof(sensitive_data));

// Now the memory should be overwritten


printf("After memset: %s\n", sensitive_data);

return 0;
}

In this example, `memset` is used to overwrite the memory that was used to
store a sensitive string. After the call to `memset`, the memory is filled with
zeros and the sensitive data is no longer in memory.
However, there's a catch: some compilers might optimize out the call to
`memset` if they see that the data isn't used afterwards. To prevent this, you
can create a secure version of `memset` that the compiler can't optimize out.
Here's an example:

C:

#include <stdio.h>
#include <string.h>

// A secure version of memset that won't be optimized out


void *secure_memset(void *v, int c, size_t n) {
volatile char *p = v;
while (n--) {
*p++ = c;
}
return v;
}

int main() {
char sensitive_data[] = "This is some sensitive data";

printf("Before memset: %s\n", sensitive_data);


// Use secure_memset to overwrite the memory
secure_memset(sensitive_data, 0, sizeof(sensitive_data));

// Now the memory should be overwritten


printf("After memset: %s\n", sensitive_data);

return 0;
}

In this example, `secure_memset` uses a volatile pointer to prevent the


compiler from optimizing out the memory overwrite.

Use Volatile Keyword


The `volatile` keyword in C informs the compiler that a variable's value can
be altered in ways not explicitly specified by the program. This can prevent
the compiler from optimizing out code that manipulates sensitive data.
Here's a simple example:

C:

#include <stdio.h>

int main() {
volatile char sensitive_data[] = "This is some sensitive data";

printf("Before modification: %s\n", sensitive_data);

// Modify the sensitive data


for (int i = 0; sensitive_data[i] != '\0'; i++) {
sensitive_data[i] = '*';
}

// Now the sensitive data should be modified


printf("After modification: %s\n", sensitive_data);
return 0;
}

In this example, `sensitive_data` is declared as `volatile`. This tells the


compiler that the value of `sensitive_data` can change, and so the compiler
won't optimize out the loop that modifies `sensitive_data`.
However, keep in mind that `volatile` only affects compiler optimizations.
It doesn't prevent sensitive data from being left in memory or swapped to
disk, and it doesn't prevent other processes or hardware from accessing the
memory. For these reasons, `volatile` should be just one part of your
strategy for handling sensitive data in memory.

Use Memory Protection


If your system supports it, you can use memory protection mechanisms to
safeguard sensitive data. For instance, you can use the `mprotect` function
on Unix-like systems to alter the protection of a memory region.
For example, you might use `mprotect` to make a region of memory read-
only while you're not using it, which can help protect it from being
overwritten.
Here's a simple example:

C:

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

int main() {
// Allocate a page of memory
size_t pagesize = sysconf(_SC_PAGESIZE);
char *buffer = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANON |
MAP_PRIVATE, -1, 0);

strcpy(buffer, "This is some sensitive data");

printf("Before mprotect: %s\n", buffer);

// Use mprotect to make the memory read-only


if (mprotect(buffer, pagesize, PROT_READ) == -1) {
perror("mprotect");
return 1;
}

// Now the memory should be read-only, and this strcpy should fail
strcpy(buffer, "This will fail");

printf("After mprotect: %s\n", buffer);

return 0;
}

In this example, `mmap` is used to allocate a page of memory, and


`mprotect` is used to make the memory read-only. After the call to
`mprotect`, attempts to write to the memory (like the `strcpy` call) will fail.
However, keep in mind that `mprotect` only changes the protection of the
memory in your program's address space. It doesn't prevent other processes
or hardware from accessing the memory, and it doesn't prevent the memory
from being swapped to disk. For these reasons, `mprotect` should be just
one part of your strategy for handling sensitive data in memory.

Avoid Storing Sensitive Data in Swap Space


If your program's memory is swapped out to disk, sensitive data could end
up in an unencrypted swap file. You can use the `mlock` function on Unix-
like systems to prevent a memory region from being swapped to disk.
Here's a simple example:
C:

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

int main() {
// Allocate a page of memory
size_t pagesize = sysconf(_SC_PAGESIZE);
char *buffer = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANON |
MAP_PRIVATE, -1, 0);

strcpy(buffer, "This is some sensitive data");

printf("Before mlock: %s\n", buffer);

// Use mlock to prevent the memory from being swapped to disk


if (mlock(buffer, pagesize) == -1) {
perror("mlock");
return 1;
}

printf("After mlock: %s\n", buffer);

return 0;
}

In this example, `mmap` is used to allocate a page of memory, and `mlock`


is used to prevent the memory from being swapped to disk. After the call to
`mlock`, the memory should stay in RAM and not be written to the swap
file.
However, keep in mind that `mlock` only prevents the memory from being
swapped to disk. It doesn't prevent other processes or hardware from
accessing the memory, and it doesn't change the protection of the memory.
For these reasons, `mlock` should be just one part of your strategy for
handling sensitive data in memory.
Also, note that calling `mlock` requires the `CAP_IPC_LOCK` capability
on Linux, or appropriate privileges on other systems. This means that you
might need to run your program as root, or give it the necessary capabilities
using a tool like `setcap`.

Limit Exposure of Sensitive Data


Try to minimize the duration that sensitive data is stored in memory. For
example, you might be able to read sensitive data, use it, and then
immediately overwrite it, rather than storing it for the duration of your
program, after the sensitive data gets processed it can be overwrite with the
secure_memset function presented earlier.

Remember, security is a complex field and these tips are merely a starting
point. Depending on your specific use case, you might need to implement
additional measures to secure your data.

OceanofPDF.com
3.21 REGULAR
EXPRESSIONS WITH
ONIGURUMA LIBRARY
Regular expressions provide a powerful way to pattern-match within
strings. They're widely used for string manipulation in various
programming languages. The Oniguruma library is a popular regular
expression library for C that supports various character encodings,
including ASCII, UTF-8, UTF-16, and UTF-32.

Installing the Oniguruma Library


To use the Oniguruma library in your C programs, you first need to install
it. On many Linux distributions, you can install it using the package
manager. For example, on Ubuntu, you can install it with `apt`:

bash:

sudo apt-get install libonig-dev

If your platform doesn't provide a package for Oniguruma, you can


download the source code from the [official GitHub repository]
(https://github.com/kkos/oniguruma) and compile it yourself. After
successful installation, you will be able to include the library in your C
source code with `#include <oniguruma.h>`.

Setting up a CMake Project with Oniguruma


To set up a CMake project using the Oniguruma library, you need to add the
library's include and lib directories to your CMakeLists.txt file. Here is an
example:

cmake:

cmake_minimum_required(VERSION 3.22)
project(untitled C)

set(CMAKE_C_STANDARD 17)

include_directories(/user/include)
link_directories(/usr/lib/x86_64-linux-gnu)

add_executable(untitled main.c)

target_link_libraries(untitled onig)

In this example, we're including the Oniguruma header files from


`/usr/include` and the libraries from `/usr/lib/x86_64-linux-gnu`. These
paths might differ on your system, so be sure to adjust them accordingly.
The `add_executable` command specifies the main source file for the
executable, in this case, `main.c`. The `target_link_libraries` command links
the `onig` library to the executable.

Using Regular Expressions with Oniguruma


Here is an example of how to use the Oniguruma library in a C program:

C:

#include <stdio.h>
#include <string.h>
#include <oniguruma.h>

int main()
{
OnigRegex regex;
OnigErrorInfo einfo;
OnigRegion *region;
UChar* pattern = (UChar* )"\\bПривет\\b"; // matches the word "Привет" (Russian for "Hello")
as a whole word
UChar* str = (UChar* )"Here is text in Russian: Привет, это пример использования
регулярных выражений с UTF-8 в C.";

OnigEncoding use_encodings[] = { ONIG_ENCODING_UTF8 };


onig_initialize(use_encodings, sizeof(use_encodings)/sizeof(use_encodings[0]));

int r = onig_new(&regex, pattern, pattern + strlen((char* )pattern),


ONIG_OPTION_DEFAULT, ONIG_ENCODING_UTF8,
ONIG_SYNTAX_DEFAULT, &einfo);
if (r != ONIG_NORMAL) {
char s[ONIG_MAX_ERROR_MESSAGE_LEN];
onig_error_code_to_str((UChar* )s, r, &einfo);
fprintf(stderr, "ERROR: %s\n", s);
return -1;
}

region = onig_region_new();

r = onig_search(regex, str, str + strlen((char* )str), str, str + strlen((char* )str),


region, ONIG_OPTION_NONE);
if (r >= 0) {
int i;
printf("Match at %d\n", r);
for (i = 0; i < region->num_regs; i++) {
UChar* start = str + region->beg[i];
UChar* end = str + region->end[i];
printf("%d: (%d-%d): ", i, region->beg[i], region->end[i]);
while (start < end) printf("%c", *start++);
printf("\n");
}
}
else if (r == ONIG_MISMATCH) {
printf("No match\n");
}
else { /* error */
char s[ONIG_MAX_ERROR_MESSAGE_LEN];
onig_error_code_to_str((UChar* )s, r);
fprintf(stderr, "ERROR: %s\n", s);
onig_region_free(region, 1 /* 1:free self, 0:free contents only */);
onig_free(regex);
onig_end();
return -1;
}

onig_region_free(region, 1 /* 1:free self, 0:free contents only */);


onig_free(regex);
onig_end();
return 0;
}

This program uses the Oniguruma library to search for the whole word
"Привет" in a string. The search is case sensitive and supports UTF-8
encoding. If the search is successful, the program prints the match's position
and the matched substring. If the search fails, the program prints an error
message or "No match" depending on whether there was a mismatch or
another error.
The `onig_initialize` function initializes the Oniguruma library and sets the
encoding for the regular expressions. The `onig_new` function compiles a
regular expression. The `onig_search` function searches a string for a match
with the compiled regular expression. The `onig_region_new` function
creates a new region for storing the search results. After using the region
and the regex, we release the resources by calling `onig_region_free` and
`onig_free`.

With the help of the Oniguruma library, you can easily add support for
regular expressions in your C programs.
OceanofPDF.com
3.22 EMBEDDING
ASSEMBLY LANGUAGE
IN C
In this chapter, we will discuss how to embed assembly language in C. This
technique, known as "inline assembly," can be used to execute low-level
tasks more quickly or to use architecture-specific instructions not accessible
from C. This is achieved by using the `asm` keyword in GCC or `__asm__`
in Microsoft's compilers.
However, please keep in mind that the use of inline assembly should be
minimized and only used when it is absolutely necessary. Inlining assembly
makes your code less portable, harder to understand, and often harder to
maintain.
Let's start with a very basic example:

Basic inline assembly

C:

#include <stdio.h>

int main() {
__asm__("nop"); // This assembly instruction does nothing
printf("Hello, world!\n");
return 0;
}

In this case, the assembly instruction `nop` (which stands for "no
operation") does nothing. It's just an example to show the syntax.
Inline assembly with output
Inline assembly can also output results. Let's consider the example below:

C:

#include <stdio.h>

int main() {
int result;
__asm__ ("movl $5, %0"
: "=r" (result) // output list
: // input list
: // clobber list
);

printf("The result is %d\n", result);


return 0;
}

In this case, we are moving the constant value 5 to the variable `result`. The
`=r` tells the compiler to use any register to store the output, which is
`result` in this case.

Inline assembly with input and output


We can also pass input values to the assembly code. Here is an example:

C:

#include <stdio.h>

int main() {
int data = 5;
int result;
__asm__ ("addl %1, %0"
: "=r" (result)
: "r" (data), "0"(10)
:
);

printf("The result is %d\n", result);


return 0;
}

In this case, we are adding the value of `data` to the constant value 10 and
storing the result in the `result` variable.

The clobber list


Finally, we have the clobber list. This is where we list the registers that will
be modified by the assembly code. This is useful because it tells the
compiler not to store important values in these registers around the
assembly code.
Here's an example:

C:

#include <stdio.h>

int main() {
int result;
__asm__ ("movl $5, %%eax; movl %%eax, %0"
: "=r" (result)
:
: "%eax"
);

printf("The result is %d\n", result);


return 0;
}
In this case, we are specifying that the assembly code will modify the
`%eax` register.
This concludes our brief introduction to inline assembly in C. Remember
that while inline assembly can be powerful, it's usually best to stick to C
whenever possible for the sake of portability and readability.

OceanofPDF.com
3.23 EXTENDING JAVA
WITH USER-CREATED
LIBRARIES IN C
Java's JNI (Java Native Interface) allows Java code running in a Java
Virtual Machine (JVM) to call and be called by native applications and
libraries written in other languages, such as C, C++, and Assembly.
JNI is complex and can introduce tricky bugs, so it should be used sparingly
and with caution. However, it can be very useful for reusing existing native
code or for writing code that needs to interface directly with system
hardware or OS services.
Let's create a simple native method in C that we can call from Java.

Step 1: Create Java class with native methods


First, let's create a Java class that declares a native method. This method
will be implemented in C.

java:

public class NativeExample {


// Declare native method (and make it public to access it from other Java classes)
public static native void printHello();

// Load the library at runtime


static {
System.loadLibrary("native_example");
}
}
The `loadLibrary` call attempts to load a native library called
"native_example". This library must be located in the system's library path,
or an UnsatisfiedLinkError will be thrown.

Step 2: Generate JNI Header


Now, compile the Java class and use the `javah` command (included in the
JDK) to generate a JNI header file:

bash:

javac NativeExample.java
javah -jni NativeExample

The `javah` command will generate a header file called `NativeExample.h`.


This header file will contain the function signature that must be
implemented in our native C code.

Step 3: Implement the native method in C


Next, create a new C file and implement the native method. The function
name should match exactly the one in the `NativeExample.h` file.

C:

#include <jni.h>
#include <stdio.h>
#include "NativeExample.h"

JNIEXPORT void JNICALL


Java_NativeExample_printHello(JNIEnv* env, jclass class) {
printf("Hello from native code!\n");
}

Step 4: Compile and create shared library


The next step is to compile this C file into a shared library. This step can
vary depending on the OS and the compiler you are using.
For Unix-based systems (like Linux, MacOS), you can use gcc:

bash:

gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libnative_example.so


NativeExample.c

For Windows, you can use MinGW or Cygwin:

bash:

gcc -I"%JAVA_HOME%/include" -I"%JAVA_HOME%/include/win32" -shared -o


native_example.dll NativeExample.c

Remember to replace `$JAVA_HOME` with your actual path to JDK.

Step 5: Call the native method from Java


Now, we can call this native method from Java like any other static method:

java:

public class Main {


public static void main(String[] args) {
NativeExample.printHello();
}
}

When you run this `Main` class, it will print out: "Hello from native code!".
This concludes our chapter on extending Java with user-created libraries in
C. The main takeaway here is that while Java is a powerful language on its
own, there are times when native methods can provide the functionality that
Java alone cannot. With careful use, native methods can make your Java
applications more versatile and capable.

OceanofPDF.com
3.24 EXTENDING PYTHON
WITH USER-CREATED
LIBRARIES IN C
Python is a versatile language that is often used for scripting, automation,
data analysis, machine learning, and much more. While Python is powerful
and expressive, there are times when it may be necessary to use C for
efficiency, or to leverage existing C libraries. Thankfully, Python provides
ways to extend its capabilities with C. In this chapter, we'll use the
Python/C API to create a simple extension module.

Step 1: Write the C code


Let's start by writing a simple C function that we'll expose to Python. For
this example, we'll write a function that takes two integers and returns their
sum.

C:

#include <Python.h>

// The function to be called from Python


static PyObject* py_myFunction(PyObject* self, PyObject* args)
{
int x, y;
PyArg_ParseTuple(args, "ii", &x, &y);
return Py_BuildValue("i", x + y);
}

// Another function to be called from Python


static PyObject* py_myOtherFunction(PyObject* self, PyObject* args)
{
double x, y;
PyArg_ParseTuple(args, "dd", &x, &y);
return Py_BuildValue("d", x*y);
}

// Binding the above functions to Python


static PyMethodDef myMethods[] = {
{ "myFunction", py_myFunction, METH_VARARGS, "Calculating the sum" },
{ "myOtherFunction", py_myOtherFunction, METH_VARARGS, "Multiply two floats" },
{ NULL, NULL, 0, NULL }
};

// Define the Python module


static struct PyModuleDef myModule = {
PyModuleDef_HEAD_INIT,
"myModule", "This is a demo module",
-1,
myMethods
};

// Initializes the module


PyMODINIT_FUNC PyInit_myModule(void)
{
return PyModule_Create(&myModule);
}

In this example, `myFunction` and `myOtherFunction` are the functions


that will be exposed to Python. They take a `self` parameter, which for
functions in modules is always NULL, and `args`, which are the arguments
passed from Python. `PyArg_ParseTuple` is used to parse Python
arguments into C variables. `Py_BuildValue` is used to create a Python
value that will be returned. `myMethods` is an array of `PyMethodDef`
structs, which map the function names as Python strings to the C functions.
`myModule` is a `PyModuleDef` struct that defines the module.
Step 2: Build the C code
The next step is to build the C code. This can be done using the `distutils`
package in Python's standard library, which simplifies the process. We need
to create a `setup.py` script.

python:

from distutils.core import setup, Extension

module = Extension('myModule', sources = ['mymodule.c'])

setup(name = 'MyModuleName',
version = '1.0',
description = 'This is a package for myModule',
ext_modules = [module])

This script will be used by `distutils` to compile and link your C code into a
shared library that Python can import. Run the script with the following
command:

bash:
python3 setup.py build

This command will create a build directory with your compiled module.

Step 3: Import and use the C extension in Python


After building the C code, you can import it into Python using the import
statement just like any other module.

python:

import myModule
print(myModule.myFunction(5, 7))
print(myModule.myOtherFunction(3.0, 2.0))

If everything is set up correctly, the output will be:

bash:

12
6.0

This concludes our chapter on extending Python with user-created libraries


in C. As with Java's JNI, Python's C API is a powerful tool that should be
used carefully, but it provides a great deal of flexibility and can help you
optimize your Python code or leverage existing C libraries.

OceanofPDF.com
3.25 EXECUTING SHELL
COMMANDS IN C
Executing shell commands in a C program can sometimes be necessary,
such as when you need to interface with the system, perform a specific
operation, or use functionality that isn't directly available in C.
There are multiple ways to execute shell commands in C, but in this
chapter, we will focus on the `system()` function.

Using `system()` function


The `system()` function is part of the C Standard Library, and it allows a C
program to run shell commands. It's included in the `stdlib.h` header file.
Here's an example:

C:

#include <stdlib.h>

int main()
{
// run the command "ls -l" in the subshell
int result = system("ls -l");

if (result != 0) {
printf("The command could not be executed\n");
}

return 0;
}
In this example, `system("ls -l")` runs the `ls -l` command in a subshell,
which lists the files and directories in the current directory in long format.
The `system()` function returns a status code. A zero status code means the
command was executed successfully. A non-zero status code indicates an
error.

Pros and Cons of Using `system()`


Using `system()` to execute shell commands in a C program can be quite
powerful. It can be used to achieve almost anything that a shell script can
do. However, this approach comes with a number of drawbacks:

Pros
1. Simplicity: The `system()` function is easy to use. It allows you to run
complex shell commands with a single function call.
2. Availability: The `system()` function is part of the C Standard Library
and is available on virtually all systems that support C.

Cons
1. Security Risks: The `system()` function can be a potential security risk. If
you're executing user-provided strings, the user could potentially provide a
string that executes malicious commands.
2. Performance Overhead: When `system()` is called, the operating system
needs to spawn a new process to execute the shell command. This can be
expensive, particularly if the `system()` function is used in a loop or
frequently called.
3. Error Handling: If the shell command fails, the `system()` function only
returns a status code. It doesn't provide any detailed error messages that
could help you debug the issue.
4. Portability: Shell commands may not work the same way on all systems.
A shell command written for a Linux system may not work on a Windows
system, and vice versa.
In conclusion, while `system()` can be a useful tool for executing shell
commands in C, its usage should be carefully considered due to the
potential drawbacks. When possible, it's often better to use platform-
specific APIs or libraries to perform system-level operations.

OceanofPDF.com
3.26 DETECTING THE
OPERATING SYSTEM IN C
It is often required to know the operating system (OS) on which a C
program is running. This information can be used to write portable code
that behaves differently on different OSes. In C, preprocessor macros are
commonly used for this purpose. These macros are defined by the compiler
depending on the target OS.
Let's look at how we can use these macros to determine the OS:

C:

#include <stdio.h>

int main() {
#ifdef _WIN32
printf("Windows OS\n");
#elif __APPLE__
printf("Mac OS\n");
#elif __linux__
printf("Linux OS\n");
#elif __unix__ // all unices not caught above
printf("Unix OS\n");
#else
printf("Unknown OS\n");
#endif
return 0;
}
In this code, `#ifdef`, `#elif`, `#else`, and `#endif` are preprocessor
directives. They check whether certain macros are defined and insert
different code accordingly.
Here's what each macro signifies:
- `_WIN32`: Defined for both 32-bit and 64-bit environments on Windows.
- `__APPLE__`: Defined on Mac OS. Sometimes, you may also need to
check `__MACH__` to ensure you're on a Mac as opposed to another Apple
OS.
- `__linux__`: Defined for Linux.
- `__unix__`: Defined for Unix-based systems.

These preprocessor directives are evaluated at compile time, not runtime.


Therefore, they don't add any runtime overhead to your program. However,
the program needs to be recompiled for each target OS.
Please note that the exact macros and their values can vary depending on
the compiler and the system. Always check the documentation of your
compiler for the most accurate information.

OceanofPDF.com
3.27 ACCESSING SPECIFIC
MEMORY LOCATIONS IN
C
Accessing specific memory locations, like video memory, directly from a C
program can be a challenging task, especially under the supervision of
modern operating systems like Linux, Windows, or macOS. These OSes
include memory protection mechanisms that prevent processes from
arbitrarily accessing memory, preventing potential conflicts and securing
the system. However, under certain conditions, such as writing a device
driver or operating in a bare-metal environment, direct memory access
might be achievable.

Direct Memory Access in C


In C, you can attempt to access a specific memory location by casting an
integer to a pointer. This integer would be the memory address you want to
access. Here's a simplified example:

C:
int* ptr = (int*)0x12345678; // This is an arbitrary memory address.

With this line of code, you now have a pointer (`ptr`) that points to the
memory address `0x12345678`. However, whether or not you can actually
read or write to this address depends on the memory protection mechanisms
of your operating system and hardware.

Accessing Video Memory


Video memory access can be a bit more involved. On older hardware or in
certain restricted environments, the video memory might be directly
accessible at a specific address. For instance, with standard VGA in text
mode, video memory begins at address `0xB8000`.
Here's a hypothetical example of writing to VGA text-mode video memory:

C:

volatile char* video_memory = (volatile char*) 0xB8000;

void print_char(int x, int y, char c, char color) {


video_memory[(y * 80 + x) * 2] = c;
video_memory[(y * 80 + x) * 2 + 1] = color;
}

int main() {
print_char(0, 0, 'A', 0x07);
return 0;
}

In this example, we're interpreting video memory as an array of characters.


Each screen character consumes two bytes of video memory: one for the
character itself and one for the attribute (including color). Therefore, we
multiply the coordinates by 2 and add 1 for the color byte.
Please note, the above example is purely illustrative and assumes an 80x25
text mode. It won't work in a modern operating system or with a modern
graphics card due to memory protection and the complexity of modern
graphics systems.

Modern Practices
In a contemporary environment, graphics programming usually involves a
library or an API such as SDL, OpenGL, or DirectX, which abstract away
the low-level details and provide a way to interact with the graphics
hardware in a more secure and portable way.
Attempting to access memory directly is generally discouraged because it
can lead to undefined behavior, including crashes, data corruption, and
security vulnerabilities. In most cases, you should use the facilities provided
by your programming language and operating system to interact with
memory and hardware.
In summary, accessing specific memory locations in C is a task that requires
a thorough understanding of both the language and the operating system's
memory management principles. It is a powerful tool when used
appropriately but should be handled with care due to its inherent complexity
and potential pitfalls.

OceanofPDF.com
CHAPTER 4: THE POWER
OF 10: RULES FOR
DEVELOPING SAFETY-
CRITICAL CODE
In the world of programming, certain applications are deemed 'safety-
critical'. These are systems where failure could result in loss of life,
significant property damage, or damage to the environment. Examples
include software for aviation, medical devices, nuclear power plants, and
automotive safety systems. When developing such systems, it's crucial to
follow stringent coding practices to ensure the highest level of safety and
reliability.
In this chapter, we will explore the "Power of 10: Rules for Developing
Safety-Critical Code". These rules were proposed by Gerard J. Holzmann of
the NASA/JPL Laboratory for Reliable Software. The rules are designed to
significantly reduce the number of programming errors in code that is
critical to the safety of a system.
We will begin by introducing you to the concept of safety-critical code and
explaining why it's important. We will then delve into each of the Power of
10 rules, discussing their implications and how they can be applied in C
programming. These rules cover a range of topics, from the use of
preprocessor directives to the control of dynamic memory allocation, and
each one plays a vital role in ensuring the reliability of safety-critical
systems.
Following that, we will explore how these rules can be applied in practice,
using real-world examples and case studies. We will also discuss how to
balance the need for safety with other considerations such as performance
and functionality.
By the end of this chapter, you will have a deep understanding of the Power
of 10 rules and be able to apply them in your own programming practice.
Whether or not you plan to work on safety-critical systems, these rules
provide valuable guidance that can help you write more reliable and robust
code. So, let's delve into the world of safety-critical programming and
discover the power of these ten rules!

OceanofPDF.com
4.1 INTRODUCTION TO
SAFETY-CRITICAL CODE
Safety-critical code refers to software that is designed to ensure safety in
systems where malfunction could result in injury or loss of life. Examples
of such systems include aircraft flight control, medical devices, nuclear
power plants, and self-driving cars. Given the high stakes involved, it's
crucial that safety-critical code is reliable, robust, and free from errors.
One approach to developing safety-critical code is outlined in "The Power
of 10: Rules for Developing Safety-Critical Code", a set of rules proposed
by NASA's Jet Propulsion Laboratory for writing safety-critical code. These
rules are designed to minimize the chance of errors and make the code
easier to review and analyze.
In this chapter, we will explore these rules in detail, discussing their
rationale and how they can be applied in practice. We will also discuss the
broader context of safety-critical software development, including the
importance of rigorous testing, formal methods, and adherence to industry
standards.
By understanding and applying these principles, you can write C code that
is not only efficient and performant, but also reliable and safe, even in the
most critical applications.

OceanofPDF.com
4.2 UNDERSTANDING THE
POWER OF 10 RULES
"The Power of 10: Rules for Developing Safety-Critical Code" is a set of
guidelines proposed by Gerard J. Holzmann of NASA's Jet Propulsion
Laboratory. These rules are designed to eliminate certain coding practices
that make code difficult to review or statically analyze, thus enhancing the
reliability and safety of the code. Here's a summary of these rules:
1. Restrict all code to very simple control flow constructs. Do not use
`goto` statements, `setjmp` or `longjmp` constructs, or direct or indirect
recursion.
This rule stipulates avoiding the use of `goto` statements, `setjmp` or
`longjmp` constructs, and recursion (both direct and indirect).
Let's look at some examples:
1.1. `goto` statements: The `goto` statement allows for arbitrary jumps in
code, which can lead to what is often referred to as "spaghetti code" —
code that is difficult to read, follow, and debug due to complex control
flows.
Here is an example of a `goto` statement in C:

C:

#include <stdio.h>

int main() {
int a = 10;
LOOP:do {
if(a == 15) {
/* skip the iteration */
a = a + 1;
goto LOOP;
}
printf("value of a: %d\n", a);
a++;
}while(a < 20);

return 0;
}

This code jumps back to the start of the loop whenever `a` equals 15,
skipping the `printf` statement. However, according to the Power of 10
rules, this kind of construct should be avoided because it can make code
more complex and harder to understand or statically analyse.
Instead, we should use simple control flow constructs. The equivalent,
Power of 10 compliant code might look like this:

C:

#include <stdio.h>

int main() {
for(int a = 10; a < 20; ++a) {
if(a == 15) {
continue; // skip the iteration
}
printf("value of a: %d\n", a);
}

return 0;
}

1.2. `setjmp` and `longjmp` constructs: These are used in C for non-local
jumps, another form of control flow that Power of 10 advises against
because it makes static analysis and code review difficult.
1.3. Recursion: Both direct and indirect recursion should be avoided. Direct
recursion is when a function calls itself, and indirect recursion is when a
function is called by another function that it had previously called.
Here's an example of direct recursion, a function to calculate factorial:

C:

int factorial(int n) {
if(n == 0) {
return 1;
} else {
return n * factorial(n-1);
}
}

Instead of recursion, we should use iterative constructs. The equivalent,


Power of 10 compliant code might look like this:

C:

int factorial(int n) {
int result = 1;
for(int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}

In all of these examples, following the Power of 10 rule simplifies the


control flow and makes the code easier to read and understand, which is
especially important in safety-critical contexts.
2. Give all loops a fixed upper bound. This ensures that all loops in the
program will terminate, preventing potential infinite loops that could cause
system hang-ups.
This rule is designed to ensure that all loops in the program will terminate,
thus preventing potential infinite loops that could cause system crashes or
hang-ups. This rule also helps in static analysis of code as it makes it easier
to prove properties of the code.
Here's an example of a loop without a fixed upper bound:

C:

int i = 0;
while (i >= 0) {
// Do something
i++;
}

In this example, the loop will never terminate because `i` will always be
greater than or equal to 0, resulting in an infinite loop. This is against the
Power of 10 rule.
Instead, loops should have a clear, fixed upper bound to ensure they always
terminate. Here's an example of a loop that follows this rule:

C:

for (int i = 0; i < 100; i++) {


// Do something
}

In this example, it's clear that the loop will run exactly 100 times, as `i` is
incremented by 1 in each iteration and the loop continues as long as `i` is
less than 100. Thus, it has a fixed upper bound and will always terminate,
which is in line with the Power of 10 rule.
It's worth noting that the "fixed upper bound" doesn't necessarily mean a
hardcoded number. It can also be a variable or constant whose value is
determined before the loop begins, and does not change during the loop
execution. For example:

C:

int n = get_some_value();
for (int i = 0; i < n; i++) {
// Do something
}

In this case, as long as the value of `n` is determined before the loop starts
and doesn't change during the loop, this is still compliant with Rule 2. The
important part is that the number of iterations is fixed when the loop starts.

3. Do not use dynamic memory allocation after initialization. Dynamic


memory allocation can lead to issues like memory leaks or dangling
pointers, which can cause unpredictable behavior.
Dynamic memory allocation is a feature in many programming languages,
including C, that allows the program to request memory from the system
during runtime. However, the misuse of dynamic memory allocation can
lead to issues like memory leaks, where allocated memory is not properly
deallocated, and dangling pointers, where pointers point to memory that has
already been deallocated. These issues can cause unpredictable behavior
and make the code hard to review or analyze statically.
Here is an example of dynamic memory allocation in C:

C:

#include <stdlib.h>

int main() {
// Dynamically allocate memory for an integer
int* ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
// Handle error
return -1;
}

// Use the allocated memory


*ptr = 10;

// Don't forget to free the memory


free(ptr);

// Using the memory after it has been freed is dangerous!


*ptr = 20; // Undefined behavior!

Return 0;
}

In the above example, memory is allocated during the runtime of the


program, not just during initialization. Additionally, after the memory is
freed with `free(ptr)`, the code attempts to use the memory again, leading to
undefined behavior. This kind of code would violate Rule 3 of the Power of
10 rules.
Instead, memory should be allocated statically or automatically, and it
should only be done during the initialization phase of the program. Here's
an example that follows Rule 3:

C:

int main() {
// Automatically allocate memory for an integer
int value = 10;

// Use the allocated memory


value = 20;
return 0;
}

In this code, memory is allocated automatically when `value` is declared,


and there is no risk of memory leaks or dangling pointers because the
memory management is handled by the system. The code is simpler and
safer, and it complies with the Power of 10 rules.

4. No function should be longer than what can be printed on a single


sheet of paper in a standard reference format with one line per
statement and one line per declaration. Typically, this means no more
than about 60 lines of code per function.
The main idea behind this rule is to keep functions short, simple, and
readable. A function that fits within about 60 lines (or roughly a printed
page) is easier to understand, analyze, and debug. It also encourages good
design principles like modularity and reusability, as a function should
ideally perform a single task or function.
Here is an example of a function that would not comply with this rule due
to its length:

C:

void longFunction() {
int i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
// more variables

// Do lots of things
for(i = 0; i < 100; i++) {
// lots of code here...
}

// More code here...


// ...
// This function is too long and does too many things!
}

In the above example, the function `longFunction` is meant to represent a


function that is too long and complex. Although the specifics aren't filled
out, the idea is that there are too many variables and too many tasks being
performed within a single function, making it difficult to read, analyze, and
debug.
Here is an example of how you might refactor this into shorter, simpler
functions that comply with Rule 4:

C:

void doTask1(int i) {
// Code for task 1...
}

void doTask2(int i) {
// Code for task 2...
}

// ... More functions for each task ...

void mainFunction() {
int i;
for(i = 0; i < 100; i++) {
doTask1(i);
doTask2(i);
// Call more functions as needed...
}
}

In this refactored example, each task that was previously performed within
`longFunction` is now performed by a separate function. The
`mainFunction` calls these smaller functions as needed. This makes the
code much easier to read, understand, and debug, and it complies with the
Power of 10 rules.

5. The assertion density of the code should average to a minimum of


two assertions per function. Assertions are statements used to test
assumptions made by the program and can help catch unexpected behavior.
Assertions are statements used to test assumptions made by the program
and can help catch unexpected behavior."
The use of assertions in programming is a powerful tool to increase the
robustness of code. Assertions are used to set conditions in the code that
must be true for the program to run correctly. If an assertion condition
evaluates to false during the execution of the program, the program will
typically stop running and provide an error message. This helps to identify
bugs and incorrect assumptions made during the coding process.
Here's an example of a C function with assertions:

C:

#include <assert.h>

void processArray(int* arr, size_t arr_size) {


assert(arr != NULL); // Assertion 1: The array must not be NULL.
assert(arr_size > 0); // Assertion 2: The array size must be greater than 0.

// Process the array...


for (size_t i = 0; i < arr_size; i++) {
assert(arr[i] >= 0); // Assertion 3: Array elements must be non-negative.
// Do something with arr[i]
}
}

In this example, `processArray` contains three assertions. The first two


assertions check that the array to be processed is not NULL and that its size
is greater than 0. Without these assertions, if the function was called with a
NULL array or a zero-size array, it could lead to undefined behavior. The
third assertion ensures that all elements of the array are non-negative. If the
function assumes this in its processing of the array, this assertion can help
catch any violations of this assumption.
By following Rule 5 and including a minimum of two assertions per
function on average, we can catch many common programming mistakes
and erroneous assumptions before they cause difficult-to-diagnose bugs. It
is a simple and effective way to increase the reliability and safety of the
code.

6. Data objects must be declared at the smallest possible level of scope.


Scope, in C programming language, refers to the region of the code where a
variable can be accessed. By keeping the scope of a variable as small as
possible, we minimize the potential for unexpected side effects, improve the
maintainability of the code, and enhance readability as it's easier to
understand a variable's purpose when it's confined to a smaller scope.
Let's consider a simple example in C to illustrate this:

C:

#include <stdio.h>

void printValues() {
int i;
for(i = 0; i < 10; i++) {
printf("%d ", i);
}
printf("\n");
}

int main() {
printValues();
return 0;
}
In the code above, the variable `i` has a scope that extends across the entire
`printValues` function. However, `i` is only used in the `for` loop, so its
scope could be reduced, as per Rule 6:

C:

#include <stdio.h>

void printValues() {
for(int i = 0; i < 10; i++) {
printf("%d ", i);
}
printf("\n");
}

int main() {
printValues();
return 0;
}

In this improved version, `i` is declared inside the `for` loop. This reduces
the scope of `i` to just the loop, which is the smallest possible level of scope
for this variable. This approach ensures that the variable `i` can't be misused
or accidentally accessed outside the `for` loop, reducing the chance of bugs
and making the code safer and easier to understand.

7. The return value of non-void functions must be checked by each


calling function, and the validity of parameters must be checked inside
each function. The idea here is that every function that returns a value
should have its return value checked by the function that calls it.
Furthermore, functions should check the validity of their parameters. This
can prevent errors from propagating through the program and can also help
identify and handle unexpected or invalid input values.
Let's look at a simple example in C:
C:

#include <stdio.h>

int divide(int dividend, int divisor) {


if (divisor == 0) {
printf("Error: Division by zero is undefined.\n");
return -1;
}
return dividend / divisor;
}

int main() {
int result = divide(20, 0);
if (result == -1) {
printf("Division failed.\n");
return -1;
}
printf("The result is %d.\n", result);
return 0;
}

In the example above, the `divide` function checks whether the divisor is
zero before attempting the division. If the divisor is zero, it prints an error
message and returns -1 to indicate failure.
The `main` function, which calls `divide`, also checks the return value of
`divide`. If `divide` returns -1, `main` knows that the division failed, prints
an error message, and exits with a status of -1. This check ensures that the
program handles the error appropriately and doesn't continue executing with
incorrect or undefined values.
This approach helps create more robust and error-resistant code by ensuring
errors are handled promptly and close to the source.

8. The use of the preprocessor must be limited to the inclusion of


header files and simple macro definitions. Macro substitutions that are
more complex than a simple constant or function call are not allowed.
The preprocessor in C is a powerful tool that performs a variety of
operations before the actual compilation of code. These operations include
inclusion of header files, macro substitution, conditional compilation, and
line control. However, its misuse can lead to hard-to-diagnose bugs, such as
unintended side effects and obscure code.
As per the rule, preprocessor directives should be limited to simple
operations like including header files and defining simple macros. Complex
macro substitutions can obscure the true behavior of the code and should be
avoided.
Here's an example of what you should and should not do:

C:

// Good practice as per Rule 8:


#include <stdio.h>
#define PI 3.14159

int main() {
printf("Value of PI: %f\n", PI);
return 0;
}

// Bad practice as per Rule 8:


#define SQUARE(x) ((x) * (x))

int main() {
int a = 5;
printf("Square of a: %d\n", SQUARE(a)); // This seems fine...
printf("Square of a+1: %d\n", SQUARE(a+1)); // This is not fine - evaluates to 11, not 36!
return 0;
}
In the good practice, `#include <stdio.h>` is a simple header file inclusion,
and `#define PI 3.14159` is a simple macro definition. Both uses of the
preprocessor are straightforward and unlikely to cause confusion or bugs.
The bad practice defines a macro, `SQUARE(x)`, that squares its argument.
At a glance, this seems like a handy shortcut. However, when
`SQUARE(a+1)` is used, the macro expands to `((a+1) * (a+1))`, which is
not the same as `(a+1)*(a+1)`. This is because of operator precedence, the
multiplication is carried out before the addition. Hence, for `a=5`, it will
give `5+1*5+1=11`, not `36`. This kind of bug can be very hard to track
down.
The above rules limit the use of preprocessor directives to simple and
predictable constructs, making code more readable and less error-prone.

9. The use of pointers should be restricted. Specifically, no more than one


level of dereferencing is allowed, and pointer dereference operations may
not be hidden in macro definitions or inside typedef declarations.
This rule is to limit the complexity associated with pointers and to avoid
errors due to misunderstanding or misuse of them. Pointers in C are a
powerful but tricky feature. They can lead to hard-to-find bugs when
misused. By limiting dereferencing to only one level, the complexity of
pointer usage is reduced. Hiding dereference operations inside macros or
typedef declarations can lead to confusing and misleading code, which is
why it is discouraged.
Here's a simple example showing allowed and not allowed practices
according to this rule:

C:

// Allowed use of pointers as per Rule 9:

int main() {
int a = 10;
int *p = &a;
printf("Value of a: %d\n", *p); // Single level dereferencing
return 0;
}
// Not allowed use of pointers as per Rule 9:

int main() {
int a = 10;
int *p = &a;
int **pp = &p;
printf("Value of a: %d\n", **pp); // Double level dereferencing, not allowed.
Return 0;
}

In the allowed example, there is only a single level of dereferencing. The


pointer `p` points to `a`, and we print the value of `a` using `*p`. This is
simple and straightforward.
In the disallowed example, there are two levels of dereferencing. The
pointer `p` points to `a`, and `pp` points to `p`. So, when we print the value
of `a` using `**pp`, we have two levels of dereferencing (`pp` to `p`, and
`p` to `a`), which increases complexity and is not allowed according to this
rule.

10. All code must be compiled, from the first day of development, with
all compiler warnings enabled at the compiler’s most pedantic setting.
All code must compile without any warnings, and all code must be checked
daily with at least one, but preferably more than one, state-of-the-art static
source code analyzer and should pass the analyses with zero warnings.
This rule emphasizes that every piece of code should be clean with respect
to the compiler's most pedantic setting. This is to ensure that all potential
programming faults are exposed early and handled during development.
Also, using static analysis tools can detect issues that a compiler might not.
Static code analyzers scan your source code for potential bugs, errors, and
vulnerabilities and can help enforce coding rules and standards.
Consider this simple C code example:

C:

#include <stdio.h>
int main() {
int a;
printf("%d\n", a);
return 0;
}

In this code, we're using the uninitialized variable `a`. If we compile this
program with warnings enabled (e.g., by using the `-Wall` option in gcc,
which enables all warnings), we'll receive a warning about this.

Command: `gcc -Wall code.c`

Output: `warning: 'a' is used uninitialized in this function [-


Wuninitialized]`

By following Rule 10, we would need to correct this code to eliminate the
warning:

C:

#include <stdio.h>

int main() {
int a = 0;
printf("%d\n", a);
return 0;
}

Now the code compiles without any warnings.


The rule also encourages the use of static analysis tools. There are many
such tools available, like Splint, Cppcheck, PVS-Studio, etc. For example,
running the initial flawed code through a static analysis tool would give an
error or warning about the uninitialized variable, providing another layer of
error checking beyond the compiler's warnings.
It's important to mention that turning on all compiler warnings and using
static analysis tools doesn't guarantee the absence of bugs, but it
significantly helps in catching common errors and leads to more robust and
reliable code.

These rules are a complement to the MISRA C guidelines, a set of software


development guidelines for the C programming language developed by the
Motor Industry Software Reliability Association.
By adhering to these rules, developers can write safer, more reliable code,
reducing the likelihood of errors that could lead to system failures in safety-
critical applications.

OceanofPDF.com
CHAPTER 5:
OPTIMIZATION
TECHNIQUES,
DEBUGGING AND
TESTING
As you continue to hone your skills in C programming, it's important to
remember that writing code is not just about getting the program to work.
It's also about writing code that is clean, efficient, maintainable, and easy
for others (and your future self) to understand. In this chapter, we will share
some best practices and tips that will help you write high-quality C code.
We will begin by discussing code optimization techniques. While modern
compilers are quite good at optimizing code, understanding these
techniques can help you write code that is more efficient and easier for the
compiler to optimize.
Next, we will delve into debugging and testing your code. No matter how
careful you are, bugs are an inevitable part of programming. We will
discuss strategies for finding and fixing bugs, and for testing your code to
ensure it behaves as expected.
We will also cover some common pitfalls in C programming and how to
avoid them. These include things like memory leaks, buffer overflows, and
undefined behaviour. By being aware of these pitfalls, you can avoid many
common mistakes and write more robust code.
Finally, we will provide some general tips for writing clean, readable, and
maintainable code. This includes things like choosing meaningful variable
names, using comments effectively, and organizing your code in a logical
and consistent way.
By the end of this chapter, you will have a toolbox of strategies and
techniques for writing high-quality C code. So, let's dive in and learn how
to not just write code, but to craft code that is a pleasure to read and work
with!

OceanofPDF.com
5.1 CODE OPTIMIZATION
TECHNIQUES
Code optimization is the process of modifying your code to make it more
efficient. This can involve reducing the time it takes to execute, the amount
of memory it uses, or both. Here are some common techniques for
optimizing C code:
1. Loop Unrolling: Loop unrolling is a technique that reduces the overhead
of loop control instructions by increasing the number of operations per
iteration of the loop. This can lead to performance improvements, especially
in cases where the loop body is small and the overhead of the loop control
instructions is significant. However, it can increase the size of your code, so
it should be used judiciously.
Here's a simple example of a loop in C:

C:

for (int x = 0; x < 100; x++) {


delete(x);
}

This loop will iterate 100 times, performing some operation each time. The
overhead here comes from the incrementing of `x`, the comparison of `x`
with 100, and the jump back to the start of the loop. These operations are
performed 100 times.
Now, let's see an example of this loop unrolled:

C:
for (int x = 0; x < 100; x += 5 ) {
delete(x);
delete(x + 1);
delete(x + 2);
delete(x + 3);
delete(x + 4);
}

In this unrolled version of the loop, the loop control instructions are only
executed 25 times, but the operation inside the loop is still performed 100
times. This can lead to a performance improvement, as the overhead of the
loop control instructions is reduced.
However, it's important to note that loop unrolling can increase the size of
your code, as the same operation is repeated multiple times. This can lead to
issues with instruction cache misses, which can actually decrease
performance. Therefore, loop unrolling should be used judiciously, and the
performance of your code should be tested both with and without loop
unrolling to see if it actually provides a benefit.
Also, modern compilers often have options to automatically unroll loops, so
manual loop unrolling may not be necessary. It's always a good idea to
understand the optimization options of your compiler and use them where
appropriate.

2. Function Inlining: Function inlining is a technique where the compiler


replaces a function call with the body of the function itself. This can reduce
the overhead of function calls, as it eliminates the need for the program to
jump to a different location in memory to execute the function and then
jump back once the function is complete, but it can also increase the size of
your code.
Here's a simple example of a function in C:

C:
int add(int a, int b) {
return a + b;
}

int main() {
int result = add(5, 7);
// ...
}

In this example, every time the `add` function is called, the program has to
jump to the location in memory where the `add` function is defined, execute
the function, and then jump back to the location in the `main` function
where the `add` function was called.
Now, if we inline the `add` function, it might look something like this:

C:

#define add(a, b) ((a) + (b))

int main() {
int result = add(5, 7);
// ...
}

In this example, the `add` function is defined as a macro. When the program
is compiled, every instance of `add(a, b)` is replaced with `((a) + (b))`. This
eliminates the need for the program to jump to a different location in
memory to execute the `add` function, which can lead to a performance
improvement.
However, it's important to note that function inlining can increase the size
of your code, as the body of the function is duplicated at each point where
the function is called. This can lead to issues with instruction cache misses,
which can actually decrease performance. Therefore, function inlining
should be used judiciously, and the performance of your code should be
tested both with and without function inlining to see if it actually provides a
benefit.
Also, modern compilers often have options to automatically inline
functions, so manual function inlining may not be necessary. It's always a
good idea to understand the optimization options of your compiler and use
them where appropriate. For example, in GCC, the `inline` keyword can be
used to suggest that a function should be inlined, although the final decision
is up to the compiler.

3. Efficient Data Structures: Using the right data structure for the task can
greatly improve the efficiency of your code. For example, if you frequently
need to find items by key, a hash table might be more efficient than a list or
array.
Let's consider a simple example where we need to store student grades and
then look them up by student ID. If we were to use an array or a list, we
would need to iterate over the entire list to find a grade for a specific
student ID, which would take O(n) time in the worst case.

C:

#include <stdio.h>

struct Student {
int id;
float grade;
};

struct Student students[100];

float get_grade(int id) {


for (int i = 0; i < 100; i++) {
if (students[i].id == id) {
return students[i].grade;
}
}
return -1; // Not found
}

In contrast, if we use a hash table, we can look up a grade by student ID in


constant time, O(1), assuming a good hash function and ignoring potential
collisions.
Unfortunately, C does not have a built-in hash table data structure, but there
are many libraries available that provide hash table functionality. Here's an
example using the UTHash library:

C:

#include "uthash.h"
#include <stdio.h>

struct Student {
int id;
float grade;
UT_hash_handle hh;
};

struct Student *students = NULL;

void add_student(int id, float grade) {


struct Student *s;
HASH_FIND_INT(students, &id, s); /* id already in the hash? */
if (s==NULL) {
s = (struct Student *)malloc(sizeof *s);
s->id = id;
HASH_ADD_INT(students, id, s); /* id: name of key field */
}
s->grade = grade;
}
float get_grade(int id) {
struct Student *s;
HASH_FIND_INT(students, &id, s); /* s: output pointer */
return s ? s->grade : -1;
}

In this example, we use the UTHash library to create a hash table of


students. The `add_student` function adds a student to the hash table, and
the `get_grade` function looks up a student by ID in the hash table. This
lookup operation is much faster than the equivalent operation on a list or
array, especially when the number of students is large.
Remember, the choice of data structure depends on the specific
requirements of your program, and different data structures may be more
efficient for different tasks. Always consider the characteristics of your data
and the operations you need to perform on it when choosing a data
structure.

4. Memory Management: Efficient use of memory can greatly improve the


performance of your code. This can involve techniques like reusing
memory buffers instead of allocating new ones, or using stack memory
instead of heap memory where appropriate.
Let's consider a simple example where we need to process a large number
of items, and for each item, we need to create a buffer to hold some
temporary data. If we allocate and deallocate a new buffer for each item,
this can lead to significant overhead, as memory allocation and deallocation
are relatively expensive operations.

C:

#include <stdlib.h>

void process_item(int item) {


char *buffer = malloc(1024); // Allocate a new buffer for each item

// ... Do some processing with the buffer ...


free(buffer); // Deallocate the buffer
}

void process_items(int *items, int count) {


for (int i = 0; i < count; i++) {
process_item(items[i]);
}
}

In this example, a new buffer is allocated and deallocated for each item.
This can lead to significant overhead, especially if the number of items is
large.
Now, let's see how we can improve this with better memory management:

C:

#include <stdlib.h>

void process_item(int item, char *buffer) {


// ... Do some processing with the buffer ...
}

void process_items(int *items, int count) {


char *buffer = malloc(1024); // Allocate a single buffer

for (int i = 0; i < count; i++) {


process_item(items[i], buffer); // Reuse the same buffer for each item
}

free(buffer); // Deallocate the buffer


}

In this improved example, a single buffer is allocated and then reused for
each item. This can lead to a significant performance improvement, as the
overhead of memory allocation and deallocation is incurred only once,
rather than once for each item.
Remember, efficient memory management is a complex topic, and the best
approach depends on the specific requirements of your program. Always
consider the characteristics of your data and the operations you need to
perform on it when deciding how to manage memory.

5. Compiler Optimizations: Compiler optimizations are a powerful tool


for improving the performance of your code. Modern compilers have many
options for optimizing your code, including things like loop vectorization,
dead code elimination, constant propagation, function inlining, and more.
Here's a simple example of how a compiler might optimize your code.
Consider the following C code:

C:

int add(int a, int b) {


return a + b;
}

int main() {
int x = 5;
int y = 7;
int z = add(x, y);
return 0;
}

In this example, the `add` function is called to add two integers. However, a
smart compiler might notice that the values of `x` and `y` are known at
compile time, and replace the call to `add` with the result of the addition.
This is known as constant propagation. The optimized code might look
something like this:

C:
int main() {
int z = 12;
return 0;
}

This is a simple example, but real-world compilers can perform much more
complex optimizations. For example, they can eliminate loops that are
never entered, replace recursive function calls with iterative loops, and
more.
To take advantage of these optimizations, you need to understand the
options provided by your compiler. For example, in GCC, you can use the
`-O` option to specify the level of optimization. `-O1` enables basic
optimizations like constant propagation and dead code elimination, `-O2`
enables further optimizations like loop unrolling and function inlining, and
`-O3` enables even more aggressive optimizations that may take longer to
compile.
Here's how you might compile a C program with optimization level 2 in
GCC:

bash:

gcc -O2 myprogram.c -o myprogram

Remember, while compiler optimizations can greatly improve the


performance of your code, they can also make debugging more difficult, as
the optimized code may not behave exactly the same as the original code.
Therefore, it's generally a good idea to disable optimizations when
debugging your code.

6. Algorithmic Efficiency: Algorithmic efficiency refers to the


computational efficiency of an algorithm, which is typically expressed in
terms of its time complexity and space complexity. Choosing the right
algorithm for the task is one of the most important aspects of code
optimization, as an efficient algorithm can often make a bigger difference
than any amount of low-level optimization.
Let's consider a simple example: finding the smallest number in an array. A
naive approach might be to sort the array first, and then return the first
element. Here's what that might look like in C:

C:

#include <stdlib.h>

int compare(const void *a, const void *b) {


return (*(int*)a - *(int*)b);
}

int find_min(int *arr, int n) {


qsort(arr, n, sizeof(int), compare);
return arr[0];
}

In this example, `qsort` is a standard library function that sorts an array. The
time complexity of `qsort` is O(n log n), so the time complexity of the
`find_min` function is also O(n log n).
However, we can find the smallest number in an array in O(n) time by
simply iterating over the array and keeping track of the smallest number
we've seen so far:

C:

int find_min(int *arr, int n) {


int min = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
return min;
}

In this improved version of `find_min`, we only need to look at each


number once, so the time complexity is O(n). This is a significant
improvement over the previous version, especially for large arrays.
This example illustrates the importance of choosing the right algorithm for
the task. Even though the two versions of `find_min` do exactly the same
thing, the second version is much more efficient because it uses a more
efficient algorithm. Always consider the time complexity and space
complexity of your algorithms when writing code, and try to choose the
most efficient algorithm that meets your needs.

7. Minimize System Calls: System calls are expensive in terms of time


because they involve a context switch from user mode to kernel mode. This
context switch can be costly, so minimizing the number of system calls can
lead to performance improvements.
Let's consider a simple example where we need to write data to a file. A
naive approach might be to write one byte at a time using the `write` system
call:

C:

#include <fcntl.h>
#include <unistd.h>

void write_data(int fd, char *data, size_t len) {


for (size_t i = 0; i < len; i++) {
write(fd, &data[i], 1); // Write one byte at a time
}
}
In this example, a system call is made for each byte of data. This can lead to
a significant overhead if the amount of data is large.
Now, let's see how we can improve this with fewer system calls:

C:

#include <fcntl.h>
#include <unistd.h>

void write_data(int fd, char *data, size_t len) {


write(fd, data, len); // Write all data at once
}

In this improved example, all data is written with a single system call. This
can lead to a significant performance improvement, as the overhead of
system calls is incurred only once, rather than once for each byte of data.
Remember, while minimizing system calls can improve performance, it's
also important to consider other factors, such as the memory usage and the
complexity of your code. Always test your code after optimizing to make
sure it still works correctly and that the optimization actually provides a
benefit.

8. Use of Register Variables: In C, you can use the `register` keyword to


suggest to the compiler that a variable should be stored in a CPU register, if
possible. CPU registers are the fastest memory locations available, so
accessing a register variable can be faster than accessing a variable stored in
RAM.
Here's a simple example:

C:

void foo() {
register int i;
for (i = 0; i < 1000000; i++) {
// ... Do some computation ...
}
}

In this example, the variable `i` is declared as a register variable. This


suggests to the compiler that `i` should be stored in a CPU register, which
can make the loop run faster because accessing `i` is faster.
However, there are a few things to keep in mind when using register
variables:
1. The `register` keyword is only a hint to the compiler. The compiler
is free to ignore this hint if it determines that storing the variable in a
register would not be beneficial, or if there are not enough registers
available.
2. Register variables have the same scope and lifetime as automatic
variables (i.e., variables declared inside a function without the `static`
keyword). However, you cannot take the address of a register variable,
because it may not have a memory location associated with it.
3. The number of registers is limited, and they are needed for many
different things. Therefore, declaring too many register variables can
actually hurt performance, because it can force the compiler to
generate slower code to manage the registers.
4. Modern compilers are often better at choosing which variables to
store in registers than programmers, so the `register` keyword is not
used as much in modern C programming. In fact, in C++, the
`register` keyword is deprecated.
In general, it's best to let the compiler handle the decision of which
variables to store in registers, unless you have a specific reason to do
otherwise. Always test your code after making changes to make sure the
optimization actually provides a benefit.
Remember, before optimizing your code, it's important to profile it to find
out where the bottlenecks are. Optimization should be a careful, measured
process, not a haphazard one. Always test your code after optimizing to
make sure it still works correctly. And remember the words of Donald
Knuth: "Premature optimization is the root of all evil." Make sure your code
is clean and correct before you start optimizing.
OceanofPDF.com
5.2 DEBUGGING AND
TESTING YOUR CODE
WITH GDB
GNU Debugger, commonly known as GDB, is an open-source debugger
that supports various programming languages, including C, C++, Objective-
C, Fortran, and more. It is widely used because it provides robust
debugging capabilities, like breaking and resuming program execution,
inspection of runtime states, stack traces, and more.

Installation of GDB
On most Linux distributions, GDB can be installed from the package
manager.
For Debian-based distributions (like Ubuntu), use the following command:

bash:
sudo apt-get install gdb

For Red Hat-based distributions (like Fedora, CentOS), use the following
command:

bash:
sudo yum install gdb

For Arch-based distributions, use the following command:


bash:
sudo pacman -S gdb

Compiling Programs for Debugging with GDB


When compiling your program for debugging with GDB, it's essential to
include the `-g` flag. This flag instructs the compiler to include debugging
information within the executable, which GDB can use to provide better
information during debugging.

For instance, if you're using the gcc compiler to compile a program written
in C, you would use:

bash:

gcc -g -o myprogram myprogram.c

Using GDB
To start GDB, you can type `gdb` in your terminal. To open a program
within GDB, you can use the `file` command followed by the executable
name:

bash:

gdb
(gdb) file myprogram

Alternatively, you can start GDB with your program as an argument:

bash:
gdb myprogram
Once your program is loaded, you can use the `run` command to start it:

bash:
(gdb) run

Setting Breakpoints
To halt execution at a certain point in your code, you can set breakpoints.
This is done using the `break` command, followed by a function name or a
line number:

bash:
(gdb) break main

or

bash:
(gdb) break myprogram.c:24

Inspecting Program State


Once a breakpoint is hit, you can inspect the state of your program. For
instance, you can use the `print` command to inspect the value of a variable:

bash:
(gdb) print myVariable

or use the `backtrace` command to see the stack trace:

bash:
(gdb) backtrace

To step through your program one line at a time, you can use the `next`
command. If you want to step into a function, use the `step` command.

Exiting GDB
When you're done debugging, you can quit GDB using the `quit` command:

bash:
(gdb) quit

This chapter covers the very basics of setting up and preparing GDB for
debugging on a Linux system. GDB is an extremely powerful tool, and
mastery of it can greatly enhance your efficiency and effectiveness as a
developer.

OceanofPDF.com
5.3 UNIT TESTING C CODE
WITH CHECK
FRAMEWORK
The Check framework is a popular tool used for unit testing in C, enabling
developers to ensure their code functions as expected under various
conditions. It provides mechanisms to create automated tests, where each
test is isolated and checks a small bit of functionality in the program. With
features like automatic test discovery, setup/teardown operations, and
diverse types of assertions, Check makes unit testing in C efficient and
effective.

OceanofPDF.com
5.3.1 SETTING UP THE
CHECK FRAMEWORK ON
LINUX AND WINDOWS
Installation on Linux
Step 1: Update the Package List
Before installing new packages on a Ubuntu-like Linux system, it's
generally a good idea to update the package lists for upgrades and new
installations. Open your terminal and enter:

bash:
sudo apt-get update

Step 2: Install the Check Package


Next, install the Check framework with the following command:

bash:
sudo apt-get install check

For Fedora or CentOS, you would use the following command instead:

bash:
sudo dnf install check
Installation on Windows
Installing the Check framework on Windows can be a bit more challenging
because Windows does not natively support the GNU toolchain which
Check is part of. However, we can use Cygwin, which provides a large
collection of GNU and Open Source tools which provide functionality
similar to a Linux distribution on Windows.

Step 1: Install Cygwin


Download the Cygwin setup program from the official website
(https://www.cygwin.com/) and run it. Choose the "Install from Internet"
option, and proceed with the default settings until you reach the "Select
Packages" screen.

Step 2: Install Check and Dependencies


In the "Select Packages" screen, you'll need to install the Check package
and its dependencies. Type each of the following into the search bar and
click on the "Skip" label next to them so it changes to show the version
number to be installed:
- `check`
- `gcc-core`
- `gcc-g++`
- `make`
- `libtool`

These packages include the Check unit testing framework, the GCC
compiler, and the make build automation tool.

Step 3: Proceed with the Installation


Click "Next" to proceed with the installation. Cygwin will then download
and install the selected packages and their dependencies. This may take a
while depending on your internet speed.
You're now ready to start writing and running unit tests in C using the
Check framework on both Linux and Windows operating systems. In the
next chapter, we will cover how to write simple unit tests in C using this
setup.

OceanofPDF.com
5.3.2 WRITING AND
RUNNING UNIT TESTS
In this chapter, we will cover how to write and run simple unit tests using
the Check framework. To demonstrate this, we'll create a simple C program
that includes a function to test, and then write a test for it.

Writing a Simple C Program


Let's create a simple C program that calculates the factorial of an integer.
Save this as `factorial.c`:

C:

#include "factorial.h"

long factorial(int n) {
if(n < 0) {
return -1;
}
if(n == 0) {
return 1;
} else {
return n * factorial(n-1);
}
}

And the associated header file `factorial.h`:


C:
long factorial(int n);

Writing the Unit Test


Now let's write some tests for our `factorial` function. In Check, each test
case is a C function on its own. We group test cases into a `Suite`, and a
`SRunner` is used to run one or more suites. Here's how we might test the
`factorial` function.
Save the following code as `check_factorial.c`:

C:

#include <check.h>
#include <stdlib.h>
#include "factorial.h"

START_TEST(test_factorial) {
ck_assert_int_eq(factorial(0), 1);
ck_assert_int_eq(factorial(1), 1);
ck_assert_int_eq(factorial(2), 2);
ck_assert_int_eq(factorial(3), 6);
ck_assert_int_eq(factorial(10), 3628800);
ck_assert_int_eq(factorial(-1), -1);
}
END_TEST

Suite * factorial_suite(void) {
Suite *s;
TCase *tc_core;
s = suite_create("Factorial");
tc_core = tcase_create("Core");

tcase_add_test(tc_core, test_factorial);
suite_add_tcase(s, tc_core);

return s;
}

int main(void) {
int number_failed;
Suite *s;
SRunner *sr;

s = factorial_suite();
sr = srunner_create(s);

srunner_run_all(sr, CK_NORMAL);
number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

In the above code, `test_factorial` is the unit test case that tests the
`factorial` function. The function `factorial_suite` creates a test suite and
adds the test case to it. The `main` function runs all tests and reports any
failures.

Compiling and Running the Unit Test


Now that we have our unit test, we can compile it. You must link the
`check_factorial.c` file with `factorial.c` and the Check library.

On Linux:

bash:
gcc -o check_factorial check_factorial.c factorial.c `pkg-config --cflags --libs check`

On Windows (Cygwin Terminal):

bash:

gcc -o check_factorial check_factorial.c factorial.c -lcheck -lm -lrt -lpthread -lsubunit

To run the test:

bash:
./check_factorial

If everything is working correctly, you should see output indicating that


your tests passed. If a test failed, Check would let you know which test
failed and what the failing condition was.

console:
Running suite(s): Factorial
100%: Checks: 1, Failures: 0, Errors: 0

This wraps up our introduction to writing and running simple unit tests
using the Check framework. Remember that unit testing is a powerful tool
to verify that your functions are behaving as expected and can save you a
lot of debugging time in the long run.

OceanofPDF.com
5.4 COMMON PITFALLS
AND HOW TO AVOID
THEM
C is a powerful language, but it also has some pitfalls that can lead to bugs
and other issues. Here are some common pitfalls and how you can avoid
them:

1. Memory Leaks: A memory leak occurs when you allocate memory (with
`malloc`, for example) but never free it. Over time, this can consume all of
your program's memory and cause it to crash. To avoid memory leaks, make
sure to free any memory that you allocate when you're done with it.

2. Buffer Overflows: A buffer overflow occurs when you write more data
to a buffer than it can hold. This can overwrite other data and lead to
unpredictable behavior. To avoid buffer overflows, always check that there
is enough space in the buffer before writing to it.

3. Null Pointer Dereferencing: If you try to dereference a null pointer,


your program will crash. To avoid this, always check that a pointer is not
null before dereferencing it.

4. Uninitialized Variables: Using the value of a variable before it has been


initialized can lead to unpredictable behavior. To avoid this, always
initialize your variables before using them.

5. Ignoring Return Values: Many functions in C return a value that


indicates whether the function succeeded or failed. Ignoring this return
value can mean that you miss errors. To avoid this, always check the return
value of functions.
6. Off-by-One Errors: These are errors that occur when you go one step
too far in a loop or array. To avoid these, be careful with your loop
conditions and array indices.

7. Undefined Behaviour: C has many features that can lead to undefined


behaviour if used incorrectly. This can include things like shifting an integer
by too many bits, or dividing by zero. To avoid undefined behaviour, make
sure you understand the rules of C and follow them.

By being aware of these pitfalls and taking steps to avoid them, you can
write C code that is more robust, reliable, and easy to understand.

OceanofPDF.com
CHAPTER 6: PROJECTS
AND EXERCISES
The journey of learning any programming language is incomplete without
applying the acquired knowledge to solve real-world problems. This
chapter, "Projects and Exercises," is designed to provide you with practical
scenarios where you can apply the concepts you've learned throughout this
book.
The chapter is divided into two main sections: Projects and Exercises. The
Projects section includes a variety of comprehensive programming projects
that encompass multiple concepts from the previous chapters. These
projects are designed to simulate real-world problems and will challenge
you to develop complete programs in C. They will test your understanding,
creativity, and problem-solving skills.
The Exercises section, on the other hand, contains a series of shorter
problems aimed at reinforcing specific concepts. These exercises are more
focused and will help you practice and solidify your understanding of
individual topics.
Each project and exercise is followed by a detailed solution and
explanation. These solutions serve not only as a means for you to check
your work, but also as a learning tool. They provide insight into how
experienced programmers approach problem-solving and offer tips and
techniques that you can incorporate into your own coding practices.
By the end of this chapter, you will have gained hands-on experience with
C programming and have a portfolio of projects to showcase your skills.
Remember, the key to mastering programming is practice, so roll up your
sleeves and let's get coding!

OceanofPDF.com
6.1 PRACTICAL PROJECTS
TO APPLY YOUR
KNOWLEDGE
Applying what you've learned in practical projects is a great way to solidify
your understanding and gain hands-on experience. Here are some project
ideas that you can work on to apply your knowledge of C programming:
1. Command Line Calculator: Write a program that takes mathematical
expressions as input and evaluates them. This will help you practice
working with data structures, parsing input, and error handling.
2. Text Editor: Create a simple text editor that can open, edit, and save text
files. This project will give you experience with file I/O, memory
management, and user interaction.
3. File Compression Tool: Write a program that can compress and
decompress files using a simple compression algorithm like Run-Length
Encoding (RLE) or Huffman coding. This will give you practice with bit
manipulation, data structures, and file I/O.
4. Simple Shell: Implement a simple Unix shell that can execute commands
and manage processes. This will give you experience with system calls,
process management, and error handling.
5. Memory Allocator: Write your own version of the `malloc` and `free`
functions. This is a more advanced project that will give you a deep
understanding of memory management in C.
6. Implement a Data Structure: Choose a data structure, like a hash table
or a binary tree, and implement it from scratch. This will help you
understand how these data structures work and how to use pointers
effectively.
7. Safety-Critical Code Review: Find an open-source project that is used
in a safety-critical context, and review it for compliance with the Power of
10 rules. This will help you understand how these rules are applied in
practice and how they contribute to code safety and reliability.
Remember, the goal of these projects is not just to produce a working
program, but to practice writing good C code. Pay attention to code
organization, style, and correctness. Use the techniques you've learned to
handle errors, manage memory, and write efficient code. And most
importantly, have fun! Programming can be a creative and rewarding
activity, and these projects are an opportunity to explore what you can do
with C.

OceanofPDF.com
6.2 EXERCISES FOR SELF-
ASSESSMENT
Here are some exercises that you can use to test your understanding of the
material covered in this book. Try to solve these problems on your own, but
don't hesitate to look up information or ask for help if you get stuck.
1. Data Types and Variables: Write a program that declares variables of
several different types, assigns values to them, and prints their values.
2. Control Structures: Write a program that uses `if`, `for`, and `while`
statements to print the numbers from 1 to 10 in various orders.
3. Functions: Write a function that takes two integers as arguments and
returns their sum. Test your function with several pairs of numbers.
4. Arrays and Strings: Write a program that declares an array of integers,
initializes it with some values, and prints the values. Do the same with a
string.
5. Pointers: Write a program that declares a pointer to an integer, assigns
the address of an integer variable to the pointer, and uses the pointer to read
and modify the value of the variable.
6. Structures: Define a structure that represents a point in 2D space, with
fields for the x and y coordinates. Write a function that takes two points as
arguments and returns the distance between them.
7. File I/O: Write a program that reads lines of text from a file and prints
them to the console. Then modify your program to write lines of text to a
file.
8. Bit Manipulation: Write a function that takes an integer as an argument
and returns the number of bits that are set to 1 in the binary representation
of the integer.
9. Error Handling: Modify one of your previous programs to include error
checking. For example, check the return value of the `fopen` function and
print an error message if the file cannot be opened.
10. Safety-Critical Code Review: Review one of your previous programs
for compliance with the Power of 10 rules. Identify any violations and
describe how you would fix them.
These exercises cover a range of topics and difficulty levels. If you can
solve them, you have a good understanding of the basics of C
programming. If you find them difficult, don't worry. Programming is a
skill that takes time and practice to master. Keep studying, keep practicing,
and don't give up!

OceanofPDF.com
6.3 SOLUTIONS AND
EXPLANATIONS
Here are the solutions and explanations for the self-assessment exercises.
Note that in programming, there are often many ways to solve a problem.
The solutions provided here are just one possible approach.

1. Data Types and Variables:

C:

#include <stdio.h>

int main() {
int a = 10;
float b = 20.5;
char c = 'x';

printf("a: %d, b: %f, c: %c\n", a, b, c);

return 0;
}

This program declares variables of type `int`, `float`, and `char`, assigns
values to them, and prints their values.
2. Control Structures:

C:

#include <stdio.h>
int main() {
int i;

printf("Using for loop: ");


for(i = 1; i <= 10; i++) {
printf("%d ", i);
}
printf("\n");

printf("Using while loop: ");


i = 1;
while(i <= 10) {
printf("%d ", i);
i++;
}
printf("\n");

return 0;
}

This program uses a `for` loop and a `while` loop to print the numbers from
1 to 10.
3. Functions:

C:

#include <stdio.h>

int add(int a, int b) {


return a + b;
}

int main() {
printf("3 + 4 = %d\n", add(3, 4));
printf("10 + 20 = %d\n", add(10, 20));
return 0;
}

This program defines a function `add` that takes two integers as arguments
and returns their sum. It then calls this function with several pairs of
numbers.
4. Arrays and Strings:

C:

#include <stdio.h>

int main() {
int numbers[] = {1, 2, 3, 4, 5};
char string[] = "Hello, world!";

for(int i = 0; i < 5; i++) {


printf("%d ", numbers[i]);
}
printf("\n");

printf("%s\n", string);

return 0;
}

This program declares an array of integers and a string, initializes them with
values, and prints the values.
5. Pointers:

C:

#include <stdio.h>
int main() {
int x = 10;
int *p = &x;

printf("x = %d\n", *p);

*p = 20;

printf("x = %d\n", x);

return 0;
}

This program declares a pointer to an integer, assigns the address of an


integer variable to the pointer, and uses the pointer to read and modify the
value of the variable.
6. Structures:

C:

#include <stdio.h>
#include <math.h>

struct Point {
double x;
double y;
};

double distance(struct Point a, struct Point b) {


double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx*dx + dy*dy);
}
int main() {
struct Point p1 = {0, 0};
struct Point p2 = {3, 4};

printf("Distance: %f\n", distance(p1, p2));

return 0;
}

This program defines a structure that represents a point in 2D space, with


fields for the x and y coordinates. It also defines a function that takes two
points as arguments and returns the distance between them.
7. File I/O:

C:

#include <stdio.h>

int main() {
FILE *file = fopen("test.txt", "r");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}

char line[100];
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line);
}

fclose(file);

return 0;
}
This program opens a file, reads lines of text from it, and prints them to the
console.
8. Bit Manipulation:

C:

#include <stdio.h>

int countBits(int n) {
int count = 0;
while (n) {
count += n & 1;
n >>= 1;
}
return count;
}

int main() {
printf("Number of 1 bits in 5: %d\n", countBits(5));
printf("Number of 1 bits in 7: %d\n", countBits(7));

return 0;
}

This program defines a function that takes an integer as an argument and


returns the number of bits that are set to 1 in the binary representation of the
integer.
9. Error Handling:

C:

#include <stdio.h>

int main() {
FILE *file = fopen("test.txt", "r");
if (file == NULL) {
perror("Error opening file");
return 1;
}

char line[100];
if (fgets(line, sizeof(line), file) == NULL) {
perror("Error reading from file");
return 1;
}

printf("%s", line);

fclose(file);

return 0;
}

This program opens a file and reads a line of text from it. It checks the
return value of the `fopen` and `fgets` functions and prints an error message
if an error occurs.
10. Safety-Critical Code Review: This exercise involves reviewing your
own code, so there's no specific solution. However, you should look for
violations of the Power of 10 rules, such as using `goto` statements, not
checking the return value of functions, or using complex preprocessor
macros. If you find any violations, think about how you could modify your
code to comply with the rules.
Remember, the goal of these exercises is to practice your C programming
skills and deepen your understanding of the language. If you found these
exercises challenging, don't worry. Keep practicing, and you'll get better
over time.

OceanofPDF.com
Notes

OceanofPDF.com
Notes

OceanofPDF.com
Notes

OceanofPDF.com
Notes

OceanofPDF.com
INDEX
!
​3
! 5
​ 2, 89, 109, 141, 142, 149, 151, 174, 176, 177, 178, 192, 193, 195, 196, 197, 202, 204, 208, 209,
!= 5
212, 213, 229, 231, 232, 246, 247, 275, 276, 277, 282, 284, 286, 290, 294, 327, 334, 335, 348,
350, 411, 416, 442, 445, 446, 449, 453, 459, 476, 481, 492, 506, 545

#
#define 18, 21, 23, 29, 32, 49, 50, 149, 151, 153, 156, 159, 161, 166, 170, 208, 210, 211,
231, 302, 312, 329, 331, 333, 337, 342, 346, 347, 350, 352, 354, 357, 388, 401, 410, 415, 424,
427, 430, 433, 435, 510, 516
#elif 18, 493, 494
#else 18, 494
#endif 18, 24, 29, 32, 494
#if ​18
#ifdef 18, 493, 494
#ifndef 18, 23, 29, 32
#include 18, 21, 22, 26, 29, 31, 32, 34, 58, 65, 66, 67, 93, 94, 95, 96, 97, 98, 99, 100, 101,
102, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 139, 140, 141, 142, 145, 149, 151, 153,
155, 159, 161, 166, 170, 175, 176, 177, 178, 179, 182, 188, 192, 195, 198,202, 206, 207, 208,
210, 214, 216, 218, 220, 223, 227, 228, 230, 232, 236, 239, 241, 242, 246, 247, 248, 249, 251,
252, 253, 255, 256, 259, 260, 261, 262, 264, 266, 268, 271, 274, 275, 276, 278, 280, 281, 283,
285, 287, 288, 290, 291, 294, 299, 301, 302, 306, 310, 312, 315, 319, 322, 326, 329, 331, 333,
335, 337, 340, 342, 345, 347, 349, 352, 354, 357, 360, 363, 365, 368, 372, 375, 379, 384, 388,
393, 395, 398, 401, 407, 410, 414, 420, 424, 427, 430, 432, 435, 441, 445, 446, 449, 451, 453,
457, 462, 471, 472, 474, 475, 477, 478, 480, 481, 482, 483, 484, 486, 488, 492, 493, 498, 499,
502, 506, 507, 508, 510, 512, 513, 517, 518, 519, 520, 523, 524, 525, 533, 534, 540, 541, 542,
543, 544, 545, 546

&
&& 53, 265, 267
.
​4
. 7
. Ignoring Return Values 537

/
/dev/ttyS0 182, 183, 185

:
: Function inlining 516

_
__APPLE__ 493, 494
__linux__ 494
__MACH__ 494
__unix__ 494
_WIN32 493, 494

{
​2
{} 4

|
​ 3, 179, 262, 292, 335, 350, 352, 355, 461
|| 5

<
​2
< 5
<= ​52

=
== ​52, 53, 72, 73, 89, 102, 104, 110, 140, 141, 149, 151, 154, 170, 172, 179, 182, 188, 190, 192, 206,
207, 209, 210, 215, 216, 219, 220, 227, 231, 232, 237, 239, 242, 264, 267, 268, 275, 276, 280,
281, 283, 286, 287, 289, 290, 292, 306, 310, 315, 320, 329, 331, 334, 335, 338, 339, 340, 343,
358, 360, 384, 389, 396, 402, 417, 422, 428, 433, 435, 443, 450, 452, 458, 471, 477, 478, 481,
498, 499, 500, 502, 508, 518, 533, 535, 545, 546
>
​2
> 5
>= ​52

OceanofPDF.com
1
1xx 165

2
2xx 165

OceanofPDF.com
3
3D Cube 318, 319
3D Sphere 312, 322, 323
3xx 165

OceanofPDF.com
4
404 165, 173
404 Not Found 165, 173
4xx 165

OceanofPDF.com
5
500 Internal Server Error 165
5xx 165

OceanofPDF.com
A
AAC 325, 330, 332
abs() 92, 111
Addition 52, 88, 135, 137, 368
Address Calculation 20
Advanced Audio Coding 325
AES-256-CBC cipher 446, 453
AES-256-CBC Cipher 446, 453
Amplitude Compression 345
AND 53, 125, 126, 131, 132, 133, 134
Apache 164, 169, 201, 205, 465
API 258, 259, 294, 363, 365, 391, 392, 496
Apple 362, 494
Arithmetic Operators 52
Arrays 66, 83, 84, 85, 87, 404, 539, 542
asctime() 92, 96, 97
Assembly 19, 20, 482, 485
assembly code 20, 483, 484, 485
Asymmetric and Symmetric Keys 441
Asymmetric Encryption 437, 439, 448
Asymmetric Key Encryption 449
Asymmetric Key pairs 441
atof() 92, 108
atoi() 92, 108
atol() 92, 108
aubio 356, 357, 359
Audio Files 325, 326
Audio Mixing 349
Audio Reversal 347
AutoCAD 303, 304
OceanofPDF.com
B
B2 ​430
backtrace 529
Base-10 121
Base-16 121
Base-2 121
Base-8 121
basic syntax 5, 40, 43, 61, 64
bcrypt 470, 471, 472
Binary 109, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138
Bit Manipulation 125, 540, 545
BLAKE2 430, 432
Blending 291, 293
Blocks 42
bool 47
Breakpoints 528
Brightness adjustment 287
bsearch() 92, 109
BSON 199, 201
Buffer Overflow 117, 118
Buffer Overflows 536

OceanofPDF.com
C
​ , 5, 7, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 34, 40, 41, 42, 43, 47, 48, 49,
C 2
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
76, 77, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 125,
126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144,
145, 146, 149, 151, 153, 155, 159, 161, 166, 167, 168, 169, 171, 172, 174, 175, 176, 177, 178,
179, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198,
199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 210, 211, 212, 213, 214, 216, 217, 218, 219,
220, 222, 223, 224, 225, 226, 227, 228, 230, 232, 234, 235, 236, 239, 241, 242, 244, 245, 246,
247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 264, 266, 268,
270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 283, 285, 287, 288, 290, 291, 292,
293, 294, 295, 298, 299, 301, 302, 306, 310, 312, 314, 315, 317, 318, 319, 322, 326, 329, 330,
331, 333, 334, 335, 337, 339, 340, 341, 342, 345, 347, 349, 351, 352, 354, 356, 357, 359, 360,
361, 363, 365, 368, 372, 374, 375, 379, 384, 387, 388, 391, 392, 393, 394, 395, 398, 399, 400,
401, 404, 406, 407, 408, 409, 410, 414, 420, 424, 427, 429, 430, 432, 434, 435, 441, 443, 445,
446, 448, 449, 450, 451, 452, 453, 457, 460, 461, 462, 468, 469, 470, 471, 472, 473, 474, 475,
477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495,
496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514,
515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 530, 532, 533, 534, 536, 537,
538, 539, 540, 541, 542, 543, 544, 545, 546, 547
C API 187, 242, 244, 293, 488, 491
c_cc 186, 187
c_cc[VEOF] 187
c_cflag 183, 186, 187
c_iflag 186
c_lflag 186, 187
c_oflag 186, 187
C++ 16, 17, 25, 26, 27, 201, 205, 236, 306, 314, 404, 485, 526
Cairo 236, 242, 243, 298, 299, 300, 302, 303, 306
Calculator 538
callback 196, 197, 229, 259, 265, 267, 269, 458, 460
calloc 90, 91, 92, 110, 352, 355
CAP_IPC_LOCK 479
cass_iterator_from_result 204
cass_session_execute 203
Cassandra 187, 201, 202, 203, 205
ceil() 92, 93, 95
CGLM 314, 315, 317
char 47
Check framework 5, 530, 531, 532, 536
Checking the Value of a Bit 133
checksum 405, 406, 410, 412, 413
ciphertext 446, 447, 448, 453, 454, 455
cksum 406, 412, 413
Clearing a Bit 131
click 264, 265, 267, 298, 532
Client 468
clobber list 483, 484
clock() 92, 96, 97, 145, 146, 147
clock_gettime() 147
clock_t 96, 97, 146
CLOCKS_PER_SEC 97, 145, 146, 147
cmake 35, 36, 37, 38, 39, 201, 238, 295, 480
CmakeLists.txt 35, 36, 37, 38
CMakeLists.txt 35, 36, 37, 38, 238, 296, 480
Code Organization 23
Comma Separated Values 226
Compilation Switches 24
Compiler Optimizations 521
Conditional Statements 54
const 50, 51, 69, 70, 71, 73, 109, 120, 160, 161, 166, 167, 168, 185, 200, 203, 204, 206, 245,
310, 313, 315, 319, 365, 368, 372, 376, 380, 384, 388, 415, 420, 428, 433, 435, 458, 462, 469,
471, 523
constants 49
Contrast stretching 279
Control Structures 43, 53, 539, 541
cos() 92, 93
CPU 19, 97, 145, 394, 395, 399, 525, 526
CPUs 362, 404
CRC 405, 406, 407, 408, 409, 412, 413
crc32 410, 411, 412, 413
CRC32 409, 410, 412
CRUD 165, 169, 171, 175, 179, 188
Cryptographic Hash Functions 405, 414
CSIZE 183, 187
CSR 465
CSTOPB 183, 187
CSV 205, 226, 227, 228, 229, 230
csv_init 229
csv_parse 229
csv_parser 229
ctype library 93
CUDA 5, 391, 392, 393, 395, 397, 398, 401, 402, 403, 404, 405
curly braces 42, 73, 80, 211
Cyclic Redundancy Check 405, 406, 409, 413
Cygwin 487, 531, 532, 535

OceanofPDF.com
D
Data Structure 539
Data Types 41, 43, 65, 539, 540
Database Connections 187
DataStax 201, 202, 205
DBL_MAX 46
DBL_MIN 46
Deadlocks 254, 256
Debug Information 24
Debugging 18, 514, 526, 527
Decimal 121, 122, 123, 124
decompress 230, 231, 232, 325, 539
Decompressing 230
Decrypt 440, 452
Decrypting 453
Decryption 438, 444, 451
DELETE 164, 166, 168, 169, 171, 172, 173, 175, 178, 179, 180
Difference of Two Pointers 88
difftime() 92, 96, 97
Digital Signatures 405
Direct Memory Access 495
distutils 490
dividing by zero 537
Division 52, 135, 136, 508
do...while 56
double 46
DPI 241, 242, 243
Drawing Exchange Format 270, 303
DSPs 362, 404
DXF 270, 271, 303, 304, 305, 306, 307
Dynamic 27, 32, 33, 35, 37, 38, 90, 110, 502
OceanofPDF.com
E
Echo Effect 352
Embedding Assembly 482
Encrypt 440, 449
Encryption 432, 438, 444, 446, 449
Entry Point 21
enum 80, 81, 120
Enumerating OpenCL 363
errno 72, 73, 158
Error Checking 147, 213
Error Handling 71, 72, 222, 225, 233, 460, 493, 540, 546
exit() 92, 110, 336
exp() 92, 93
Expressions 51
extern 23, 25, 26, 27

OceanofPDF.com
F
FAAC 330, 331, 332
fclose 92, 104, 105, 106, 207, 208, 209, 211, 212, 213, 215, 217, 228, 229, 231, 233, 307,
311, 314, 317, 321, 324, 328, 332, 334, 344, 345, 409, 412, 419, 423, 449, 451, 545, 546
fclose() 92, 104
fgetc() 92, 105
fgets 92, 105, 106, 207, 208, 227, 228, 545, 546
fgets() 92, 105, 114, 117, 118, 119
File Compression Tool 539
FLAC 325, 326, 328, 329
float 46
floor() 92, 93, 95
FLT_MAX 46
FLT_MIN 46
fopen 71, 72, 73, 92, 104, 105, 106, 206, 207, 209, 210, 211, 212, 213, 215, 216, 227, 228,
229, 231, 232, 233, 306, 310, 313, 315, 319, 323, 327, 331, 333, 342, 344, 409, 412, 419, 423,
449, 451, 540, 545, 546
for ​55
FPGAs 404
fprintf() 92, 106
fputc() 92, 105
fputs 92, 105, 166, 167, 168, 169, 206, 207, 208
fputs() 92, 105
fread() 92, 104
Free Lossless Audio Codec 325, 326
fscanf() 92, 106
Function Prototypes 82
Functions 31, 34, 57, 72, 75, 81, 91, 93, 94, 95, 96, 98, 103, 104, 107, 111, 114, 116, 248,
441, 472, 539, 542
fwrite 92, 104, 209, 211, 212, 213, 215, 231, 322, 332, 334, 344, 450
fwrite() 92, 104
OceanofPDF.com
G
g_filename_to_uri 237, 238, 239, 242
gcc​29, 30, 31, 33, 34, 152, 153, 162, 247, 294, 300, 302, 303, 330, 332, 339, 341, 361, 443, 445, 448,
450, 452, 455, 464, 487, 512, 522, 527, 528, 532, 535
GDB 5, 526, 527, 528, 530
GeForce 391
Gerard J. Holzmann 497, 498
GET 163, 164, 166, 167, 169, 171, 172, 175, 179, 460
gets() 92, 107, 117, 118, 119
gettimeofday() 147
glBegin 260, 261, 262, 263, 264, 266, 268
glClear 260, 261, 262, 263, 264, 266, 268
glEnd 260, 261, 262, 264, 266, 268
glFlush 260, 261, 264, 266, 268
GLib 142, 143, 144, 145, 236, 238
GLUT 259, 260, 261, 262, 263, 264, 265, 267, 268, 269
glutCreateWindow 259, 260, 261, 263, 265, 267, 269
glutDisplayFunc 259, 260, 261, 263, 265, 267, 269
glutInit 259, 260, 261, 263, 265, 267, 269
glutInitDisplayMode 259, 260, 261, 263, 265, 267, 269
glutInitWindowSize 259, 260, 261, 263, 265, 267, 269
glutMainLoop 259, 260, 261, 263, 265, 267, 269
glutMotionFunc 267, 269
glutPostRedisplay 263, 265, 266, 267, 269
glVertex 260
goto 442, 498, 499, 547
GPU 391, 392, 393, 394, 395, 398, 399, 401
GPUs 362, 383, 391, 392, 404
Graphics 258, 270, 296, 391
grayscale 270, 277, 278, 288, 289, 290, 291
GTX 391
OceanofPDF.com
H
Header Files 22
HEADER_FILE_H 23, 24
Hexadecimal 121, 123, 124
HTML 164, 165, 297, 298
HTTP 163, 164, 165, 166, 167, 168, 169, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180,
181, 460, 461, 464
HTTP Protocol 163
HTTP Request 164
HTTP Response 164
HTTP Status Codes 165

OceanofPDF.com
I
I/O 18, 21, 92, 104, 184, 185, 186, 205, 208, 211, 318, 329, 336, 337, 363, 364, 365, 367,
368, 371, 372, 374, 375, 378, 379, 382, 384, 386, 395, 397, 464, 469, 538, 539, 540, 544
​4
if 5
if...else 54
Image 242, 270, 271, 272, 273, 274, 275, 276, 278, 281, 283, 285, 290, 291, 293, 294, 387,
388, 401
INADDR_ANY 150, 154, 160, 161, 162, 170, 462
Including Directories 25
Initialization Vector 455
inline assembly 482, 485
Input/Output library 92
Instruction Translation 20
int ​43, 44, 48
int16_t 43, 45
int32_t 43, 45
int8_t 43, 45
isalnum() 93, 111, 112
isalpha() 93, 111, 112
isdigit() 93, 111, 112
islower() 93, 112
isupper() 93, 112
​ 38, 446, 448, 453, 455
IV 4

OceanofPDF.com
J
Jansson 222, 223, 225, 226
Java 5, 139, 485, 486, 487, 488, 491
Java Virtual Machine 485
JNI ​485, 486, 491
JPEGs 279
JPL Laboratory 497
JSON 172, 175, 176, 177, 178, 198, 205, 222, 223, 224, 226
json_decref 224, 226
json_error_t 223, 225
json_load_file 224, 225
json_object_get 224

OceanofPDF.com
K
Kernel 393
key pair 441, 442, 443, 444
Keys Generation 441
Khronos Group 362, 363, 404

OceanofPDF.com
L
lame 332, 333, 334, 342, 344, 345
LD_LIBRARY_PATH 34
LDBL_MAX 47
LDBL_MIN 47
Left shift 129
Leptonica 274, 275, 276, 277, 278, 279, 281, 282, 283, 284, 285, 286, 287, 288, 290, 291,
293, 295, 296
Lexical Analysis 19
libcsv 228
libcurl 169, 175, 178
libFLAC 326
libpq 191, 192, 194
library 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 45, 46, 47, 59, 60, 72, 90, 92,
93, 96, 98, 104, 107, 111, 113, 114, 115, 142, 145, 169, 175, 179, 181, 188, 191, 194, 197, 198,
213, 217, 218, 222, 223, 226, 230, 235, 236, 238, 239, 242, 245, 248, 251, 252, 258, 259, 261,
269, 271, 272, 273, 274, 275, 276, 277, 278, 279, 281, 282, 283, 284, 285, 286, 287, 288, 290,
291, 293, 294, 296, 298, 299, 306, 307, 314, 315, 326, 329, 330, 332, 334, 336, 337, 339, 340,
341, 342, 344, 345, 347, 349, 351, 352, 353, 354, 356, 357, 359, 361, 388, 424, 427, 430, 432,
434, 441, 443, 445, 446, 448, 450, 452, 453, 455, 457, 458, 460, 461, 464, 468, 470, 472, 479,
480, 482, 486, 487, 490, 496, 518, 519, 523, 535
Library 21, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 93, 96, 98, 107, 111, 258, 294, 308, 315,
388, 401, 479, 491, 492
librsvg 298, 299, 300, 301, 302, 303
libsndfile 329, 330, 334, 335, 336, 337, 339, 340, 341, 342, 344, 345, 347, 349, 351, 352,
353, 354, 356
libxml2 217, 218, 222
Linkage 25
Linking 20, 21, 24, 294
Linux 182, 236, 296, 466, 479, 487, 493, 494, 527, 530, 531, 532, 535
List 139, 142, 530
Locks 250, 251
log() 92, 93, 94
log10() 92, 93, 94
long 43, 44
long double 46, 47
long int 44
long long 43, 44
long long int 44
longjmp 498, 499
Loop unrolling 514
Loops 55

OceanofPDF.com
M
Mac 493, 494
Macros 25
make 16, 19, 22, 36, 37, 38, 39, 40, 53, 56, 57, 58, 61, 64, 69, 70, 71, 81, 90, 93, 117, 146,
149, 181, 185, 202, 240, 264, 283, 298, 306, 308, 316, 318, 319, 320, 345, 404, 432, 439, 468,
476, 477, 485, 488, 498, 499, 502, 514, 522, 525, 526, 532, 536, 537
malloc 60, 90, 91, 92, 110, 328, 331, 336, 348, 350, 352, 355, 363, 369, 372, 376, 380, 385,
386, 389, 396, 397, 399, 400, 403, 416, 502, 518, 520, 536, 539
Manipulating Structs 77
Map 139, 140, 142, 144
Math library 92
Matrix Multiplication 372, 398
MD5 405, 414, 424, 430
Measuring Execution Time 145
Mel-frequency cepstral coefficients 359
memcpy 68, 77, 78, 79, 103, 416
Memory Allocator 539
memory leak 91, 536
Memory Leaks 91, 536
Memory Management 90, 519
Memory Protection 476
memset 103, 156, 160, 161, 166, 167, 168, 169, 469, 472, 473, 474, 475, 479
Message Encryption 438, 446
MFCC 357, 359
minizip 230, 232, 233, 234
MISRA C 513
mlock 478, 479
mmap 477, 478, 479
mode_t 185
Modulus 52
mongoc_cleanup 199, 200
mongoc_client_new 198, 199
mongoc_collection_insert_one 199
MongoDB 187, 197, 198, 199, 201
Monte Carlo Simulation 375
mouse 263, 264, 265, 266, 267, 268, 269, 270, 303
MP3 325, 332, 333, 334, 335, 336, 337, 342
mpg123 334, 335, 336, 337
mprotect 476, 477
Multiplication 52, 135, 372, 398
MuPDF 234, 235
Mutexes 250, 251
MySQL 187, 188, 189, 191
mysql_fetch_row 190
mysql_init 188, 189
mysql_query 189, 190
mysql_real_connect 189
MYSQL_RES 190
mysql_store_result 190

OceanofPDF.com
N
Name Mangling 25, 26, 27
NASA 497, 498
N-body Simulation 379
nop 483
Normalization 341
NoSQL 187, 198
NOT 38, 53, 128, 132
NULL 71, 72, 73, 90, 96, 97, 98, 99, 100, 104, 105, 107, 108, 109, 110, 142, 174, 176, 177,
180, 188, 189, 190, 196, 199, 200, 202, 206, 207, 209, 210, 211, 212, 213, 215, 216, 219, 220,
227, 229, 231, 232, 234, 237, 239, 242, 245, 246, 247, 248, 249, 255, 256, 266, 273, 275, 276,
278, 280, 281, 283, 286, 287, 289, 290, 292, 294, 301, 306, 310, 315, 320, 327, 329, 335, 338,
339, 340, 344, 360, 363, 366, 369, 373, 376, 381, 385, 389, 402, 416, 428, 433, 435, 442, 447,
449, 452, 453, 458, 477, 478, 489, 502, 506, 518, 545, 546
Null Pointer 536
NVIDIA 391, 392, 398, 401, 404, 405

OceanofPDF.com
O
O_APPEND 185
O_CREAT 185
O_EXCL 185
O_NDELAY 185
O_NOCTTY 182, 183, 185
O_NONBLOCK 185
O_RDONLY 185, 425, 428, 430, 433, 435
O_RDWR 182, 185
O_SYNC 185
O_TRUNC 185
O_WRONLY 185
OBJ 270, 271, 308, 309, 310, 312, 314
Object Code Generation 20
OCR 235, 240, 242, 244, 293, 294, 295
Octal 121
onig 480, 481, 482
Oniguruma 479, 480, 482
open 185
Open Computing Language 362, 404
OpenBSD 470
OpenCL 5, 362, 363, 364, 365, 367, 368, 371, 372, 374, 375, 378, 379, 382, 383, 384, 386,
387, 388, 390, 391, 401, 404, 405
OpenGL 5, 258, 259, 260, 261, 263, 265, 267, 269, 270, 314, 496
OpenSSL 424, 426, 427, 429, 432, 434, 439, 441, 443, 444, 445, 446, 448, 450, 451, 452,
453, 455, 457, 458, 459, 460, 461, 462, 464, 465, 466, 468, 470, 472
OPENSSL_cleanse 472, 473
Operating System 493
Operators 51
Optical Character Recognition 235, 240, 242, 293
Optimization 19, 24, 514, 526
OR ​53, 126, 127, 130, 131, 135, 137, 138, 139, 185

OceanofPDF.com
P
Parallel computing 362, 391
PARENB 183, 187
Parsing 19, 217, 218, 222, 223, 226
PCM 325, 327, 329, 330, 331, 332, 336, 337, 338, 339, 340, 341, 345
PDF 234, 235, 236, 238, 239, 240, 241, 242, 243, 244
PDFBox 234
pdftoppm 240, 241
PI 3.14159 18, 50, 510
PIC​33
Pitch 358, 359
Pixel Subtraction 278
Playing Audio 359
PNG 241, 243, 272, 274, 275, 278, 280, 282, 288, 289, 290, 292, 297, 300, 302, 413
Pointer Addition 88
Pointer Arithmetic 87
Pointer Assignment 89
Pointer Comparison 89
Pointer Subtraction 88
Pointers 51, 62, 69, 70, 71, 75, 85, 86, 87, 88, 89, 91, 511, 540, 543
POLYNOMIAL 410, 412
Poppler 234, 236, 238, 239, 240, 242, 244
Poppler API 238
poppler_document_get_n_pages 237, 238, 239
poppler_document_get_page 237, 238, 239, 243
poppler_document_new_from_file 237, 238, 239, 242
poppler_page_get_text 237, 238, 239
PopplerDocument 237, 238, 239, 242
Portable Document Format 234, 238
position-independent code 33
POSIX 182, 245, 248, 251, 252
POST 163, 166, 167, 168, 169, 171, 172, 173, 174, 175, 176, 177, 179, 181
PostgreSQL 187, 191, 192, 194
pow() 92, 93, 95
Power of 10 5, 497, 498, 499, 500, 501, 503, 504, 505, 539, 540, 547
PQconnectdb 192
PQexec 192, 193
PQfinish 192, 193, 194
PQgetvalue 193
preprocessing 18
Preprocessing 17
preprocessor 17, 18, 21, 22, 49, 50, 493, 494, 497, 509, 510, 547
Preventing Double Inclusion 23
Prime Numbers 384, 395
printf 18, 58, 59, 65, 66, 67, 68, 72, 73, 75, 79, 89, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 134, 139,
140, 141, 142, 143, 144, 146, 149, 151, 155, 156, 160, 161, 171, 172, 173, 174, 179, 190, 193,
196, 202, 204, 206, 207, 209, 210, 213, 215, 216, 219, 220, 225, 227, 228, 248, 249, 255, 256,
272, 273, 294, 306, 310, 315, 320, 329, 338, 339, 340, 358, 360, 363, 364, 365, 367, 368, 371,
372, 374, 375, 378, 379, 382, 383, 386, 387, 389, 392, 394, 397, 398, 400, 402, 409, 411, 418,
423, 425, 428, 431, 434, 436, 442, 445, 446, 449, 451, 453, 458, 461, 469, 471, 473, 474, 475,
476, 477, 478, 481, 483, 484, 485, 487, 492, 493, 499, 507, 508, 510, 511, 512, 513, 541, 542,
543, 544, 545, 546
printf() 92, 106
Processing HTTP 169, 179
Program State 529
pthread_attr_t 245
pthread_create 245, 246, 247, 248, 249, 250, 255, 257
pthread_join 245, 246, 247, 248, 249, 250, 255, 257
pthread_t 245, 246, 247, 248, 249, 255, 257
Public Key Encryption 437, 439
Pulse Code Modulation 325
PUT 164, 166, 168, 169, 171, 172, 173, 174, 175, 177, 179, 181
puts() 92, 107
Python 5, 139, 306, 488, 489, 490, 491

OceanofPDF.com
Q
qsort() 92, 108
quicksort 92, 108

OceanofPDF.com
R
Race Conditions 254
RAM 479, 525
rand() 92, 107, 108, 376, 380, 399
read() 149, 151, 153, 157, 158
realloc 90, 91, 267
Real-Time 267
recv() 153, 155, 157, 158, 167, 175
Register Variables 525
Regular Expressions 479, 480
Relational Operators 52
Relocation 21
Reverb Effect 354
Right shift 129
RIPEMD-160 434, 435, 436, 437

OceanofPDF.com
S
S_IRGRP 186
S_IROTH 186
S_IRUSR 186
S_IWGRP 186
S_IWOTH 186
S_IWUSR 186
S_IXGRP 186
S_IXOTH 186
S_IXUSR 186
Safety-Critical Code 497, 498, 539, 540, 547
Scalable Vector Graphics 270
scanf() 92, 106
SDL 271, 272, 273, 274, 359, 360, 361, 496
Secure Handshake 456, 457
Secure Messaging 438, 441
Secure Server 461, 468
Secure Sockets Layer 456
SELECT 190, 193, 196, 197, 200, 203, 204, 205
self-signed certificate 461, 464, 465, 466, 467, 468
self-signed certificates 465
sem_init 252
sem_post 252
sem_wait 252
Semantic Analysis 19
Semaphores 250, 251
send() 153, 155, 157, 158, 159, 167
Sending HTTP 165, 175
Sensitive Data 470, 473, 478, 479
serial port 181, 182, 183, 184, 185
Serial Port 181, 182, 183, 184
Server 149, 151, 152, 153, 161, 164, 165, 169, 460, 461, 468
Set​139, 140, 142, 143, 260, 264, 266, 268, 282, 327, 329, 333, 338, 339, 342, 359, 366, 370, 373,
377, 381, 386, 459, 463
setjmp 498, 499
Setting a Bit 130
SF_FORMAT_ENDMASK 341
SF_FORMAT_SUBMASK 341
SF_FORMAT_TYPEMASK 341
SHA-1 405, 420, 424, 430
SHA-2 420, 430
SHA256 420, 424, 425, 426, 427, 429, 432
SHA3 427, 429, 432, 434
SHA-3 405
SHA-3 427
SHA-3 430
SHARED 37
Shell 491, 493, 539
short 43, 44
signed char 43, 44
sin() 92, 93
size_t 5, 58, 59, 60, 100, 179, 203, 204, 210, 228, 233, 328, 331, 335, 367, 370, 374, 377,
382, 386, 389, 402, 408, 409, 410, 415, 420, 445, 474, 477, 478, 506, 524, 525
sizeof 5, 58, 59, 60, 64, 65, 66, 67, 68, 90, 91, 104, 106, 109, 110, 149, 151, 154, 156, 160,
161, 166, 167, 168, 169, 170, 184, 209, 210, 212, 213, 215, 216, 227, 231, 233, 267, 322, 326,
331, 334, 336, 344, 348, 350, 352, 355, 363, 366, 369, 372, 376, 380, 385, 389, 394, 396, 399,
409, 411, 418, 423, 445, 446, 449, 453, 461, 462, 469, 473, 474, 475, 481, 502, 518, 523, 545,
546
sndfile 329, 334, 335, 337, 340, 342, 345, 347, 349, 352, 354
Source Files 22
SQLite 187, 194, 195, 197
sqlite3_close 195, 196, 197
sqlite3_exec 195, 196, 197
sqlite3_open 195
sqrt() 94
srand() 92, 107
SSL 420, 439, 456, 457, 458, 459, 460, 462, 464, 468, 469, 470
Standard library 92
start_routine 245
Statements 42, 54
Static 21, 27, 28, 30, 35, 36, 512
stderr 114, 115, 176, 177, 178, 180, 189, 190, 192, 193, 195, 196, 197, 199, 203, 204, 219,
221, 222, 224, 225, 227, 229, 231, 232, 275, 277, 280, 281, 283, 286, 287, 289, 290, 292, 294,
313, 327, 333, 335, 342, 346, 347, 350, 352, 355, 357, 419, 458, 462, 469, 481
stdin 114, 115, 116, 118, 119
stdout 114, 115, 116, 166, 167, 168, 169, 200, 453
Stereolithography 317, 318, 322
STL 317, 318, 319, 321, 322, 324
strcat() 92, 101
strchr() 92, 98
strcmp() 92, 102
strcpy() 92, 101
strdup() 92, 100
Streams 114, 115, 116
strerror 72, 176, 177, 178, 229, 335
String library 92
Strings 49, 83, 85, 101, 102, 107, 206, 539, 542
strlen() 92, 100
strncat() 92, 101
strncmp() 92, 102
strncpy() 92, 101
strrchr() 92, 98
strstr() 92, 99
strtok() 92, 99
struct 62, 67, 68, 73, 74, 75, 76, 97, 98, 141, 149, 151, 154, 156, 160, 161, 166, 170, 176,
177, 179, 183, 186, 211, 212, 213, 214, 216, 217, 229, 249, 250, 266, 322, 326, 379, 420, 429,
432, 462, 469, 489, 490, 517, 518, 544
Structures 73, 75, 76, 77, 120, 211, 212, 517, 540, 543
Subtraction 52, 88, 138, 278
SVG 270, 271, 296, 297, 298, 299, 300, 302, 303
Swap Space 478
switch 54
Symbol Resolution 20, 21
Symmetric AES Key 444
Symmetric Encryption 446
Symmetric Key 438, 441, 448, 451
Symmetric Key Decryption 438
Syntax Analysis 19
System Calls 524
system() 241, 491, 492, 493

OceanofPDF.com
T
tan() 92, 93
TCP Client 151
TCP Server 149
TCP/IP 147, 148, 153, 159
tcsetattr 183, 187
Tempo 358, 359
termios 182, 183, 186, 187
Ternary Operator 56
Tesseract 235, 293, 294, 295, 296
Testing 514, 526
Text Editor 538
threads 68, 70, 244, 245, 248, 250, 251, 252, 253, 254, 255, 256, 257, 368, 371, 375, 378,
383, 387, 392, 393, 397, 398, 400, 401, 469
Thresholding 290
TIFFs 279
Time library 92
time() 92, 96
TLS 420, 439, 456, 457, 458, 468
Toggling a Bit 132
tolower() 93, 113
toupper() 93, 113
Transport Layer Security 456
trust store 466, 467, 468
typedef 5, 61, 62, 63, 64, 141, 249, 266, 322, 326, 379, 420, 511

OceanofPDF.com
U
UDP 147, 148, 159, 161, 163
UDP Client 161
UDP Server 159
uint16_t 43, 46
uint32_t 43, 46
uint8_t 43, 46
Undefined Behaviour 537
Uninitialized Variables 537
Unit Test 533, 535
Unit testing 530
Unit Tests 532
Unix 20, 186, 189, 413, 470, 476, 478, 487, 494, 539
UNIX 16, 17, 148
unsigned char 43, 45
unsigned int 43, 45
unsigned long 43, 45
unsigned long long 43, 45
unsigned long long int 45
unsigned short 43, 45
unsigned short int 45
unzClose 231, 233, 234
unzCloseCurrentFile 231, 232
unzGoToFirstFile 231, 232, 233
unzOpen 231, 232, 233
unzOpenCurrentFile 231, 232, 233
UTF-8 195, 218, 479, 481, 482
UTHash 518, 519

OceanofPDF.com
V
Values Through Pointers 86
Variable Definitions 23
Variables 41, 43, 48, 65, 74, 250, 252, 525, 537, 539, 540
Vector Addition 368
Video Memory 495
void 47
volatile 5, 68, 69, 70, 71, 120, 474, 475, 476, 495
Volatile 69, 475

OceanofPDF.com
W
Warning Level 24
WAV 325, 326, 329, 332, 334, 336, 337, 338, 339, 340, 345, 347, 349, 352, 354
Waveform Audio File Format 325
Wavefront 270, 308
while 55
Whirlpool 432, 434, 436
Windows 20, 38, 182, 325, 487, 493, 494, 530, 531, 532, 535
write() 149, 151, 153, 158, 159

OceanofPDF.com
X
x509 465
XML 205, 217, 218, 219, 220, 222, 296, 297
xmlDocGetRootElement 219, 221
xmlFirstElementChild 219
xmlFreeDoc 219, 221, 222
xmlGetLastError 222
xmlGetProp 220
xmlNextElementSibling 219
xmlParseFile 218, 219, 221
XOR 127, 128, 132, 133, 135, 138, 139

OceanofPDF.com
Z
ZIP ​205, 230, 231, 232, 233, 234, 410, 413
zipClose 232, 233, 234
zipCloseFileInZip 233
zipOpen 232, 233
zipWriteInFileInZip 233
zlib 230, 233, 234

OceanofPDF.com
OceanofPDF.com

You might also like