Practical C A Comprehensive Guide - Rafal Jackiewicz
Practical C A Comprehensive Guide - Rafal Jackiewicz
com
PRACTICAL C
OCEANOFPDF.COM
A COMPREHENSIVE
GUIDE TO THE C
PROGRAMMING
LANGUAGE
by
Rafał Jackiewicz
OceanofPDF.com
Author: Rafał Jackiewicz
email: [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.
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
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
#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.
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:
After the C++ compiler mangles these names, they might look something
like this:
CPP:
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.
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"
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
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
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.
C:
// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H
#endif
C:
// mathlib.c
#include "mathlib.h"
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:
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:
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:
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.
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)
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
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)
add_executable(my_app main.cpp)
target_link_libraries(my_app my_static_lib)
bash:
mkdir build
cd build
cmake ..
make
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)
bash:
mkdir build
cd build
cmake ..
make
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)
if(NOT MY_DYNAMIC_LIB)
message(FATAL_ERROR "my_dynamic_lib not found")
endif()
target_link_libraries(my_app ${MY_DYNAMIC_LIB})
or another approach:
cmake:
cmake_minimum_required(VERSION 3.10)
project(MyApplication)
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)
bash:
mkdir build
cd build
cmake ..
make
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
multi-line comment */
C:
int myVariable;
C:
int myVariable = 5;
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
}
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.
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 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.
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.
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;
C:
type variable_name = value;
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:
Here, `constant_name` is the name of the constant you want to define, and
`value` is the value of the constant.
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:
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:
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
}
C:
if (condition) {
// code to be executed if condition is true
} else {
// code to be executed if condition is false
}
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:
- `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.
C:
condition ? result_if_true : result_if_false;
C:
int a = 10;
int b = 20;
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`.
C:
int a = 10;
int b = 20;
int c = 30;
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.
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;
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:
int arr[10];
size_t 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:
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.
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.
C:
Here's an example:
C:
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;
C:
struct Point {
int x;
int y;
};
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;
C:
C:
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)
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.
C:
#include <stdio.h>
int main() {
int a;
float b;
char c;
double 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.
C:
#include <stdio.h>
int main() {
int arr[10];
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])`.
C:
#include <stdio.h>
struct MyStruct {
int a;
double b;
char c;
};
int main() {
struct MyStruct s;
return 0;
}
In this example, `sizeof` is used to find the size of a user-defined struct
type.
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)];
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:
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:
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:
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:
C:
const volatile int x = 10;
const volatile int *p = &x;
C:
int x = 10;
int * const volatile p = &x;
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:
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:
C:
C:
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;
};
C:
struct Student john;
C:
C:
C:
Now, `p` points to `john`. To access the fields of the structure that `p` points
to, we use the arrow operator (`->`):
C:
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;
};
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`.
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.
C:
Student students[4] = {
{1, "John Doe"},
{2, "Jane Doe"},
{3, "Jim Doe"},
{4, "Jill Doe"}
};
C:
Student secondStudent;
memcpy(&secondStudent, &students[1], sizeof(Student));
C:
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>
int main() {
// Create an array of Student objects
Student students[4] = {
{1, "John Doe"},
{2, "Jane Doe"},
{3, "Jim Doe"},
{4, "Jill Doe"}
};
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:
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};
C:
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:
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];
C:
int numbers[5];
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";
C:
char firstLetter = name[0]; // firstLetter is now 'J'
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;
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`.
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`.
C:
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:
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:
C:
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:
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;
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.
C:
- `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:
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.
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.
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.
C:
#include <stdio.h>
#include <math.h>
int main() {
double angle = M_PI / 4; // 45 degrees in radians
return 0;
}
C:
#include <stdio.h>
#include <math.h>
int main() {
double num = 2.0;
return 0;
}
C:
#include <stdio.h>
#include <math.h>
int main() {
double num = 10.0;
return 0;
}
C:
#include <stdio.h>
#include <math.h>
int main() {
double num = 16.0;
return 0;
}
C:
#include <stdio.h>
#include <math.h>
int main() {
double num = 10.7;
return 0;
}
C:
#include <stdio.h>
#include <math.h>
int main() {
double base = 2.0, exponent = 3.0;
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.
- 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);
return 0;
}
#include <stdio.h>
#include <time.h>
int main() {
clock_t start = clock();
// Execute some code here
clock_t end = clock();
return 0;
}
C:
#include <stdio.h>
#include <time.h>
int main() {
time_t start = time(NULL);
// Execute some code here
time_t end = time(NULL);
C:
#include <stdio.h>
#include <time.h>
int main() {
time_t current_time = time(NULL);
struct tm *time_info = localtime(¤t_time);
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.
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;
}
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;
}
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;
}
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;
}
C:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
size_t length = strlen(str);
return 0;
}
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);
return 0;
}
- 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);
C:
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello";
char str2[] = "World";
int result;
return 0;
}
C:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, world!";
char dest1[20];
char dest2[20];
char dest3[20];
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;
}
- 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;
}
- 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;
}
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;
}
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;
}
- 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;
}
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.
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;
}
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;
}
- 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]);
return 0;
}
- 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>
return 0;
}
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;
}
return 0;
}
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;
}
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;
}
- 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';
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;
}
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;
}
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;
}
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.
C:
#include <stdio.h>
int main() {
char buffer[5];
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];
return 0;
}
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.
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.
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.
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.
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
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.
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.
C:
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.
C:
C:
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:
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.
C:
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:
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
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
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:
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.
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:
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:
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:
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:
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:
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 result = x | y;
C:
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:
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;
}
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;
if (exists) {
printf("%d exists in the set\n", value);
} else {
printf("%d does not exist in the set\n", value);
}
return 0;
}
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}};
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;
}
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");
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);
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);
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.
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();
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.
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.
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
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:
#define MAX 80
#define PORT 8080
#define SA struct sockaddr
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:
Client side:
bash:
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>
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
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;
}
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>
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};
memset(&serv_addr,
'0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
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;
}
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()`.
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>
int main() {
char buffer[MAX_BUFFER];
char *message = "Hello Client";
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);
return 0;
}
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>
int main() {
char buffer[MAX_BUFFER];
char *message = "Hello Server";
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");
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.
bash:
gcc udp_server.c -o server
gcc udp_client.c -o client
bash:
./server
./client
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
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.
C:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
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));
http_get(socket_fd, "/index.html");
close(socket_fd);
return 0;
}
C:
memset(buffer, 0, sizeof(buffer));
while (recv(socket_fd, buffer, sizeof(buffer), 0) > 0) {
fputs(buffer, stdout);
}
}
C:
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\"}");
C:
memset(buffer, 0, sizeof(buffer));
while (recv(socket_fd, buffer, sizeof(buffer), 0) > 0) {
fputs(buffer, stdout);
}
}
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.
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int server_fd, client_fd;
struct sockaddr_in address;
int address_len = sizeof(address);
char buffer[MAX_BUFFER_SIZE] = {0};
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
while(1) {
printf("\n+++++++ Waiting for new connection ++++++++\n\n");
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.
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);
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"
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`.
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;
}
C:
#include <stdio.h>
#include <curl/curl.h>
int main() {
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;
struct curl_slist *headers = NULL;
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;
}
C:
#include <stdio.h>
#include <curl/curl.h>
int main() {
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;
struct curl_slist *headers = NULL;
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;
}
C:
#include <stdio.h>
#include <curl/curl.h>
int main() {
CURL *curl = curl_easy_init();
if(curl) {
CURLcode res;
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>
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" */
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;
}
event_base_dispatch(base);
return 0;
}
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);
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];
This code reads up to 256 bytes from the serial port into a buffer, and then
writes a string to the port.
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.
C:
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:
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.
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 */
};
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.
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;
}
mysql_close(con);
return 0;
}
Executing Queries
Once a connection is established, we can execute SQL queries using the
`mysql_query` function:
C:
Retrieving Results
After executing a SELECT query, we can retrieve the results using the
`mysql_store_result` and `mysql_fetch_row` functions:
C:
if (result == NULL) {
fprintf(stderr, "%s\n", mysql_error(con));
mysql_close(con);
return 1;
}
MYSQL_ROW row;
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.
C:
mysql_close(con);
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:
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;
}
PQfinish(conn);
return 0;
}
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) {
fprintf(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:
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return 1;
}
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.
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;
return 1;
}
sqlite3_close(db);
return 0;
}
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);";
if (rc != SQLITE_OK ) {
fprintf(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:
printf("\n");
return 0;
}
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.
C:
sqlite3_close(db);
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.
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 ();
bson_destroy (doc);
mongoc_collection_destroy (collection);
mongoc_database_destroy (database);
mongoc_client_destroy (client);
mongoc_cleanup ();
return 0;
}
Executing Queries
Once a connection is established, we can execute SQL queries using the
`mongoc_collection_insert_one` function:
C:
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;
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.
C:
mongoc_cleanup();
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.
bash:
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();
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:
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:
while (cass_iterator_next(iterator)) {
const CassRow* row = cass_iterator_get_row(iterator);
const CassValue* value = cass_row_get_column_by_name(row, "column1");
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.
C:
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.
C:
C:
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}
return 0;
}
C:
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;
}
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.
C:
#include <stdio.h>
int main() {
// Create a large array filled with integer data
int data[SIZE];
for(int i = 0; i < SIZE; i++) {
data[i] = i;
}
// Close file
fclose(file);
return 0;
}
C:
#include <stdio.h>
// 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;
};
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);
}
C:
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:
#include <stdio.h>
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]"}
};
return 0;
}
C:
#include <stdio.h>
return rec;
}
int main() {
// Open the database file
FILE *file = fopen("database.dat", "rb");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}
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:
xml:
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;
}
xmlFreeDoc(doc);
return 0;
}
C:
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:
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>
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;
}
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:
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);
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
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;
json_decref(root);
return 0;
}
C:
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;
}
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);
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.
csv:
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
fclose(file);
return 0;
}
In this code, we first open the file with `fopen`. We then read each line of
the file with `fgets`.
csv:
C:
#include <stdio.h>
#include <csv.h>
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;
}
}
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.
bash:
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>
int main() {
unzFile *zipfile = unzOpen("example.zip");
if (zipfile == NULL) {
fprintf(stderr, "Failed to open ZIP file\n");
return 1;
}
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;
}
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.
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.
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.
C:
#include <poppler.h>
int main() {
PopplerDocument *doc;
GError *error = NULL;
gchar *uri;
if (doc == NULL) {
g_print("Error creating document\n");
return 1;
}
int num_pages, i;
num_pages = poppler_document_get_n_pages(doc);
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.
cmake:
C:
#include <poppler.h>
int main() {
PopplerDocument *doc;
GError *error = NULL;
gchar *uri;
int num_pages, i;
num_pages = poppler_document_get_n_pages(doc);
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:
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
if (doc == NULL) {
g_print("Error creating document\n");
return 1;
}
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:
C:
#include <pthread.h>
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);
C:
#include <pthread.h>
int main() {
pthread_t thread_id;
pthread_create(&thread_id, NULL, my_thread, NULL);
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.
C:
#include <pthread.h>
#include <stdio.h>
int main() {
pthread_t thread_id;
int number = 42;
pthread_create(&thread_id, NULL, print_number, &number);
pthread_join(thread_id, NULL);
return 0;
}
C:
#include <pthread.h>
#include <stdio.h>
pthread_join(thread_id, NULL);
return 0;
}
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>
void critical_section() {
pthread_mutex_lock(&lock);
// Access shared resources
pthread_mutex_unlock(&lock);
}
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);
}
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>
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);
}
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.
C:
#include <pthread.h>
#include <stdio.h>
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);
return 0;
}
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>
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);
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
}
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
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
}
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>
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();
// Reverse direction when the square reaches the edge of the screen
if (x_position >= 1.0f || x_position <= -1.0f) {
delta = -delta;
}
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.
C:
#include <GL/glut.h>
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();
}
}
C:
#include <GL/glut.h>
#include <stdlib.h>
typedef struct {
int x, y;
} Point;
glFlush();
}
C:
#include <GL/glut.h>
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();
}
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:
C:
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
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:
C:
C:
C:
C:
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;
// 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;
}
// 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;
}
// Clean up
pixDestroy(&pixs);
pixDestroy(&pixd);
return 0;
}
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;
/* Cleanup. */
pixDestroy(&pix1);
pixDestroy(&pix2);
pixDestroy(&pixd);
return 0;
}
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;
/* Cleanup. */
pixDestroy(&pixs);
pixDestroy(&pixd);
return 0;
}
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;
// Clean up
pixDestroy(&image);
pixDestroy(&translatedImage);
return 0;
}
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;
// Clean up
pixDestroy(&image);
pixDestroy(&sharpenedImage);
return 0;
}
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;
// Clean up
pixDestroy(&image);
pixDestroy(&blurredImage);
return 0;
}
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;
/* Cleanup. */
pixDestroy(&pixs);
pixDestroy(&pixd);
return 0;
}
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;
/* 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;
}
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;
/* Cleanup. */
pixDestroy(&pixs);
pixDestroy(&pixd);
return 0;
}
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;
/* 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.
C:
#include <tesseract/capi.h>
#include <leptonica/allheaders.h>
int main() {
TessBaseAPI *api = TessBaseAPICreate();
TessBaseAPIInit3(api, NULL, "eng");
if (TessBaseAPIRecognize(api, NULL) != 0) {
fprintf(stderr, "Could not recognize image\n");
return 1;
}
// Clean up
TessDeleteText(outText);
pixDestroy(&image);
TessBaseAPIEnd(api);
TessBaseAPIDelete(api);
return 0;
}
cmake:
cmake_minimum_required(VERSION 3.22)
project(untitled34 C)
set(CMAKE_C_STANDARD 17)
set(Leptonica_INCLUDE_DIRS "/usr/local/include/leptonica")
set(Leptonica_LIBRARIES "/usr/local/lib/liblept.so")
add_executable(untitled34 main.c)
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.
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.
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.
bash:
C:
#include <cairo.h>
#include <cairo-svg.h>
int main() {
cairo_surface_t *surface;
cairo_t *cr;
cairo_destroy(cr);
cairo_surface_destroy(surface);
return 0;
}
Compile it using:
bash:
Run the program and it will generate an SVG file named `circle.svg` with a
red circle having a black border.
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();
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:
After running this, you'll have a PNG file, `output.png`, rendered from your
input SVG file.
C:
#include <cairo.h>
#include <cairo-svg.h>
#include <math.h>
int main() {
cairo_surface_t *surface;
cairo_t *cr;
int i;
);
cairo_destroy(cr);
cairo_surface_destroy(surface);
return 0;
}
Compile it with:
bash:
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:
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)`.
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.
obj:
- 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:
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:
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>
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}
};
// 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}
};
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
if (!file) {
fprintf(stderr, "Unable to open file %s\n", filename);
exit(1);
}
fclose(file);
}
int main() {
generate_sphere_obj(1.0, 40, "sphere.obj");
return 0;
}
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.
C:
#include <stdio.h>
#include <cglm/cglm.h>
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}
};
// 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}
};
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).
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>
if (file == NULL) {
printf("Error: unable to open file for writing\n");
return;
}
// 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}
};
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}
};
fprintf(file, "endloop\n");
fprintf(file, "endfacet\n");
}
fclose(file);
}
int main() {
write_stl_file("cube.stl");
return 0;
}
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;
int main() {
FILE *f = fopen("sphere.stl", "wb");
// 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.normal = facet.v1;
writeFacet(f, facet);
numFacets++;
}
}
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.
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;
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;
}
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];
}
// 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>
int main() {
SF_INFO input_info, output_info;
SNDFILE *input_file, *output_file;
double buffer[BUFFER_SIZE];
// 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>
int main() {
faacEncHandle encoder;
faacEncConfigurationPtr 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);
// 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>
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;
}
// 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;
// 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;
}
// 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>
int main() {
SF_INFO info;
SNDFILE *input_file, *output_file;
short buffer[BUFFER_SIZE];
// 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:
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;
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>
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);
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);
}
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>
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;
}
// 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>
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;
}
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>
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;
}
// 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>
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;
}
// 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>
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;
}
// 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>
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
// 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.
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 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;
SDL_CloseAudioDevice(deviceId);
SDL_FreeWAV(wavBuffer);
SDL_Quit();
return 0;
}
bash:
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.
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:
int main() {
// Get the number of platforms
cl_uint numPlatforms;
clGetPlatformIDs(0, NULL, &numPlatforms);
return 0;
}
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:
int main() {
// Define the data
float a[1] = {5.0f};
float b[1] = {7.0f};
float c[1] = {0.0f};
// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);
// Clean up
clReleaseMemObject(aMem);
clReleaseMemObject(bMem);
clReleaseMemObject(cMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}
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:
int main() {
// Define the size of the vectors
unsigned int n = 1000000;
// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);
// Clean up
free(a);
free(b);
free(c);
clReleaseMemObject(aMem);
clReleaseMemObject(bMem);
clReleaseMemObject(cMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}
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:
// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);
*)&CMem);
clSetKernelArg(kernel, 3, sizeof(unsigned int), (void *)&N);
// Clean up
free(A);
free(B);
free(C);
clReleaseMemObject(AMem);
clReleaseMemObject(BMem);
clReleaseMemObject(CMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}
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:
int main() {
// Define the number of work items and the number of iterations per work item
unsigned int numWorkItems = 1000;
unsigned int iterationsPerWorkItem = 1000000;
// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);
// Clean up
free(seed);
free(count);
clReleaseMemObject(seedMem);
clReleaseMemObject(countMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}
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:
typedef struct {
float x;
float y;
float z;
float w;
} float4;
int main() {
// Define the number of bodies and the time step
int numBodies = 1000;
float dt = 0.01f;
float softeningSquared = 0.00125f;
NULL);
// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, 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);
// Clean up
free(pos);
free(vel);
clReleaseMemObject(posMem);
clReleaseMemObject(velMem);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}
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:
int main() {
// Define the count of numbers
int count = 100;
// Create a context
cl_context context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, NULL);
return 0;
}
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);
}
// 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);
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).
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.
C:
#include <stdio.h>
#include <assert.h>
#include <cuda.h>
#include <cuda_runtime.h>
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;
C:
int main(void)
{
// set the size of the array (1 in this case)
int N = 1;
// 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:
int main() {
// Define the count of numbers
int count = 100;
return 0;
}
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 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;
// 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);
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>
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);
}
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.
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;
C:
void init(void) {
uint32_t i, j, 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:
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;
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:
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>
uint32_t crc_tab[256];
uint32_t crc;
uint32_t length = 0;
void init(void) {
uint32_t i, j, r;
void finalize(void) {
size_t i;
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;
}
}
return ~crc;
}
generate_table();
uint32_t crc = crc32(file);
printf("CRC32 Checksum: %08x\n", crc);
fclose(file);
return 0;
}
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.
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>
// 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 };
//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
// 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;
// cleanup
free(msg);
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));
// Open file
file = fopen(argv[1], "rb");
if (!file) {
printf("Unable to open file %s", argv[1]);
return 1;
}
// Allocate memory
buffer=(uint8_t *)malloc(fileLen+1);
if (!buffer) {
fprintf(stderr, "Memory error!");
fclose(file);
return 1;
}
// 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;
// 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;
if (ferror(file)) {
perror("File read failed");
fclose(file);
return EXIT_FAILURE;
}
fclose(file);
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:
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>
SHA256_Init(&sha256);
ssize_t bytesRead;
if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}
SHA256_Final(hash, &sha256);
close(file);
return 0;
}
return 0;
}
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:
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>
EVP_DigestInit(mdctx, md);
ssize_t bytesRead;
if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}
return 0;
}
hash_file(argv[1]);
return 0;
}
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>
blake2b_init(S, BLAKE2B_OUTBYTES);
ssize_t bytesRead;
if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}
return 0;
}
hash_file(argv[1]);
return 0;
}
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>
EVP_DigestInit(mdctx, md);
ssize_t bytesRead;
if (bytesRead < 0) {
perror("Failed to read file");
close(file);
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
C:
#include <stdio.h>
#include <openssl/evp.h>
#include <fcntl.h>
#include <unistd.h>
EVP_DigestInit(mdctx, md);
ssize_t bytesRead;
if (bytesRead < 0) {
perror("Failed to read file");
close(file);
return -1;
}
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.
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:
Now, let's say you have a message in a file named `message.txt` that you
want to encrypt.
bash:
bash:
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.
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;
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:
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
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.
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;
}
// 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.
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;
}
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.
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);
int main() {
return decrypt_symmetric_key();
}
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);
// Clean up
EVP_CIPHER_CTX_free(ctx);
return EXIT_SUCCESS;
}
int main() {
return decrypt_message();
}
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);
}
return preverify_ok;
}
int main() {
const char *hostname = "www.google.com";
const char *port = "443";
BIO_get_ssl(bio, &ssl_conn); // Get the SSL object from the BIO object
// Clean up
BIO_free_all(bio);
SSL_CTX_free(ssl_ctx);
return 0;
}
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>
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 (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;
}
if (!SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
}
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";
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.
bash:
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
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`.
bash:
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
bash:
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.
bash:
sudo rm /usr/local/share/ca-certificates/server.crt
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.
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);
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.
C:
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.
C:
#include <stdio.h>
#include <bcrypt.h>
int main() {
char stored_hash[BCRYPT_HASHSIZE];
C:
#include <stdio.h>
#include <string.h>
#include <openssl/crypto.h>
int main() {
char sensitive_data[] = "This is some sensitive data";
return 0;
}
C:
#include <stdio.h>
#include <string.h>
int main() {
char sensitive_data[] = "This is some 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>
int main() {
char sensitive_data[] = "This is some sensitive data";
return 0;
}
C:
#include <stdio.h>
int main() {
volatile char sensitive_data[] = "This is some sensitive data";
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);
// Now the memory should be read-only, and this strcpy should fail
strcpy(buffer, "This will fail");
return 0;
}
#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);
return 0;
}
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.
bash:
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)
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.";
region = onig_region_new();
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:
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
);
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.
C:
#include <stdio.h>
int main() {
int data = 5;
int result;
__asm__ ("addl %1, %0"
: "=r" (result)
: "r" (data), "0"(10)
:
);
In this case, we are adding the value of `data` to the constant value 10 and
storing the result in the `result` variable.
C:
#include <stdio.h>
int main() {
int result;
__asm__ ("movl $5, %%eax; movl %%eax, %0"
: "=r" (result)
:
: "%eax"
);
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.
java:
bash:
javac NativeExample.java
javah -jni NativeExample
C:
#include <jni.h>
#include <stdio.h>
#include "NativeExample.h"
bash:
bash:
java:
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.
C:
#include <Python.h>
python:
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.
python:
import myModule
print(myModule.myFunction(5, 7))
print(myModule.myOtherFunction(3.0, 2.0))
bash:
12
6.0
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.
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
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.
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.
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.
C:
int main() {
print_char(0, 0, 'A', 0x07);
return 0;
}
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);
}
}
C:
int factorial(int n) {
int result = 1;
for(int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
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:
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.
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;
}
Return 0;
}
C:
int main() {
// Automatically allocate memory for an integer
int value = 10;
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...
}
C:
void doTask1(int i) {
// Code for task 1...
}
void doTask2(int i) {
// Code for task 2...
}
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.
C:
#include <assert.h>
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.
#include <stdio.h>
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.
C:
int main() {
printf("Value of PI: %f\n", PI);
return 0;
}
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.
C:
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;
}
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.
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;
}
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:
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.
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:
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;
};
C:
#include "uthash.h"
#include <stdio.h>
struct Student {
int id;
float grade;
UT_hash_handle hh;
};
C:
#include <stdlib.h>
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>
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.
C:
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:
C:
#include <stdlib.h>
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:
C:
#include <fcntl.h>
#include <unistd.h>
C:
#include <fcntl.h>
#include <unistd.h>
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.
C:
void foo() {
register int i;
for (i = 0; i < 1000000; i++) {
// ... Do some computation ...
}
}
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 instance, if you're using the gcc compiler to compile a program written
in C, you would use:
bash:
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
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
bash:
(gdb) print myVariable
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
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.
These packages include the Check unit testing framework, the GCC
compiler, and the make build automation tool.
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.
C:
#include "factorial.h"
long factorial(int n) {
if(n < 0) {
return -1;
}
if(n == 0) {
return 1;
} else {
return n * factorial(n-1);
}
}
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.
On Linux:
bash:
gcc -o check_factorial check_factorial.c factorial.c `pkg-config --cflags --libs check`
bash:
bash:
./check_factorial
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.
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.
C:
#include <stdio.h>
int main() {
int a = 10;
float b = 20.5;
char c = 'x';
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;
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 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!";
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;
*p = 20;
return 0;
}
C:
#include <stdio.h>
#include <math.h>
struct Point {
double x;
double y;
};
return 0;
}
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;
}
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
gcc29, 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
PIC33
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
Set139, 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