Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
23 views

Programming With C and C++

Uploaded by

kocazeynep222
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views

Programming With C and C++

Uploaded by

kocazeynep222
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 363

Contents

Programming in C and C++ 3


Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

C programming 6
Basic Hello World program . . . . . . . . . . . . . . . . . . . . . . . . 7
Basic Data types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
sizeof operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Format Specifier . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
const keyword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
type definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
typecasting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Scope and Lifetime of the variables . . . . . . . . . . . . . . . . . . . . 20
Control statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Goto statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Function returning local data . . . . . . . . . . . . . . . . . . . . 55
Variadic functions . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Function like macros . . . . . . . . . . . . . . . . . . . . . . . . . 64
inline functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
String manipulation operations . . . . . . . . . . . . . . . . . . . 66
Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Pass by value and Pass by reference in functions . . . . . . . . . 80
Dynamic Memory Allocation . . . . . . . . . . . . . . . . . . . . . . . 85
Double pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Recap about variables and scope . . . . . . . . . . . . . . . . . . 96
Function Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Bit fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Structure padding and packing . . . . . . . . . . . . . . . . . . . 110
Enumeration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Appendix A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Significance of header files . . . . . . . . . . . . . . . . . . . . . . 116
Header description . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Compilation of C program . . . . . . . . . . . . . . . . . . . . . . 118
GCC compilation options . . . . . . . . . . . . . . . . . . . . . . 119
Valgrind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Command line arguments (argc, argv) . . . . . . . . . . . . . . . 121

1
File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Operating with the binary files . . . . . . . . . . . . . . . . . . . 130
I/O operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Useful macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Useful helper functions . . . . . . . . . . . . . . . . . . . . . . . . 140

C++ programming 141


Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
cout, cerr and cin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
New operators in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
New keywords in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Typecasting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Constructors and Destructors . . . . . . . . . . . . . . . . . . . . 161
namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . 167
Function Overloading . . . . . . . . . . . . . . . . . . . . . . . . 185
Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . 186
noexcept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Standard library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
std::pair . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
std::initializer_list . . . . . . . . . . . . . . . . . . . . . . . . . . 193
std::bitset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
File streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Dequeue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
shared_ptr, unique_ptr . . . . . . . . . . . . . . . . . . . . . . . 217
File systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
Mutexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Conditional Variables . . . . . . . . . . . . . . . . . . . . . . . . 226
Derived Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Template overloading . . . . . . . . . . . . . . . . . . . . . . . . . 236
Appendix B . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Use cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Design Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Factory Design pattern . . . . . . . . . . . . . . . . . . . . . . . . 259

2
Singleton Design pattern . . . . . . . . . . . . . . . . . . . . . . . 260
Builder Design pattern . . . . . . . . . . . . . . . . . . . . . . . . 263
Adapter Design pattern . . . . . . . . . . . . . . . . . . . . . . . 266

Appendix C 267
Code organization for software development . . . . . . . . . . . . . . . 268
Building large software . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
creating libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
creating binaries . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
cmake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273

Data Structures 279


Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
Doubly Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
Circular Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
Circular Doubly Linked Lists . . . . . . . . . . . . . . . . . . . . . . . 318
Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
Ring Buffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
Merkel Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
Hash Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341

Search and Sorting 346


Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
Bubble Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
Insertion Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
Quick Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
Merge Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
Searching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Binary search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358

Changelog 362

Programming in C and C++


This is a work in progress book.
The users are allowed to read the book as many times as they want. Copying
and redistribution without permission are prohibitted.
All the source code of this book is kept here.
Ubuntu OS is used to write this book and gcc, g++ compilers are used to compile
and run the programs in this book.
Copyright: Devendra Naga All rights reserved.

3
Introduction
This book tries to explain programming in C and C++. C and C++ although are
the oldest programming languages they are still in use by most of the operating
systems such as Linux, RTOSes such as FreeRTOS.
Both languages are still used in firmware development. Sometimes with libc
and libstdc++ and sometimes not using libc and libstdc++. When writing
code for Linux systems, most of the time libraries are used when writing the
software. Using the library simplifies the software development as it provides
most of the interfaces. In cases where the libraries become costly are when we
are programming smallscale devices such as microcontrollers with limited RAM
and Flash memory. In such cases, the preference is to use the programming
language’s constructs as much as possible than using any of the library functions.
For example, the following are provided by the C and C++ libraries.
1. string manipulation
2. console operations (writing to and reading from console).
3. file system interaction (FILE i/o)
4. Operating system abstractions in case of C++ (such as creation and use
of threads, interacting with file system)
The compiler implements the C, C++ languages. It verifies the program is
compliant to the language specification and generates a binary file.
To install gcc and g++ on Ubuntu:
sudo apt install gcc g++

4
Audience
The audience of this book are basic and intermediate programmers. For advanced
programmers, you may already know about the programming constructs.

5
C programming

6
Basic Hello World program
Below is a basic hello world program in C.
#include <stdio.h>

int main()
{
// prints Hello World on console
printf("Hello World\n);

return 0;
}
Example.1 : hello_world.c
The main() is a function and the program always executes from main(). The
function call printf, allows the program to write to a console. The header file
stdio.h contains the prototype of printf.
The // indicates comment line. Anything that goes after // for the entire line is
ignored. Generally these are called C++ styled comments. These are not multi
line comments.
There is another type of comment style that start with /* and ends with */.
This can be used as a multiline comment.
Here’s one example,
/*
This is a multi line comment,
showing more than one line can be written.
*/
The preference of using // or /* entirely depends on their coding style.
The statement return 0 specifies that the function returns 0. In C, a function
may or may not return depend on the function signature.
In order to run the program above, we need to create an executable. To create
this executable, we need to compile it.
Copy and Paste the above program in a text editor and save it in a file called
hello_world.c.
All the source code files written in C will have an extension .c or .h.
The files with .h extensionn are called header files. Header files contain the
following.
1. Macro definitions.
2. Function prototypes.
3. Data structure definitions.

7
The below command is used to compile the C file.
gcc hello_world.c

8
Basic Data types
Below are the following data types in C. All are applicable for C++ as well.
Below are some of the data types for 32 bit systems.

S.No type description ranges


1 char 1 byte signed integer -127 to 127
2 unsigned char 1 byte unsigned integer 0 to 255
3 short int 2 byte signed integer -32767 to 32767
4 unsigned short int 2 byte unsigned integer 0 to 65535
5 int 4 byte signed integer -2147483648 to 2147483647
6 unsigned int 4 byte unsigned integer 0 to 4294967295
7 float 4 byte floating point variable -
8 double 4 / 8 byte double variable -

A variable of the above type can be declared as follows.


int a;
a is a variable of type int.
int a = 4;
The above statement initializes a to 4.
int a;

a = 4;
int a = 0x20;
initializes a with a hexadecimal number. Hexadecimals are noted with the 0x.
The above statement assigns a to 4.
Sometimes in a program we initialize variables and we assign the variables a
value sometime later.
A variable that is not initialized is called uninitialized variable. Uninitialized
variables are a bigger problem when using them. The reason being that they
hold some unknown value when declared. When the variable is used without
initialized, the behavior of the program when run will be unexpected. This
generally results in incorrect execution and so provides incorrect results.
The ideal solution is to always initialize a variable when declaring them to avoid
unwanted problems that arise with uninitialized variables.
Each declared variable is associated with a type. Read Recap section about the
variables and their scope.
Declaring doubles:

9
double f = 3.14;
Double values are represented after the decimal .. Exact double values are not
possible to represent in C.

10
sizeof operator
The sizeof operator is used find out the size of a data type. Below is an
example.
#include <stdio.h>

int main()
{
int a;

printf("size a: %d\n", sizeof(a));

return 0;
}
Example. 2 sizeof operator
The sizeof operator can be used on a variable or the data type itself. Such as
calling sizeof(int) is valid.
The sizeof is generally computed at compile time by the compiler and replaces
the portion of the code containing the call to sizeof with the computed value.

11
Format Specifier
The functions printf and scanf and standard library functions when linked.
The prototypes of these functions look as follows.
int printf(const char *fmt, ...); // shown here for easy reading .. this is not same as prin
int scanf(const char *fmt, ...); // shown here for easy reading .. this is not same as scanf
The first argument generally requires a format specifier in a particular format.
Below are some of the format specifiers for printf and scanf. They apply for
fprintf, vfprintf, vsnprintf fscanf and sprintf as well.
The %d used to print integers. The function printf recognizes the integer
variables given as function arguments when specified as %d.
The body portion start with " and end with " is called as string. More about
the strings in the Strings section below.
Below are some of the format specifiers.

S.No Format specifier Meaning


1 %d integer
2 %c character
3 %s string
4 %f float or double
5 %u unsigned integer
6 %ld long integer
7 %lld longlong integer
8 %lu long unsigned integer
9 %llu long long unsigned integer
10 %x hexadecimal

Below are some more functions that use the format specifiers. Functions are
described in detail below.

S.No Function Name


1 scanf
2 fprintf
3 fscanf
4 vfprintf
5 vfscanf

For example a call to print an integer is as follows,


printf("%d\n", 3); // prints integer
If there are multiple variables to be printed, they can be printed as well.

12
printf("%d %c %f\n", 3, 'f', 6.1);
One does not have to give the space for each format specifier to print them. The
space is given only for readability perspective.
The printf function parses and understand the format specifier in the order
given in the rest of the arguments.
For example, if the order of variables or the order of the format specifiers is
incorrect, this would result in incorrect print results.
printf("%c %d %f\n", 3, 'f', 6.1);
The above statement would print a completely different data. Thus ordering the
format specifiers with the variables is very important.

13
const keyword
The const keyword is generally applied on variables that does not change their
value over the execution time.
const int a = 4;
defines a constant int of 4. Sometimes a result of a mathematical calculation
can also be a constant.
const double radius = 3;
const double circumferance = 2 * 3.14 * 3;
The const keyword is used in many places in this book and is explained in the
below sections.

14
type definition
Any type can be type defined to another type. The keyword typedef is used for
this purpose.
typedef int integer_t;
Now, integer_t can be used as a new type to declare variables. The typedef
can be applied for many other data types such as structures and function pointers.
Generally the typedef is used to give a meaningful name to the type or when
the type used is too long, typedef provides an alternative way to represent it in
the short form.
For example,
typedef unsigned int u_int32_t; // unsigned 32 bits integer
typedef unsigned char u_int8_t; // unsigned 8 bits integer
C allow extending new types with the use of struct, this is described in the
Structures section.
In the below sections, the typedef is used in many places and it is explained
further.

15
typecasting
Variables of one type can be typecasted to other variables. Typecasting is
sometimes unavoidable. More about this is described in void pointer section.
The below program gives an example of typecast from integer to double. Down-
load it here
#include <stdio.h>

int main()
{
int d;
double v = 10.1;

d = (int)v;

printf("d %d v %f\n", d, v);

return 0;
}
Example.3 Typecasting
Typecasting double to int loses the precision and the number loses the decimal
points. Note that the number will not get rounded off to the nearest integer.
For example,
int r;
double p = 10.99;

r = (int)p; // rounds off to 10.


will round off the value 10.99 to 10 and assigns to r.

16
operators
C has below operators that can be used on the variables of given types.

S.No operator meaning


1 + addition
2 - subtraction
3 * multiplication
4 / division
5 % modulo
6 = equals to
7 == comparison operator
8
9 && logical AND
10
11 & AND
12 ˆ XOR
13 ! NOT
14 !! Logical NOT
15 ++ increment operator
16 – decrement operator

Below example shows an example of the operators.


#include <stdio.h>

int main()
{
int a = 4;
int b = 2;
int sum;
int sub;
int mul;
int div;
int mod;

sum = a + b; // add two numbers


sub = a - b; // subtract two numbers
mul = a * b; // multiply two numbers
div = a / b; // divide two numbers
mod = a % b; // modulo two numbers

printf("sum %d sub %d\n", sum, sub);


printf("mul %d div %d modulo %d\n", mul, div, mod);

17
return 0;
}
Example.4 Operators example
The ++ and -- are increment and decrement operators. Below example shows
how to use them.
The boolean operations such as | , &, ||, &&, ˆ and ! never apply to the double
or float variables.
#include <stdio.h>

int main()
{
int i = 0;

printf("i %d \n", ++ i);

return 0;
}
Example.5 Pre increment operator
The above program prints the value 1.
Consider another program.
#include <stdio.h>

int main()
{
int i = 0;

printf("i %d\n", i ++);

return 0;
}
Example.6 Post increment operator
The above program prints the value 0.
This is generally called the undefined behavior. The language leaves the behavior
upto the compiler. The ++ i used, this is called prefix notation and the i ++ is
the postfix notation.
In general, it is upto the programmer to choose ++i or i++ appropriately.
However, choosing ++i makes it less paranoid when debugging the software.
More usecases of ++ and -- in while and for.

18
The !! is used to check the value of a number is non zero or zero. Below example
shows how to use it.
#include <stdio.h>

int main()
{
int a = 4;
int b = 0;

printf("a=%d b=%d\n", !!a, !!b);

return 0;
}
Example.7 Logical NOT operator
This means even if a number is negative (non-zero) the !! operation would
result in 1.
For example in the below snippet, the values of !!r and !!p results to 1 and 1
respectively.
int r = -1;
int p = 1;

printf("%d %d\n", !!r, !!p);


Below example shows the use of & | and ˆ operators. Download it here.
#include <stdio.h>

int main()
{
int a = 0x80;
int b = 0x81;

printf("AND 0x%02x OR 0x%02x XOR 0x%02x\n",


a & b, a | b, a ˆ b);

return 0;
}
Example.8 Logical AND OR XOR operator
The output is :
AND 0x80 OR 0x81 XOR 0x01

19
Scope and Lifetime of the variables
We have declared many variables over the examples. Each variable declared
within the function has some certain scope and lifecycle.
In the above function, the variables a and b are called local variables. This
means that the scope of these variables are within the lifetime of the function. If
the function returns the variables will be destroyed or removed from the memory.
Each program has a determined memory set aside by default by the operating
system. This memory is dvidied into the following partitions.
1. stack segment
2. heap segment
3. data segment
4. text segment

20
Below is the diagrammatic representation.
|--------------------|
| |
| |
|--------------------|
| |
| Stack segment | -> Stack grows downwards in memory
| |
|--------------------|
| | |
| V |
| |
| |
| ^ |
| | |
|--------------------|
| Heap Segment |
| | -> Heap grows upwards in memory
|--------------------|
| Uninitialized data |
|--------------------|
| Initialized data |
|--------------------|
| text |
|--------------------|

21
1. Stack segment
In linux stacks are of 8 kbytes in size by default. But it varies on operating
system, sometimes application wise. In RTOS based systems, it is configured at
the linker script.
Stack grow downwards in memory. Local variables and function arguments are
part of the stack.
Everytime a function is called, the function arguments kept in the stack and a call
is made and grows down. After the return, the stack is freed up automatically.
In general, the stack can exhaust quickly if allocation reaches maximum value,
resulting in stack overflow. In some operating systems, it will result in entire
system halt (such as in RTOSes running on a Microcontroller) or a program halt
while the rest of the system execute normally.
In either cases, the result is a program abort. To avoid this, we must carefully
use the stack memory. Sometimes data section can be of use if there is no heap
section.
Lifetime of stack variables is only local to the function. This is in general one of
the right approaches to programming by confining the scope to a function or to
a group.
Below example shows the use of static variables confined to a function
int F()
{
int a = 3;

return a * 2;
}
Example.9 Local variable
Here the variable a is a local variable and is part of the stack.
Below is another example of scoped variable setting with stack.
int F()
{
// variable a is scoped between { and }
{
int a = 3;

a *= 3;
}

return a; // returns error that a is undeclared


}

22
Example.10 Scoped declaration statement
GCC provides few compiler options to check the stack usage. See GCC compiler
options.
2. Heap segment
This is the space where the dynamic memory can be allocated. This is explicitly
allocated by calls to allocation functions in programming languages. C, C++
exposes allocation routines such as the following:
1. malloc
2. calloc
3. realloc
4. free
5. new
6. delete
The last two are in C++ standard library but it supports all of them. C library
supports the first 4.
Once allocated by any of these calls, only way to free is by a call to free routines.
There is no automatic freeing. Without freeing any heap memory, the system
will soon run out of memory space if run for long duration.
Not freeing the heap results in memory leaks. When a program stops running,
in general operating system will cleanup the memory allocated to it and returns
back the memory back to the free pool. However, if the program has been
running for a long time and never frees memory, it results in increase of memory
usage and most probably leads to memory pressure / exhaustion.
Generally operating systems responds to the memory exhaustion by triggering
spontaneous kills of programs that used high memory to restore the memory
usage. In linux this is called out of memory killer in the kernel space.
The Lifetime of the heap section is entire program but the scope is limited to
either global, local, or file scope. But the allocated memory is always part of the
heap section.
3. Data segment
Data segment is initialized and uninitialized segments.
Here are few cases the variables are part of data segment.
• Variables that are accessible by all functions globally from all the source
files.
• Variables that are accessible by all the functions local to the source file.
Generally we prefix them with static to keep the scope bound to a single
file.
• Variables that are prefixed with static and are accessible by a function
locally.

23
The lifetime of these variables is the entire program.
The pointers can also be global.
Though the usage of globals and static globals result in easier programming but
it results in a large mess of variables and their usage. This further results in
confusion in case the program need to be updated. In cases, the global or static
gllobals are required, it has to be considered and an alternative approaches to
the use of globals must be considered.
On microcontrollers, generally stack is limited to avoid the stack smashing that
occur if there are many variables and lots of function calls. To avoid this some
of the large and common variables are kept in the data segment.
4. Text segment
The text segment contain the program code , instructions. It is generally below
the stack and heap to avoid overwriting. It is also readonly but most of the
time an active attack could overwrite the text segment and execute an unknown
program (also viruses, worms).
For example,
char *msg = "hello";
The msg is allocated in the text segment. Changing any of the content at this
location results in a fault.
#include <stdio.h>

int main()
{
char *ptr = "hello";

*ptr = 'i';

printf("%s\n", ptr);

return 0;
}
Example.11 Text section
The above program when ran, results in segmentation fault and program stops.
In general, the language provides some mechanisms to detect the modifications
at compile time. For example when const used as the prefix to char *ptr, the
compilation will fail noting that the read-only location is being modified.
#include <stdio.h>

int main()

24
{
const char *ptr = "hello";

*ptr = 'i';

printf("%s\n", ptr);

return 0;
}
Example.12 Text section with const
c/read_only.c: In function ‘main’:
c/read_only.c:7:10: error: assignment of read-only location ‘*ptr’
7 | *ptr = 'i';
| ˆ

25
Control statements

26
1. if else statement
The if statement provides a method of controlling the execution path of a
program based on the data.
The if statement holds the condition and is evaluated for true or false. If the
condition is true, then the statements in the if are executed, Otherwise the
statements in else are executed.
The else statement in general is followed by the if statement and never alone.
The else statement does not contain any condition test like the if.
#include <stdio.h>

int main()
{
char p = 'd';

if (p == 'd') {
printf("p '%c' is %c\n", p, 'd');
} else {
printf("p '%c' is not 'd'\n", p);
}

return 0;
}
Example.13 if else condition
A conditional check if (p) is also a valid check , but this check only check if
the value is non zero. If the value is 0, then the statements under if (p) never
executes. Understand that in C, the conditional checks depend on the value in
the variable.
Generally the else statement does not have to follow the if statement. But it
is a good practise to have an else statement if needed.
Let us consider the below program. Download it here.
#include <stdio.h>

int main()
{
char p = 'd';

if (p = 'f') {
printf("p '%c' is %c\n", p, 'd');
}

27
return 0;
}
Example.14 if condition with assignment
Notice the mistake?
The if conditional have the assignment operator than the equality test. This
results in plain assignment which executes and so the printf inside the if will
execute and prints the following:
p 'f' is d
We generally tend to avoid assignment statements inside the if to prevent getting
into such kind of issues.
The below test is also right.
if (p) {
printf("p is '%c'\n", p);
} else {
printf("p is not '%c'\n", p);
}
Example.15 validation of p
The condition that is checked in the if statement is either 0 or non-zero.
So the above condition results in printing p is d.

28
2. if else-if statement
The if else-if statement also called if else ladder that is used to construct
a series of if else if conditions.
Below is an example of the if else-if ladder.
#include <stdio.h>

int main()
{
int number = 50;

if (number < 50) {


printf("number is less than 50\n");
} else if (number > 50) {
printf("number is greater than 50\n");
} else if (number == 50) {
printf("number is equal to 50\n");
} else {
printf("number is [%d] unknown\n", number);
}

return 0;
}
Example.16 if else ladder
In general if else-if ladders are not used in many large scale applications
unless there are ranges involved. That is why the above example shows the use
of if else-if with the ranges.
In most of the cases if else-if never ends with an else case, only few pro-
gramming situations else case might be required.
For any direct comparision (==) the switch statement is used.
For example, the && and || can be used within the if conditional.
Below is an example,
#include <stdio.h>

int main()
{
int a = 0x80;
int b = 0x0;

if (a && b) {
printf("a and b are non zero\n");
} else if (a || b) {

29
printf("a or b are non zero\n");
}

return 0;
}
Example.17 if else ladder with comparison operations && ||

30
3. Switch statement
The switch statement example is as shown below.
#include <stdio.h>

int main()
{
int n = 50;

switch (n) {
case 10:
printf("number is 10\n");
break;
case 20:
printf("number is 20\n");
break;
case 50:
printf("number is 50\n");
break;
default:
printf("number [%d] is unknown\n", n);
break;
}
return 0;
}
Example.18 switch statement
As you can see, the switch has a series of case statements and a default
statement. Each of the case statement ends with a break statement if necessary.
If there is no break statement then the statements fall through. Below example
shows the descripton.
#include <stdio.h>

int main()
{
int n = 10;

switch (n) {
case 10:
case 20:
printf("n is %d\n", n);
break;
}

return 0;

31
}
Example.19 switch with case casdading
The output is :
n is 10
In some cases the fallthroughs are needed to have execute a series of statements
for more than one case types.
The switch statement can also be used with characters. However, it cannot
be used with strings, floating point and double variables. Strings are discussed
more below. Below program is an example usage of the switch statement with
character.
#include <stdio.h>

int main()
{
char p = 't';

switch (p) {
case 't':
printf("value is t\n");
break;
default:
printf("value is %c\n", p);
break;
}
}
Example.20 switch case with char type
In general, the default statement can be omitted. The switch statement must
atleast have one case.

32
4. Trigraph ?: sequence
The ?: is called a trigraph sequence. Here’s how it can be used.
#include <stdio.h>

int main()
{
int a = 10;
int b = 5;
int res;

res = (a > b) ? a : b;

printf("res %d\n", res);

return 0;
}
Example.21 Trigraph sequence
Trigraphs are similar to the if else cases the true case is right after the ? and
the false case is right after the :.
They are mostly useful when writing simple test on a variable instead of the
general if else conditionals.

33
Loops
1. While loop
The while loop allows to loop over a certain condition until it fails. An example
of the while is as follows.
while (condition) {
// statements
}
Example.22 While condition
An example use of while loop is as follows.
#include <stdio.h>

int main()
{
int i = 0;

while (i < 10) {


printf("i %d\n", i);
i ++;
}

return 0;
}
Example.23 While loop example
In the above program the loop repeats until i reaches 10. Upon reaching 10, the
while condition fails breaking the loop.
The break statement can be used in the while loop as well.
int main()
{
int i = 0;

while (1) {
if (i >= 10) {
break;
}
printf("%d\n", i);
i ++;
}

return 0;
}

34
Example.24 while loop and break statement
Above program shows the use of while (1). Generally this means that the
condition in the while loop is never false. It is an infinite loop.
Generally infinite loops are not preferable in programming without any condi-
tional checks in the while statement.
The infinite loops generally do nothing but increase in CPU load on the process
the program runs and consumes the CPU cycles unnecessarily. However, some
programs written for the operating systems do need to run infinitely (such as
graphics, display, editors etc). To do this, operating systems employ certain
event based mechanisms supported by the hardware. This ensures that the
program executes only based on certain events.

35
2. For loop
The for loop is similar to the while loop. The syntax is as follows,
for (initialization; condition; increment / decrement operation)
Below is an example of the use of for loop.
#include <stdio.h>

int main()
{
int i;

for (i = 0; i < 10; i ++) {


printf("i %d\n", i);
}

return 0;
}
Example.25 For loop
the i = 0 statement in for executes only once. The i < 10 statement exe-
cutes everytime the loop repeats. The i ++ statement executes everytime the
statements in the for loop executes.
Another way to do is the following:
#include <stdio.h>

int main()
{
int i = 0;

for (;i < 10; i ++) {


printf("i %d\n", i);
}

return 0;
}
Example.26 For iteration
The initializer statement can be left aside.
The above while (1) can be re-written with for as follows.
#include <stdio.h>

int main()
{

36
int i = 0;

for (;;) {
if (i >= 10) {
break;
}
printf("i %d\n", i);
i ++;
}

return 0;
}
Example.27 Infinite for loop
The for(;;) is also an infinite for loop. As mentioned, the infinite loops must
be used with caution.

37
3. do while loop
The do..while loop is similar to the while. Below is an example.
#include <stdio.h>

int main()
{
int i = 0;

do {
printf("Hello World\n");
} while (i != 0);

return 0;
}
Example.28 do..while loop
Once run, it prints Hello World. This means that the statements execute and
the checks happen later.

38
Goto statement
The statement goto is similar to a jump instruction in assembly. The above
loop can be rewritten with goto as follows.
#include <stdio.h>

int main()
{
int i = 0;

begin:
if (i < 10) {
printf("i %d\n", i);
i ++;
goto begin;
}

return 0;
}
Example.29 goto loop
We do not use goto in most of the programs for the following reasons:
1. Readability reduces with many gotos with in a function or within a C file.
2. Incorrectly written gotos can cause loops in program.
Gotos are not bad when used correctly in a program. For example in usecases
when certain conditions fail during a program initialization, the deinitialization
sequence must do the opposite. In such cases a jump required on the failure
case.
Here’s a pseudo code example,
int init_1()
{
...
return 0;
}

int init_2()
{
...
return 0;
}

void deinit_1()
{

39
...
}

int init_main()
{
int ret;

ret = init_1();
if (ret != 0) {
return -1;
}

ret = init_2();
if (ret != 0) {
goto deinit;
}

deinit:
deinit_1();
return -1;
}
Example.30 goto usecase
More about functions in the functions section.
In areas such as Automotive and Aerospace software application, goto statement
is seldom used. It is treated as a bad practise. So avoiding this is a good step
when writing software for such applications.

40
Arrays

41
1. One Dimensional Arrays
One dimensional array are the base type in arrays.
An array of integers is defined as,
int a[10];
Above statement defines an array a of 10 integers. Each element in the array is
an element of type integer.
Array indexes start from 0. Each item in the array is indexed with regular
numbers ranging from 0 to 9.
Maximum elements in the above array are 10 but the last index of the 10th
element is 9, not 10. Accessing the array beyond its maximum range is also
called out of bounds access. Out of bounds accesses are major security problem
as the element is accessing an address beyond the allocated range.
Below program assigns the elements in the array.
#include <stdio.h>

int main()
{
int a[10];
int i = 0;

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


a[i] = i;
}

printf("array elements:\n");
for (i = 0; i < sizeof(a) / sizeof(a[0]); i ++) {
printf("\ta[%d] = %d\n", i, a[i]);
}
printf("\n");
}
Example.31 array iteration
The size of an array is calculated the same way.
#include <stdio.h>

int main()
{
int a[10];

printf("size of array %lu\n", sizeof(a));

42
return 0;
}
Example.32 sizeof array
With the sizeof, one can also find out the number of elements in the array as
follows.
#include <stdio.h>

int main()
{
int a[10];

printf("number of elements %d\n", sizeof(a) / sizeof(a[0]));

return 0;
}
Example.33 number of elements in an array
Initializing array elements
The below statement generally initializes the array.
int a[10] = {0};
However, this initializes the first element to 0. Since only one element is initialized
then by default all elements are initialized to 0.
So if we have initialized it,
int a[10] = {10};
the first element of the array is initialized to 10 and the rest of the elements are
initialized as 0s. Below is one example:
#include <stdio.h>

int main()
{
int a[10] = {10};
int i;

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


printf("a[%d] = %d\n", i, a[i]);
}

return 0;
}
Example.34 Initializing array

43
This example prints the first element as 10 and rest as 0.
General way sometimes tend to be the use of memset which is discussed in below
sections. But the below example shows how to initialize an array.
int a[10];

memset(a, 0, sizeof(a));
Sets all the elements of the array a to 0.
Another way to set array elements is as follows:
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int i;

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


printf("a[%d] = %d\n", i, a[i]);
}
Example.35 iterating array
But this means that all array elements must be initialized which is impractical
for a large set of arrays.
copying array elements
Copying one array to another is simple as iterating over each element and copying
one element to another.
Below is one example,
#include <stdio.h>

int main()
{
int a1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int a2[10];
int i;

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


a2[i] = a1[i];
}

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


printf("a1[%d] = %d a2[%d] = %d\n", i, a1[i], i, a2[i]);
}

return 0;
}
Example.36 copying array

44
Another way is to use memcpy. This is explained in Strings section.

45
2. Two Dimensional Arrays
Two dimensional arrays are represented as follows.
int a[10][10];
denotes a two dimensional array.
To access the elements one must iterate both indexes with two loops. Or we call
them here the nested loops.
#include <stdio.h>

int main()
{
int a[10][10];
int i;
int j;

for (i = 0; i < 10; i ++) {


for (j = 0; j < 10; j ++) {
a[i][j] = j + i;
}
}

for (i = 0; i < 10; i ++) {


for (j = 0; j < 10; j ++) {
printf("a[%d][%d] = %d\n", i, j, a[i][j]);
}
}

return 0;
}
Example.37 Setting two dimensional array

46
3. Three Dimensional Arrays
A 3 dimensional array is a group of 2-D arrays. Thet are denoted as follows,
int array[10][20][40];
The below example shows how a 3-D array is used.
#include <stdio.h>

int main()
{
int array[10][20][40];
int i;
int j;
int k;

for (i = 0; i < 10; i ++) {


for (j = 0; j < 20; j ++) {
for (k = 0; k < 40; k ++) {
array[i][j][k] = 10 + i;
}
}
}

for (i = 0; i < 10; i ++) {


for (j = 0; j < 20; j ++) {
for (k = 0; k < 40; k ++) {
printf("array[%d][%d][%d] = %d\n", i, j, k, a[i][j][k]);
}
}
}

printf("\n");
return 0;
}
Example.38 Setting Three Dimensional Array
The usecases of arrays range in many places, such as grouping a set of characters
together, or grouping of similar types together to represent a hardware device or
group of same hardware devices and so on. Also arrays play a big role in search
and sort techniques.

47
Macros
Macros are compile time constants and do not allocate any space in runtime.
This means that the preprocessor replaces the sections where Macros are used,
into their corresponding values.
In the above example we used #include <stdio.h>, where #include is a direc-
tive. This informs the compiler to replace this statement with the header file
stdio.h.
#define macro
The preprocessor stage, replaces the macros with the actual values present in
the macro definition.
The macro #define defines macro constants. An example is as follows.
#define TWO 2
Macro statements are used as substitutes for constants or some common opera-
tions.
For example using,
#define PI 3.1413

double circumferance(double radius)


{
return 2 * PI * radius;
}
is more meaningful that using a constant value.
#define ADD(_a, _b) ((_a) + (_b))
is an operation. We discuss about function like macros in the below sections.
Below is an example,
#define ARRAY_SIZE 10

int main()
{
int r[ARRAY_SIZE];
}
Example.39: macro conversion
Running gcc -E on the file gives,
# 0 "c/def.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4

48
# 0 "<command-line>" 2
# 1 "c/def.c"

int main()
{
int r[10];
}
Example.40: gcc -E output
Macro replaced with an actual value after the pre-processor stage.
#undef macro
The #undef macro undefines a macro. For example,

#define PI 3.1413
#undef PI

double circumferance(double radius)


{
return 2 * PI * radius;
}
results in compiler error as the preprocessor removes PI.
#ifdef macro
The #ifdef macro checks if a certain macro is defined and if so preprocessor
enables the portion of code that is between the #ifdef and #endif. The #ifdef
is always followed by #endif or #else statement.
For example,
int a = 10;
#ifdef CONFIG_MACRO
a = 5;
#else
a = 6;
#endif
Will set a to 5 if CONFIG_MACRO is defined or 6 otherwise.
Below example shows how to define the macro.
#include <stdio.h>

#define CONFIG_MACRO

int main()

49
{
int a = 10;

#ifdef CONFIG_MACRO
a = 5;
#else
a = 6;
#endif

printf("%d\n", a);
}
Example.41 Macro definitions
Defining a simple #define CONFIG_MACRO is enough to enable the statements
under the #ifdef. In general these can be passed as command line arguments
instead to the compiler. For example,
gcc -DCONFIG_MACRO macro.c
The argument -D to the gcc accepts the macros and many number of -Ds can
be given as arguments.
The #ifndef macro is a negation of #ifdef where if the particular macro
variable is not defined, preprocessor will enable that portion of the code.

50
Functions
Function in a C program is a group of instructions. Functions allow us to break
a large program into pieces of understandable segments. Each segment with
some defined business logic and logical implementation.
A function has none, one or more input arguments and returns or do not return
anything. A prototype is as follows,
void function(void)
or more generally,
return-type function-name(arguments, ..).
For example,
#include <stdio.h>

void print_hello()
{
printf("Hello World\n");
}

int main()
{
print_hello();
}
The above example calls a function called print_hello to print the “Hello
World” on screen.
The call to the function is simply by writing its name followed by the parantheses
with none, one or more arguments and followed by a semicolon.
This function does not accept any argument and does not return anything.
The statements,
void print_hello()
{
printf("Hello World\n");
}
comprise the body of the function. Its also referred as function definition. A
program typically contains more than one function.
main() is also a function which is the starting point of a program.
If the main() is not defined, then the compilation results in undefined error. In
general the linker expects main() to be defined as it is expected by the sequence
before it calling main(). Who calls main()? For this, the short answer is the
call is defined somewhere in the libc.

51
A function can only have one signature in C (A function can have many signatures
in C++). main() prototypes are many unlike many other functions. Some of
the most used prototypes are as follows.
int main(void)
int main(int argc, char **argv)
void main(void) -> however this is seldom used when writing software. main()
must return. This is to let the executing shell to know the status of the program
when returned. The shell can use this status to further perform certain operations.
(example is that shell scripts can use the return status of a program).
The second prototype is further described in command line arguments section.
A function can return any data type or none. For example,
int f(void);
The above function returns integer type but accepts no arguments.
A function can take one or more arguments and return none or one type. For
example,
int f(double a, double b);
The above function accepts two variables of type double and returns an integer.
A small example shows the usecase.
#include <stdio.h>

int add(int a, int b)


{
return a + b;
}

int sub(int a, int b)


{
return a - b;
}

int mul(int a, int b)


{
return a * b;
}

int div(int a, int b)


{
return a / b;
}

52
int main()
{
int a = 10;
int b = 5;

printf("add %d\n", add(a, b));


printf("sub %d\n", sub(a, b));
printf("mul %d\n", mul(a, b));
printf("div %d\n", div(a, b));

return 0;
}
The above program creates and calls 4 functions add, sub, mul, and div from
main().
Each function does exactly one job and named as per the job it does to help
reader of the program understands. This is usually the discipline that is followed
when writing software.
Each function returns only integers, but the program does not work correctly
(rounding off errors) when given floating point numbers. Writing such generic
functions are explained more in C++ templates.
When a function call is made, some space is allocate in the stack and the
arguments to the function are kept there. The function return address and stack
pointer are stored. This repeats when another function call is made from that
called function to another function. Soon a function returns, the stack allocated
for the function is then freed up and the return value is returned back to the
caller. Thus if there is function call nesting (ex: 11 function calls in a function
call chain), then it could very well be possible that the stack space allocated for
the program is exhausted and will result in overflow causing a program crash.
On a micro controller device, this results in a firmware crash, which is more
dangerous.
On Linux based systems, the typical stack is 8KB. One can change the stack
size on Linux with ulimit command.
An example of a function call depth is as shown below.
f(1, 2) calls ->
f1(1, 3) calls ->
f2(1, 4) calls ->
f3(1, 5) calls ->
g(2, 2) calls ->
g1(2, 3) calls ->
g2(2, 4) calls ->
g3(2, 5) calls ->
h(3, 1) calls ->

53
h1(3, 2) calls ->
h2(3, 3) calls ->
This above example shows a function call depth of 11. Soon as h2 return the
stack allocated for the h2 will be destroyed and freed up and soon as h1 return,
the stack allocated for it will be freed up and so on till the function f.
It is advised to not perform many calls that create high depth stacks. When
writing software or abstractions, it could be possible to hit a depth of 4 or more.
It is advised not to go beyond a certain level of function call depth to avoid
stack related problems.
Debugging stack related problems is generally very hard. The only approach is
to test if moving a large variable out of stack to data section and observe if the
stack overflow goes away.
If the Operating system support dynamic memory allocation, this can be used
to avoid stack related problems. But, the dynamic memory usage mean that the
user must free the allocated memory.
Dynamic memory is explained in below sections of Pointers.

54
Function returning local data
Functions can return the local data of a variable or a structure. But there are
few cases which needs to be understood when returning certain types of data
from functions.
A function returning pointer to a local variable is dangerous. In other words,
a function returning local address back to the caller, makes caller access an
unknown address that has been deallocated after the function exit. This results
in an undefined behavior and may crash the program.
For example,
char *get()
{
char hello_msg[20];

strcpy(hello_msg, "hello");

return hello_msg;
}
This returns a local variable hello_msg to the caller as a pointer. The hello_msg
is on the stack of get which gets cleaned up soon as the get function returns
causing hello_msg to point to a freed memory. Since this memory does not
belong to the program, the result could be a crash or could execute something
else. Crash generally is the case if the address accessed is out of the allocated
space.
Consider another example of function returning the pointer.
char *get()
{
return "hello";
}
This returns the same message back to the caller, but allocation of hello is
in text section of the program, where the address is still in program’s address
space. So the return here, does not return the address of a local variable but
the address of a text section. This call is correct.
Below is the full example,
#include <stdio.h>
#include <string.h>

char *get_2()
{
char hello_msg[20];

55
strcpy(hello_msg, "hello");

return hello_msg;
}

char *get_1()
{
return "hello";
}

int main()
{
char *ptr = get_1();

printf("get_1 %s\n", ptr);

ptr = get_2();

printf("get_2 %s\n", ptr);

return 0;
}
Return statement and trigraph sequences can be combined together.
For example the following statement is valid,
int is_positive(int a)
{
return (a > 0) ? true : false;
}
There are few tricks we use when we return strings.
1. Pass by reference. - This has been explained below.
2. Define a table of strings and choose to return one of them based on input.
String table can be written as follows.
static const char *books[] = {
"networking essentials",
"programming in C",
};
The table is defined in the read-only section of the program. This is kept within
the file rather in a function. The lifetime of the variable is program lifetime.
We then write the access function the following:
const char *get(unsigned int book_id)
{

56
if (book_id > sizeof(books) / sizeof(books[0])) {
return "unknown";
}

return books[book_id];
}
Below is one example,
#include <stdio.h>

static const char *books[] = {


"networking essentials",
"programming in C",
};

const char *get(unsigned int book_id)


{
if (book_id > sizeof(books) / sizeof(books[0])) {
return "Unknown";
}

return books[book_id];
}

int main()
{
printf("book %s\n", get(1));

return 0;
}
const as function argument
const specifier can be added to the variables when modification of argument
deemed not necessary.
for example,
int f(const char *str)
The argument str here is unmodifiable.
Below is one example.
#include <stdio.h>

int f(const char *str)


{
int i;

57
for (i = 0; str[i] != '\0'; i ++) {
str[i] = 'a'; // results in compiler error of modifying a constant
}

return i;
}

int main()
{
char str[20] = "hello";

f(str);

printf("str: %s\n", str);

return 0;
}
We directly index into the array, but validate the range of the book_id. Any
access to the books pointer with incorrect book_id value could result in out of
bounds access. For example consider that the check is not present, the access to
the books array if given book_id 3, results in an access to an unknown address
space and result in a fault.
Function calls can be made in if, while and for statements too.
if (is_item_fruit(item)) { // valid call

while (remaining_items() > 0) // valid call

for (i = 0; i < remaining_items(); i ++) // valid call


But in general, these calls make debugging harder when debugging with printf.

58
Variadic functions
Variadic functions are the ones that can accept many arguments as input to
them. Many arguments here mean that, the function’s arguments will vary based
on the caller. Sometimes the function will take 3 and the same function will
take 2 or 7 etc. This is why they are called variadic functions.
The header stdarg.h contains the macros and helper functions to make variadic
functions possible.
There are 2 important functions / macros in stdarg.h.
1. va_start
2. va_end
1. va_start
The prototype of va_start looks as follows.
void va_start(va_list ap, last);
last can be any argument that is like an identifier.
2. va_end
The prototype of va_end looks as follows.
void va_end(va_list ap);
Each invocation of va_start must match to va_end.
writing your own printf
vfprintf is similar to the fprintf but it takes variable arguments. The proto-
type is as follows.
int vfprintf(FILE *fp, const char *fmt, va_list ap);
Below is one example,
#include <stdio.h>
#include <stdarg.h>

void log_printf(const char *fmt, ...)


{
va_list ap;

va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}

int main()
{

59
int a = 10;
log_printf("test: test message a=%d\n", a);
}
va_arg
va_arg prototype is as follows.
type va_arg(va_list ap, type);
Below example shows an add function that adds many number of variables.
The prototype is as follows,
int add(int n_args, ...);
The n_args are number of arguments. The rest of the arguments are the
arguments to be added.
The add function iterates over the list of arguments with n_args.
For example,

int i;

va_start(ap, n_args);
for (i = 0; i < n_args; i ++) {
r += va_arg(ap, int); // get the value given the type is `int`
}
Below is one example,
#include <stdio.h>
#include <stdarg.h>

int add(int n_args, ...)


{
int i;
int r = 0;
va_list ap;

va_start(ap, n_args);

for (i = 0; i < n_args; i ++) {


r += va_arg(ap, int);
}

va_end(ap);

return r;
}

60
int main()
{
int r;

r = add(3, 1, 2, 3); // add 3 arguments


printf("r: %d\n", r);

r = add(6, 1, 2, 3, 4, 5, 6); // add 6 arguments


printf("r: %d\n", r);

return 0;
}
The last argument only need to be passed to va_start. If not passed, the
va_start and related functions do not work.
Below is another example,
#include <stdio.h>
#include <stdarg.h>

typedef enum {
INT,
DOUBLE,
STRING,
} type_t;

double add(type_t type, int n_args, ...)


{
va_list ap;
double r = 0;
int i;

va_start(ap, n_args);
for (i = 0; i < n_args; i ++) {
if (type == INT) {
r += va_arg(ap, int);
} else if (type == DOUBLE) {
r += va_arg(ap, double);
}
}

va_end(ap);

return r;
}

61
int main()
{
double r;

r = add(INT, 3, 1, 2, 3);
printf("r: %f\n", r);

r = add(DOUBLE, 6, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6);


printf("r: %f\n", r);

return 0;
}
Below is another example of variable argument strcat. The function strcat is
described in Strings section.
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

int strcat_self(char *dst, const char *src)


{
int i = 0;
int j = 0;

for (i = 0; dst[i] != '\0'; i ++) { }


for (j = 0; src[j] != '\0'; i ++, j ++) {
dst[i] = src[j];
}
dst[i] = '\0';
}

int strcat_vaarg(char *final_str, int n_args, ...)


{
va_list ap;
int i;

va_start(ap, n_args);
for (i = 0; i < n_args; i ++) {
char *str = va_arg(ap, char *);
strcat_self(final_str, str);
}

printf("final %s\n", final_str);


return 0;
}

62
int main()
{
char final_str[400] = {};
int r;

final_str[0] = '\0';
r = strcat_vaarg(final_str, 3, "fruits", "vitamins", "health");
memset(final_str, 0, sizeof(final_str));

r = strcat_vaarg(final_str, 6, "snacks", "sugars", "fats", "chips", "crispy", "bad");


}

63
Function like macros
Since we read about macros above, we can rewrite the above functions as follows.
#define add(_a, _b) ((_a) + (_b))
#define sub(_a, _b) ((_a) - (_b))
#define mul(_a, _b) ((_a) * (_b))
#define div(_a, _b) ((_a) / (_b))

int main()
{
int a = 10;
int b = 5;

printf("add %d\n", add(a, b));


printf("sub %d\n", sub(a, b));
printf("mul %d\n", mul(a, b));
printf("div %d\n", div(a, b));

return 0;
}

inline functions
The inline functions are little functions enough to be replaced by the compiler
in the calling program.
The inline specifier need to be added before the function.
Below is one example,
inline int add(int a, int b)
{
return a + b;
}
Though the inline word informs the compiler to inline the function into the
caller, the actual inlining will happen if compiler deems the function to be an
inline.
So inline is just a hint to the compiler. In most of the cases, the optimization
benefits of inlining the functions are very little.

64
Strings
The below statement is a base char type.
char d;
String is an array of characters ending with \0. For example, the below statement
declares a base string type.
char d[20];
declares a string of 19 elements with the last element allocated to the \0. Strings
must end with \0 always. Without it, the string manipulation functions in the
C library would not work or any other operations that depend on the use of \0.
For an example string “Hello”, the character setting in the array would look as
follows.
'H', 'e', 'l', 'l', 'o', '\0'
The termination \0 is a must to identify a string literal by any string manipulation
functions to find out how far the string is.

65
String manipulation operations
The header file string.h contains the functions that help to manipulate the
string data.

String manipulation functions The standard library provides below or


more of the functions to manipulate the strings.

S.No Function Name Description


1 strlen Calculate the length of string
2 strcpy Copy one string to another
3 strcat Concatenate string to another
4 strcmp Compare two strings
5 strchr Find character in the string
6 strstr Find substring in a string (GLIBC, non-C standard)
7 memcmp Compare two arrays / strings
8 memcpy Copy one array / string to another
9 strdup Duplicate strings
10 memset Set memory to a value

66
1. strlen
The library function strlen is used to get the length of a string.
The strlen prototype is as follows:
int strlen(const char *str);
Below is an example,
#include <stdio.h>

int main()
{
char str[] = "hello world";

printf("strlen = %d\n", strlen(str));

return 0;
}
The strlen function counts all the characters in the string excluding the \0
marker.
It can be implemented as follows.
int string_length(const char *str)
{
int i = 0;

for (i = 0; str[i] != '\0'; i ++);

return i;
}
The above program iterates over each character in the string str until the
character is \0. So the content of the for loop does nothing. So we end it with a
semicolon. Sometimes open and closed braces ({ and }) can be used as well.
Note that the behavior is that the last character should always be null terminated.
If there are characters present beyond the null terminating character then they
will be ignored.
If there is no null character in the string, then the strlen function will keep
on reading and may even read past the allocated buffer in some cases. This
sometimes causes crashes if lucky and sometimes executes other code if not lucky
leading to exploits.
So caution must be taken when creating and using the strings. Always null
terminate the strings.

67
Note that the strlen and string_length functions never check if the input is
a NULL. The callers must take care of the pointer validity before calling strlen.
2. strcpy
The strcpy function is used to copy the source string into the destination.
Its prototype is as follows.
char *strcpy(char *dst, const char *str);
The example of strcpy is as follows.
#include <stdio.h>
#include <string.h>

int main()
{
char *str1 = "Witcher";
char str2[20];

strcpy(str2, str1);

printf("str1 [%s] str2 [%s]\n", str1, str2);

return 0;
}
strcpy can be implemented as follows.
int string_copy(char *dst, unsigned int dst_len, const char *src)
{
uint32_t i = 0;
uint32_t len = 0;

for (i = 0; src[i] != '\0'; i ++) {


if (i < dst_len) {
dst[i] = src[i];
} else {
break;
}
}

dst[i - 1] = '\0';

return 0;
}
3. strcat
String concatenation concatenates two strings together.

68
The prototype is as follows.
char *strcat(char *dst, const char *src);
Below is an example,
#include <stdio.h>
#include <string.h>

int main()
{
char *str = "hello";
char *ptr = "mangoes";
char dst[20];

strcpy(dst, str);
strcat(dst, " ");
strcat(dst, ptr);

printf("dst %s\n", dst);

return 0;
}
Below is an example of strcat. Since strcat appends the string with a new
string, one need to iterate to the end of the previous string.
#include <stdio.h>

int string_cat(char *dst, const char *src)


{
int i = 0;
int j = 0;

while (dst[i] != '\0') {


i ++;
}

while (src[j] != '\0') {


dst[i] = src[j];
i ++;
j ++;
}
dst[i] = '\0';

return j;
}

69
int main()
{
char *src = "test";
char dst[30] = "dest";

string_cat(dst, src);

printf("dst %s\n", dst);

return 0;
}

4. strcmp
Compare a string with another string.
int strcmp(const char *src, const char *dst);
Returns 0 if both strings are equal. Returns non zero (difference between the
characters at the failed index) when both strings are not equal.
Below is an example of strcmp.
#include <stdio.h>
#include <string.h>

int main()
{
char *str = "hello";
char *str_1 = "hello";
char *str_2 = "Hello";
int res_1, res_2;

res_1 = strcmp(str, str_1);


res_2 = strcmp(str, str_2);

printf("res_1 %d res_2 %d\n", res_1, res_2);

return 0;
}
Below is the full example of the implementation of strcmp.

int string_len(const char *str)


{
int i = 0;

while (str[i] != '\0') {

70
i ++;
}

return i;
}

int string_cmp(const char *str1, const char *str2)


{
int i = 0;

if (string_len(str1) != string_len(str2)) {
return -1;
}

while (str1[i] != '\0') {


if (str1[i] != str2[i]) {
return str1[i] - str2[i];
}
}

return 0;
}
5. strchr
#include <stdio.h>
#include <string.h>

int main()
{
char *str = "english movies";
char *pos;

pos = strchr(str, 'm');


if (pos) {
printf("pos '%s'\n", pos);
}

return 0;
}
6. memcmp
The function memcmp compares data in the two memory locations given the size.
#include <stdio.h>
#include <stdint.h>
#include <string.h>

71
int main()
{
uint8_t buf1[] = {0x01, 0x02, 0x03, 0x04, 0x01, 0x04, 0x4, 0x3};
uint8_t buf2[] = {0x01, 0x02, 0x03, 0x04, 0x01, 0x04, 0x4, 0x3};

printf("compare %d\n", (memcmp(buf1, buf2, 8) == 0));

return 0;
}
A simpler memcmp implementation would involve comparing byte by byte to
length bytes. Below is an example:
int memcmp(const void *a, const void *b, int len)
{
int ret = 0;
int i;

const uint8_t *val_a = a;


const uint8_t *val_b = b;

if (!a || !b) {
return -1;
}

for (i = 0; i < len; i ++) {


if (val_a[i] != val_b[i]) {
ret = -1;
break;
}
}

return 0;
}
Another way to implement is to compare 4 or 8 bytes at a time. This can be
done based on the architecture. Below is an example:
int memcmp(const void *a, const void *b, int len)
{
int ret = 0;
int i;
int rem = len % 8;
int check_len = len / 8;

const uint64_t *val_a = mem_a;


const uint64_t *val_b = mem_b;

72
const uint8_t *ptr_a = mem_a;
const uint8_t *ptr_b = mem_b;

for (i = 0; i < check_len; i ++) {


if (val_a[i] != val_b[i]) {
ret = -1;
break;
}
}
if (ret != -1) {
if (rem != 0) {
for (i = len - rem; i < len; i ++) {
if (ptr_a[i] != ptr_b[i]) {
ret = -1;
break;
}
}
}
}

return ret;
}
Here’s the pointers taken are 8 byte and the direct comparison between 8 bytes
is performed.
The input is typecasted to the 8 byte unsigned integer and the direct value
comparisons are made 8 bytes at a time instead of 1 byte at a time.
Since the length may not always be aligned to 8 bytes, the remainder bytes must
be compared after the first comparison.
For example if the length is 15, then the last 7 bytes must be compared byte
wise.
The second example of memcmp generally results in higher performance on archi-
tecture where the 8 byte comparison instructions are available.
Generally these micro optimizations does not really have a greater benefit if
they are not operated on a large sets of data such as comparing a buffer of over
1Megabyte or so.
7. memcpy
#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main()

73
{
uint8_t buf1[] = {0x01, 0x02, 0x04, 0x02, 0x03, 0x05, 0x03, 0x03};
uint8_t buf2[8];
int i;

memcpy(buf2, buf1, sizeof(buf1));


for (i = 0; i < sizeof(buf1); i ++) {
printf("%02x\n", buf2[i]);
}

return 0;
}
8. strdup
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
char *ptr = "hello";
char *ptr1;

ptr1 = strdup(ptr);

printf("ptr %s ptr1 %s\n", ptr, ptr1);

free(ptr1);

return 0;
}
Below is one of the implementation of strdup.
#include <stdio.h>
#include <stdlib.h>

int len(const char *str)


{
int i;

for (i = 0; str[i] != '\0'; i ++) { }

return i;
}

char *strdup_1(const char *str)

74
{
char *ptr;
int i;
int length = len(str);

// 1 for the \0
ptr = calloc(1, length + 1);

for (i = 0; i < length; i ++) {


ptr[i] = str[i];
}
ptr[i] = '\0';

return ptr;
}

int main()
{
char *ptr = "hello";
char *ptr1;

ptr1 = strdup_1(ptr);

printf("ptr %s ptr1 %s\n", ptr, ptr1);

free(ptr1);

return 0;
}
9. memset
Set the memory contents of the given buffer with the given number.
The prototype of memset looks as follows.
void *memset(void *ptr, int number, size_t n_bytes);
Below is one example,
#include <stdio.h>
#include <string.h>
#include <stdint.h>

int main()
{
uint8_t a[10];
int i;

75
memset(a, 0, sizeof(a));

printf("set 0s:\n");
for (i = 0; i < sizeof(a) / sizeof(a[0]); i ++) {
printf("a[%d] = %d\n", i, a[i]);
}

memset(a, 1, sizeof(a));

printf("set 1s:\n");
for (i = 0; i < sizeof(a) / sizeof(a[0]); i ++) {
printf("a[%d] = %d\n", i, a[i]);
}

return 0;
}

**String to integer / double conversion**

Below are few functions that convert string to other types. Below are the following.

1. `atoi`
2. `strtol`
3. `strtod`
4. `strtoul`

**1. atoi**

The standard library function `atoi` converts a given input string to integer. It is declare

Below is one example:

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

int main()
{
char *str = "4";

printf("%d\n", atoi(str));
}
Lets see another example:
#include <stdio.h>

76
#include <stdlib.h>

int main()
{
char *str = "4a";

printf("%d\n", atoi(str));
}
This results in value 0, in this case the value 0 is still legit if its taken as input.
Since the input is not known. This can result in ambiguity if the result is right
or wrong.
So the preference is generally not to use atoi when writing software.
There is another way to convert the string to integer.
Below is an example. Download it here.
#include <stdio.h>

int main()
{
char *str_int = "1343";
int intval;

sscanf(str_int, "%d", &intval);


printf("%d\n", intval);

return 0;
}
We are using sscanf to read the input from the buffer into an integer.
Lets consider an invalid string input in below code snippet.
Download it here.
#include <stdio.h>

int main()
{
char *str = "123a";
int intval;
int ret;

ret = sscanf(str, "%d", &intval);


if (ret != 1) {
printf("incorrect integer\n");
} else {

77
printf("val %d\n", intval);
}
}
This results in ret being 123. However, results in no error and the integer is
still read.
2. strtol
The standard library function strtol converts a given input string to integer.
It is delcared in stdlib.h.
The function prototype is as follows:
long strtol(const char *in, char **err, int dec_or_hex);
In the above function the err argument describes if the input is incorrect. This
is checked to find out if the returned converted long value is legit.
• The argument dec_or_hex is basically 10 if the input in is decimal or
• 16 if the input is hexadecimal in the format 0x.
Below is one example:
#include <stdio.h>
#include <stdlib.h>

int main()
{
char *number = "102";
char *err = NULL;
long num_long;

num_long = strtol(number, &err, 10);


if (err && (*err != '\0')) {
printf("failed to parse number\n");
return -1;
}
printf("num_long %d\n", num_long);

return 0;
}
3. strtod
Converts string to double.
The function prototype is as follows:
double strtod(const char *ptr, char **end_ptr);
Below is the example:

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

int main()
{
char *str = "3.3";
char *err = NULL;
double val;

val = strtod(str, &err);


if (err && (err[0] != '\0')) {
return -1;
}

printf("val %f\n", val);

return 0;
}
4. strtoul
Converts string to unsigned long.
Below is the prototype.
unsigned long strtoul(const char *nptr, char **err_ptr, int base);
Below is one example:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main()
{
char *str = "4294967295";
uint32_t val;
char *err = NULL;

val = strtoul(str, &err, 10);


if (err && (err[0] != '\0')) {
return -1;
}

printf("val %u\n", val);

return 0;
}

79
Pointers
Strings can also be initialized with a pointer.
The below statement is a string that is allocated at compile time and the str is
a pointer to the beginning of the string “Hello”.
char *str = "Hello";
A pointer of any type is possible.
int *p;
declares an integer pointer.
int val = 4;
int *v = &val;
declares an integer val and a pointer v holding the address of the variable val.
The & denotes the address when placed before the variable.
Pointers can be printed with %p format specifier.
#include <stdio.h>

int main()
{
int val = 4;
int *v = &val;

printf("%d %p\n", val, v);


}
A size of a pointer can be evaluated as following.
int *v;
int size = sizeof(v);
On a 64-bit machine, the size results in 8 bytes.

Pass by value and Pass by reference in functions


Consider the below example,
void add(int a, int b, int r)
{
r = a+ b;
}

int main()
{
int a = 3;
int b = 3;

80
int r = 0;

add(a, b, r);

printf("r %d\n", r);


}
Here the function add takes a, b and r as inputs. The add function perform the
addition operation and writes the result in r.
Once the function executes and returns, the value of r is still 0. This is because
the variable r when passed is local to the function add. So the result value of r
in function add is not passed back.
One way to pass back the value is to return it. For example,
int add(int a, int b)
{
return a + b;
}
And then capture the return value in the caller.
This method of passing arguments is generally called as Pass by value. In this
approach, the value of the passed arguments do not change in the caller.
There is another approach to do this by using pointers. Refer to the pointers
section on using pointers.
void add(int a, int b, int *r)
{
*r = a + b;
}
This method is called as Pass by Reference. The r variable above is passed as a
pointer. So in the caller we need to pass the address of the variable.
...
add(a, b, &r);
...
Here we passed the address of the variable r so the actual address that the add
function is writing is in the original address of r.
This is particularly useful when functions want to change some information
about the variables that they take as inputs instead of returning.
The void pointer
The void pointer is a generic pointer that can be assigned as an address to any
structure, pointer or a variable. Below is one example:

81
int a[10];
void *p;

p = &a[0];
The void pointer cannot be dereferenced because dereferencing involve deducing
the type it points, since its void the compiler wouldn’t know which type it has to
decode. So a typecast is required or in some cases assignment back to its type.
To typecast back to the type generally, the typecast need to be used.
void *p;
int *a;

a = (int *)p;
printf("val %d\n", *a);
Typecasting can be used for the structure pointer as well.
struct S {
int p;
};

struct S s = {
.p = 3,
};

void *p = &s;

struct S *r = (struct S *)p; // typecast back to struct S


The below implementation of program results in compiler error because of the
de-reference of void pointer.
#include <stdio.h>

int main()
{
int p = 3;
void *a;

a = &p;

printf("%d\n", *a); // compiler error.. de-referencing void *

return 0;
}
Another way to do is the following.

82
#include <stdio.h>

int main()
{
int p = 3;
void *a;

a = &p;

printf("%d\n", *(int *)a); // typecasting implictly and then de-referencing the pointer

return 0;
}
Pointers and Arrays
A Pointer to an array can be simply assigned as follows.
int a[10];
int *p;

p = a;
or p = &a[0].
The pointer p assigned as the pointer to the first element of the array.
#include <stdio.h>

int main()
{
int a[10];
int *p;
int i;

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


a[i] = i;
}

p = a;

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


printf("a[%d] = %d\n", i, p[i]);
}
printf("\n");

return 0;
}

83
Below is another example of accessing array elements with a pointer. The
elements of array are updated with the pointer.
#include <stdio.h>

int main()
{
int a[10];
int i;
int *p;

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


p = &a[i];
*p = i;
}

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


printf("%d\n", a[i]);
}

return 0;
}
When passing an array to a function, the caller takes it as a pointer input. This
is called as array decaying into a pointer. When such thing happens, calling
sizeof on the pointer gives you 4 or 8 bytes that is the size of the pointer on
the architecture. In general it is wise to pass the number of elements of the array
as argument to the function call.
For example,
int f(int *a, int a_len)
{
}

int main()
{
int a[10];

f(a, 10);
}

84
Dynamic Memory Allocation
Dynamic memory is the runtime memory required by the program. Some
programs require dynamic memory and some do not. It depends on the usecase
as to why few programs require the dynamic memory to be allocated.
Dynamic memory is always allocated in the heap section of the program.
The Operating System provides a system call layer that allows the C library to
make a call to the operating system to get the memory allocated for the program.
Generally the memory returned is not contiguous but is sparsed. This behavior
will be different for different operating systems.
Though the memory specific system calls return the memory, the C library
functions keeps a note of memory asked for book keeping or for efficiency.

85
1. malloc
The malloc function is a C function that is used to allocate memory dynamically.
It is declared in stdlib.h. The memory allocated by malloc may contain the
old data that is used by other programs.
The prototype is as follows:
void *malloc(int size);
The example code is as follows.
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *a;

a = malloc(sizeof(int));
*a = 4;

printf("a %p *a %d\n", a, *a);

return 0;
}
malloc makes a call to the underlying operating system call to allocate the heap
memory.
Once the memory is allocated, it can only be freed with the free.
The call to the function malloc can fail and it results in a NULL pointer. In the
example above, we are directly dereferencing a without checking if it can be a
NULL pointer.
Dereferencing a NULL pointer results in faults and the program will be aborted.
The reason of returning NULL is that if the operating system is under high
memory pressure (that is most of the memory is being used), in such cases, the
operating system cannot allocate anymore memory available. This results in
being returning a NULL pointer.
So, the best practises of using allocated pointers is to always check for NULL.
Below is another example of malloc:
#include <stdio.h>
#include <stdlib.h>

int main()
{

86
int *a;
int i;

a = malloc(sizeof(int) * 10); // allocate space for 10 integers


if (!a) {
return -1;
}

for (i = 0; i < 10; i ++) {


a[i] = i;
}

for (i = 0; i < 10; i ++) {


printf("a[%d] = %d\n", i, a[i]);
}

return 0;
}
In the above example, the pointer a is allocated with 10 elements and is being
accessed just like an array.

87
2. calloc
The calloc function is a C function that is used to allocate memory dynamically.
It is declared in stdlib.h. Once the memory is allocated, it is cleared and
returned to the caller.
The prototype is as follows:
void *calloc(int n_elements, int size);
The example code is as follows.
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *a;

a = calloc(1, sizeof(int));
*a = 4;

printf("a %p *a %d\n", a, *a);

return 0;
}

88
3. realloc
The function realloc re-allocates memory on an already allocated memory
basically expanding it.
If the given memory is NULL, then it works similar to malloc.
void *p;

p = realloc(p, sizeof(int) * 10); // might crash -> address of p points to garbage

p = NULL;

p = realloc(p, sizeof(int) * 10); // malloc

p = realloc(p, sizeof(int) * 20); // now memory is expanded to 20 elements


The returned pointer address after realloc may not be same but if there were
old contents, they are preserved and so they are present in the newly returned
address.
The call to free can be used only one even if realloc is called many times on
the same memory.
This is because the libc tracks allocated memory for a given pointer.
Below is an implementation of a C code with the use of realloc.
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *a;
int i;

a = calloc(4, sizeof(int));
if (!a) {
return -1;
}

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


a[i] = i;
}

printf("calloced a %p\n", a);

a = realloc(a, sizeof(int) * 10);


if (!a) {
return -1;

89
}

printf("realloced a %p\n", a);

for (i = 4; i < 10; i ++) {


a[i] = i;
}

for (i = 0; i < 10; i ++) {


printf("a[%d] = %d\n", i, a[i]);
}

free(a);

return 0;
}
The usecases of realloc are generally when allocated memory is not enough
and more is required.
In general linked lists solve this problem by adding more elements to the chain.
But there could be some cases where the array of pointers are used and the
memory needs to be expanded.

90
4. free
Every memory allocation must be freed, otherwise the system will run out of
the memory. This is called memory leak. Below is one example of the free.
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *a;

a = malloc(sizeof(int));
if (a) {
*a = 4;

printf("a %p *a %d\n", a, *a);


free(a);
}

return 0;
}
2. Allocating and freeing array
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *a;
int i;

a = malloc(10 * sizeof(int));
for (i = 0; i < 10; i ++) {
a[i] = i;
}

for (i = 0; i < 10; i ++) {


printf("a[%d] = %d\n", i, a[i]);
}

free(a);

return 0;
}

91
Double pointers
Pointers can have pointers pointing to them. It can be as many pointers as
possible. But the double and triple pointers may be rarely used.
int **p;
int *r;
int v;

r = &v; // assign address of v to r


p = &r; // assign address of r to p
Below is an example,
#include <stdio.h>

int main()
{
int r;
int *p = &r;
int **s = &p;

*p = 3;

printf("r: %d *p: %d **s: %d\n", r, *p, **s);

return 0;
}
The general usecase of the double pointers is return an allocated address and
pass it as return value in the function arguments.
Below is an example:
#include <stdio.h>
#include <stdlib.h>

int alloc(int **p)


{
*p = calloc(1, sizeof(int));
if (!*p) {
return -1;
}

**p = 3;

return 0;
}

92
int main()
{
int *p = NULL;

alloc(&p);

printf("*p: %d\n", *p);

free(p);

return 0;
}
In the example, the address of p is passed and the double pointer captures it
with the Pass by Reference. Once captured, the pointer p is allocated in the
function and assigned a value.
To test the functionality we print the p value and since its allocated, we free it.
Allocating and freeing double pointers
Double pointers of type type **ptr are allocated in the following way:
1. Allocate memory for the number of pointers of type type *ptr would need
to be held.
2. Allocate memory for each type *ptr.
For example, to allocate memory for 4 integer pointers each want to further hold
3 integers, we need to do the following.
int **p; // 4 rows x 3 elements in each row
int i;

p = calloc(1, sizeof(int *) * 4 * 3); // 12 pointers in total


for (i = 0; i < 4; i ++) { // for every pointer
p[i] = calloc(1, sizeof(int) * 3); // allocate size for 3 elements
}
Below is one example, Download it here.
#include <stdio.h>
#include <stdlib.h>

int main()
{
int **p;
int i;
int j;

p = calloc(1, sizeof(int *) * 4 * 4);

93
if (!p) {
return -1;
}

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


p[i] = calloc(1, sizeof(int) * 4);
if (!p[i]) {
return -1;
}
}

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


for (j = 0; j < 4; j ++) {
p[i][j] = i + j;
}
}

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


for (j = 0; j < 4; j ++) {
printf("p[%d][%d] = %d\n", i, j, p[i][j]);
}
}

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


free(p[i]);
}

free(p);

return 0;
}
Below is another example of allocating structures and pointers with in each
structure. Download it here.
#include <stdio.h>
#include <stdlib.h>

struct S {
int v;
char *ptr;
};

int main()
{
struct S **s;
int i;

94
int j;

s = calloc(1, sizeof(struct S *) * 4 * 3); // alloc 4 pointers


if (!s) {
return -1;
}

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


// for each pointer allocate memory for structure
s[i] = calloc(1, sizeof(struct S) * 3);
if (!s[i]) {
return -1;
}

for (j = 0; j < 3; j ++) {


s[i][j].v = i;
s[i][j].ptr = calloc(1, 14);
if (!s[i][j].ptr) {
return -1;
}

sprintf(s[i][j].ptr, "alloc_%d", i);


}
}

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


for (j = 0; j < 3; j ++) {
printf("s[%d][%d].v %d s[%d][%d].ptr %s\n", i, j, s[i][j].v, i, j, s[i][j].ptr);

free(s[i][j].ptr);
}
free(s[i]);
}

free(s);

return 0;
}

95
Recap about variables and scope
auto type
The type auto does not signify anything in C. This type is very significant
however in C++.
volatile type
The keyword volatile is used in places where there is a real address being used.
It is attached generally to integers.
volatile uint32_t *addr = 0xA0000000;
Lets look at an example where a register in the memory needs to be checked
continuously until it reaches a certain value.
while ((*addr & 0x80) == 1) {
...
}
The addr is not set by the software but by the hardware.
Without the use of the volatile keyword, the compiler would simply return
without executing anything in the inner loop.

96
Function Pointers
Pointers to functions are similar to pointer to the variables. Functions also have
addresses.
int (*fptr)(void);
Defines a function pointer, that accepts no arguments and returns an integer.
int (*fptr)(char *fmt, ...);
Defines a variable argument function.
Function pointers in software are useful to write abstractions.
Consider the following case,
1. An Ethernet phy product A provides capability for transmit, receive
functionality for internet working.
2. Another Ethernet phy product B provides same capabilities for internet
working.
So to the user it does not matter which hardware is being used downside. All he
care about is the internet.
An Operating system programmer must have to implement something that must
abstract the two interfaces to provide a uniform interface to the user. This
generally is where function pointers come in.
Lets convert that above feature into software.
struct ethernet_driver {
int (*transmit)(uint8_t *packet, int packet_size);
int (*receive)(uint8_t *packet);
};
The structure ethernet_driver provides an interface to the upside user and
the downward phy device.
enum eth_driver_list {
A,
B,
};

struct ethernet_driver *e;


enum eth_driver_list list[2] = { A, B };

e = probe_for(&list, 2);
Here, the function probe_for looks for hardware registers which hardware is
inserted, whether its A or B. If its A, it would return the ethernet_driver
pointer of the driver A, otherwise it returns B.

97
in A_eth_Driver.c:

int a_transmit(uint8_t *packet, int packet_size);


int a_receive(uint8_t *packet);

static const struct ethernet_driver A_driver = {


.transmit = a_transmit,
.receive = a_receive,
};

struct ethernet_driver *get_A_functionality()


{
return &A_driver;
}
in B_eth_Driver.c:

int b_transmit(uint8_t *packet, int packet_size);


int b_receive(uint8_t *packet);

static const struct ethernet_driver B_driver = {


.transmit = b_transmit,
.receive = b_receive,
};

struct ethernet_driver *get_B_functionality()


{
return &B_driver;
}
in probe.c:

#define A_REG 0x000A0000


#define B_REG 0x000B0000

struct ethernet_driver *probe_for(enum eth_driver_list *list, int len)


{
int i;

for (i = 0; i < len; i ++) {


if (list[i] == A) {
if (match_reg(A_REG)) {
return get_A_functionality();
}
} else if (list[i] == B) {

98
if (match_reg(B_REG)) {
return get_B_functionality();
}
}
}

return NULL;
}
The functionality of the caller for probe_for remain the same even if a new
driver gets added, it will have a new driver file implementing the new driver and
the probe_for function update to call the driver.
The most important observation here is that the caller can directly perform
a call to eth->transmit and eth->receive without knowing the underlying
driver functionality.
This is called abstraction. This feature is very well defined within the language
in C++ as Abstract classes. We will describe this in more details.

99
Array of function pointers
The array of function pointers have the below syntax.
int (*fptr[4])(void); // defines array of 4 function pointers
To be much simpler, one can typedef the function pointer and define the arrays.
typedef int (*fptr)(void); // defines a function pointer

fptr f[4]; // defines 4 function pointers


Below is an example of array of function pointers,
#include <stdio.h>

int f()
{
static int count = 0;

printf("F called %d\n", count);

return ++ count;
}

typedef int (*fptr_f)(void);

int main()
{
fptr_f fptr[4];
int i;

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


fptr[i] = f;
}

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


fptr[i]();
}

return 0;
}

100
Structures
Data structure is a group of variables of different types. The struct word is
used as an identifier to the compiler to make it recognize the structure.
An example of data structure looks as follows.
struct shelf {
char book_name[10];
int n_papers;
};
Above structure defines a shelf that contains a list of books and papers, one is a
string and another is an integer.
Defining the structure variable is similar to defining the base type.
struct shelf s;
here s is of type structure shelf.
Accessing the elements in the structure is via the . operator.
#include <stdio.h>
#include <string.h>

struct shelf {
char book_name[10];
int n_papers;
};

int main()
{
struct shelf s;

strcpy(s.book_name, "Witcher");
s.n_papers = 2000;

printf("book_name: %s papers: %d\n", s.book_name, s.n_papers);

return 0;
}
A structure can be inside another structure as well.
struct book {
char book_name[10];
char book_author[10];
};

struct shelf {

101
struct book book;
int n_papers;
}
We can apply the typedefs to structures as well. such as,
typedef struct book {
...
} book_t; // define book typedef

typedef struct book book_t; // define typedef in a new line

typedef struct { // define typedef without naming


} book_t;
Now book_t can be used to define structure variables. Generally _t prefix is
used to differentiate the typedefs. But its only a choice of programmer and not
defined by the C standard.
One can also use macros for better sounding names.
#define Book_Info struct book
But doing this generally avoided although its possible.
Macros though can be used this way, their whole purpose is for naming constants
or writing small function like macros.
The elements are accessed as follows.
struct shelf s;

void set_book(struct book *b, char *book_name, char *book_author)


{
strcpy(b->book_name, book_name);
strcpy(b->book_author, book_author);
}

void set_shelf(char *book_name, char *book_author, int n_papers)


{
s.n_papers = n_papers;
set_book(&s.b, book_name, book_author);
}
The variable b of the type struct book is passed as a pointer to the set_book.
The function set_book sets the book_name and book_author.
An array of structures is possible too.
struct book {
char book_name[10];
char book_author[10];

102
};

struct shelf {
struct book books[10];
int n_papers;
}
Pointers in Structures
Structures can contain pointers as well.
struct book {
char *book_name;
char *book_author;
};
These are allocated just the way pointers are allocated.
book->book_name = calloc(1, 40);
book->book_author = calloc(1, 20);
There can be cases when there are structures in a structure which contain pointers
to another structures or variables. In such cases freeing all the structures becomes
a real problem. Doing it in the same order of allocation guarantees no crashes
but leaks memory. To do the right way, one must free in the reverse order of
allocation.
Below example shown provides such a method.
struct S1 {
int v;
};

struct S2 {
struct S1 s1;
};

...
struct S2 s2;

s2.s1.v = 3;

printf("v: %d\n", s2.s1.v);


Structure Initialization
Structures initialization can be done in many ways. One way of initialization is
as follows.
struct A {
int val;

103
double val_d;
};

struct A a = { .val = 3, .val_d = 3.1 };


Is one way to initialize the structure.
Below is an example,
#include <stdio.h>

struct A {
int val;
double val_d;
};

int main()
{
struct A a = { .val = 3, .val_d = 3.1 };

fprintf(stderr, "val: %d\n", a.val);


fprintf(stderr, "val_d: %f\n", a.val_d);

return 0;
}
Array of structures
Arrays of structures is possible as well.
struct A {
int val;
double val_d;
};

struct A a[10]; // array of structures of type `A`.


They can be iterated just like arrays.
struct A {
int val;
double val_d;
};

void set(struct A *a, int size)


{
int i;

for (i = 0; i < size; i ++) {


a[i].val = i;

104
a[i].val_d = i + 0.1 * i;
}
}
Function pointers in structures
The below structure declares two function pointers get and set which are
accessible from the structure variable.
struct S {
int (*get)();
void (*set)(int);
};

struct S s;

s.set(3); // set the variable


int var = s.get(); // get the variable
But in general the pointers s.get and s.set contain garbage pointers. So
accessing them generally results in a segmentation fault or in bad situation
results in abnormal program execution.
One way to assign the addresses is the following:
int a;
int my_get() { return a; }
void my_set(int A) { a = A; }

struct S s;

memset(&s, 0, sizeof(struct S));

s.get = my_get;
s.set = my_set;
Now accessing the function pointers s.get and s.set will indirectly call my_get
and my_set functions.
While we can have function pointers in structures, but we cannot have functions
in structure. This is not allowed in C. However, C++ allow this grouping of
data and operations.
The below structure is incorrect and results in error.
#include <stdio.h>

struct S {
int f(void);
};

105
int main()
{
}
The error,
c/struct_func.c:4:9: error: field ‘f’ declared as a function
4 | int f(void);
| ˆ
Structure pointers
Structure pointers have a wide range of usecases. One of such use cases is writing
abstractions.
The structure can have a reference of its own as the element. This is particularly
advantageous to implement data structures.
Below is one example of such structure.
struct S {
int v;
struct S *next;
};
The section “Data Structures” describe more on the usecases.
Typecasting from structure pointers to void pointers and assignment from
structure pointer to void pointer is possible.
struct S *s;
void *v;

s = calloc(1, sizeof(struct S));


v = s; //valid

s = (struct S *)v; // valid


s = v; // may produce warning that conversion from void * to struct

printf("val %d\n", v->v); // still incorrect.. dereferencing the void pointer


// is still incorrect

106
Bit fields
Unsigned integer types in structure can be represented in bits.
struct S {
uint32_t v:1; // 1 bit value
uint32_t p:2; // 2 bit value
};
Below is an example,
#include <stdio.h>
#include <stdint.h>

struct S {
uint32_t v:1;
uint32_t p:2;
};

int main()
{
struct S s;

s.v = 1;
s.p = 2;

printf("v: %d p: %d\n", s.v, s.p);


printf("size: %ld\n", sizeof(struct S));

return 0;
}
The general use of the bit-fields is to represent the hardware register values.
Some can be one bit and some more than one bit.
The addresses of bit-fields cannot be taken. Doing this results in compiler error.
#include <stdio.h>
#include <stdint.h>

struct S {
unsigned int v:1;
unsigned int p:2;
};

int main()
{
struct S s;
struct S *s1;

107
s1 = &s;

s1->v = 1;
s1->p = 3;

int *v = &s1->v;
}

The above program results in compiler error.


c/bitfields_2.c: In function ‘main’:
c/bitfields_2.c:19:14: error: cannot take address of bit-field ‘v’
19 | int *v = &s1->v;
| ˆ
One cannot represent more bits than the type can offer.
#include <stdio.h>
#include <stdint.h>

struct S {
uint32_t v:33;
uint32_t p:1;
};

int main()
{
}
The above program results in a compiler error.
c/bitfields_3.c:5:14: error: width of ‘v’ exceeds its type
5 | uint32_t v:33;
| ˆ
Only integer types allowed as bit-fields. Using other types result in compilation
error.
#include <stdio.h>
#include <stdint.h>

struct S {
double v:1;
uint32_t p:2;
};

int main()

108
{
}
The above program results in compiler error.
c/bitfields_4.c:5:12: error: bit-field ‘v’ has invalid type
5 | double v:1;
| ˆ
Lookup tables with structures
Lookup tables can be implemented with structures or arrays. Below is an
example,
enum fruits {
Apples,
Oranges,
Mangoes,
Pineapples,
Pears,
};

struct fruit_basket {
char fruit_name[20];
int price_per_kg;
} fruits_basket[] = {
{"Apples", 200},
{"Oranges", 200},
{"Mangoes", 100},
{"PineApples", 200},
{"Pears", 200},
};

struct fruit_basket *get_friut_item(fruits f)


{
if ((f < Apples) || (f > Pears)) {
return NULL;
}
return fruits_basket + f; // get the pointer for the fruit type
}
Lookup tables are predominantly used to index into a given array and return
the pointer / item. This avoids the usage of looping.

109
Structure padding and packing
Structure Padding
Sometimes data structures need to be aligned to a multiple of byte boundary.
This is because the hardware or architecture alignment. Without an aligned
buffer, the hardware access to the buffer might result in a crash.
Gcc provides __attribtue__ ((aligned())) specifier.
struct S {
int r;
int p;
} __attribute__ ((aligned(16)));
The sizeof operation on the structure above results in 16. Although the actual
size could be 8, since the aligned tag is given, extra pad bytes are added to
make it multiple of 16 bytes.
Below is an example,
#include <stdio.h>

struct S {
int v;
int r;
} __attribute__ ((aligned(16)));

int main()
{
printf("size %ld\n", sizeof(struct S));

return 0;
}
Structure Packing
The compilers provide a mechanism to pack the elements of the structure to
close any holes created due to the padding / alignment.
Gcc provides __attribute__ ((__packed__)) specifier.
struct S {
int v;
int p;
char r;
} __attribute__ ((__packed__));
The sizeof on above structure returns 9. Supposedly it should have 12 without
packed attribute.
Below is one example:

110
#include <stdio.h>

struct S {
int v;
int p;
char r;
} __attribute__ ((__packed__));

struct R {
int v;
int p;
char r;
};

int main()
{
printf("regular_size %ld packed_size %ld\n", sizeof(struct R), sizeof(struct S));

return 0;
}

111
Enumeration
Enumerations in C are similar to macros. An example of an enum is as shown
below.
enum army {
Snipers,
Medics,
Seals,
};
The elements in the enum are accessed the following way.
enum army sniper_team;

sniper_team = Snipers;
The first element of the enum always start with 0 when uninitialized.
The elements in the enum can be initialized with values too. Lets see the following
definition.
enum army {
Snipers = 10,
Medics,
Seals,
};
The rest of elements are incremented by 1 every time. For example, Medics will
become 11 and Seals will become 12.
The below declaration is valid as well.
enum army {
Snipers = 10,
Medics = 12,
Seals,
}
The enumeration Seals now becomes 13.
The enumerations are like integers. If assigned a double value to an enumeration,
results in compiler error.
The size of the enum can as well be calculated with sizeof.
#include <stdio.h>

enum army {
Snipers,
Medics,
Seals,

112
};

int main()
{
printf("size %lu\n", sizeof(enum army));

return 0;
}
We can have a pointer to the enum as well.
enum army {
Snipers,
Medics,
Seals,
};

enum army charlie_1, *charlie_2;

charlie_2 = &charlie_1;
is perfectly valid.
But most of the cases we never use enum pointers as they occupy only few bytes
(4 or 8).
We can typedef the enums similar to the structures. Below are few examples.
typedef enum army {
...
} army_t; // typedef at the end

enum army {
...
};

typedef enum army army_t; // typedef in a separate line

typedef enum { // naming not required as typedef provides the name


...
} army_t;

113
Unions

114
Appendix A

115
Significance of header files
Header files can include structures, macro definitions, and function prototypes.
Sometimes they even contain inline functions.
Here are few advantages:
1. They can be used to group related functionality. Provides structure to the
program when separated well.
2. They can be used to expose the prototypes, macro definitions and structures
to other files. The other files can use these prototypes to avoid implicit
calls.
3. Best way to distribute the software in package of header files and library
.so or .a files.

116
Header description
The stdio.h contains prototypes for printf, scanf, fprintf, fscanf, fopen,
fclose, fgets and so on. The stdint.h has further more data types. See
/usr/include/stdint.h The limits.h contains all the ranges of the base types.
See /usr/include/limits.h. The stdlib.h contains the prototypes for atoi,
malloc, calloc, realloc and free. The string.h contains the prototypes for
string related functions such as strlen, strcpy, strcat, strcmp and so on.

117
Compilation of C program
The compiler gcc is used through out this book to compile C programs. The
gcc is a compiler and it comes with other tooling to generate the binary file.
Below are the compilation stages of a C program.
1. Preprocessor
2. Compiler
3. Aseembler
4. Linker
Preprocessor
During this stage the compiler parses the input file, replaces macros, performs
syntax checking. This can be done with -E switch to the gcc.
Compiler
During this stage, the compiler converts the program into the object code of the
given platform.
Architecture based compilation is done here, where if a compiler can generate
code for a different target architecture, it can then creates the object code for
the target architecture.
This can be done with -c switch to the gcc.
Assembler
During this stage, the generated target specific object code then converted into
an assembler code representation.
This can be done with -s switch to the gcc.
Linker
During this stage, the linker converts the object representation into an executable.
Generally in linux the ELF is the format of the executable file.
This is another tool called ld. We tend not to use this directly unless we are
writing software specifically for targeted embedded devices.

118
GCC compilation options

S.No Name Description


1 -Wall Enable all warnings
2 -Werror Enable all errors
3 -Wextra Enable extra warnings
4 -Wshadow Show the warnings for a variable
declared twice in same function
5 -Os Enable possible optimizations
6 -g Enable debug symbols in the created
binary
7 -c Create an object file

119
Valgrind

120
Command line arguments (argc, argv)
The function main accepts two more arguments argc and argv. The signature
looks as follows.
int main(int argc, char **argv);
The first argument is the number of arguments and the second argument contain
the actual number of arguments.
Below is one example,
#include <stdio.h>

int main(int argc, char **argv)


{
int i;

for (i = 0; i < argc; i ++) {


printf("arg[%d]: %s\n", i, argv[i]);
}

return 0;
}
Compile and run the above program with the following arguments:
./a.out 1 2 3

arg[0]: ./a.out
arg[1]: 1
arg[2]: 2
arg[3]: 3
The argv[0] is always the program name with full path. The rest of the
arguments are the ones given after ./a.out separated by space.
argc provides the number of arguments in argv.

121
getopt The library function getopt provides a way to parse command line
arguments. Its prototype is defined in getopt.h.
int getopt(int argc, const char *argv[], const char *optstring);
The options are passed in optstring. For example the user can enter options
as follow as input to the command.
./a.out -f filename -r
The optstring here will be f:r.
• f: means that the option f will have an argument.
• r means that the option r will not have an argument, this is like a true
or false statement.
There can be as many number of arguments as possible via the command line.
Below is one example of getopt.
1. Taking command line arguments via getopt
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <getopt.h>

int main(int argc, char **argv)


{
int a = 0;
char *str = NULL;
bool t = false;
int ret;

while ((ret = getopt(argc, argv, "i:s:t")) != -1) {


switch (ret) {
case 'i':
a = atoi(optarg);
break;
case 's':
str = optarg;
break;
case 't':
t = true;
break;
default:
printf("%s <-i int value> <-s string value> -t\n", argv[0]);
return -1;
}
}

122
printf("a %d\n", a);
if (str) {
printf("str %s\n", str);
}
printf("t %d\n", t);

return 0;
}
The function getopt returns a character that is parsed and the value in the
optarg variable. The optarg is a string. The function getopt should be called
in a loop. The return value of -1 from getopt mean that the getopt has finished
parsing.

123
getopt_long

File I/O
File I/O operations are exposed to the user by the libc. The libc indirectly calls
the underlying system calls of the Operating system to manipulate the file. The
File I/O APIs are declared in the stdio.h.
Basic File operations
1. Opening and Closing a file
FILE is the structure implemented by the libc that is used in many of the file
operations.
FILE *fp;
Denotes a file pointer of type FILE.
fopen is used to open the file. fclose is used to close the file.
The prototype of fopen is
FILE *fopen(const char *filename, const char *mode);
The fopen function have the following modes.

S.No Name Description


1 r read
2 w write
3 a append
4 rb read binary
5 wb write binary
6 ab append binary

The prototype of fclose is


int fclose(FILE *fp);
Below example shows the use of fopen and fclose calls.
#include <stdio.h>

int main()
{
const char *filename = "./test.txt";
FILE *fp;
int ret = -1;

fp = fopen(filename, "r");

124
if (fp != NULL) {
printf("file opened success\n");
fclose(fp);
ret = 0;
} else {
printf("file not found\n");
ret = -1;
}

return ret;
}
2. Reading from a File
Reading a file is possible with the fgets function. The fgets prototype is as
follows.
void *fgets(char *str, uint32_t size, FILE *fp);
The fgets returns pointer to the string that is read, but also the argument str
contain the data that is read from the file. The size argument specify the size
of the string.
Below is one example use of fgets function.
#include <stdio.h>

int main()
{
const char *filename = "./test.txt";
char msg[1024];
FILE *fp;
int ret = -1;

fp = fopen(filename, "r");
if (fp) {
while (fgets(msg, sizeof(msg), fp)) {
printf("%s", msg);
}
fclose(fp);
ret = 0;
}

return ret;
}
The fgets function adds the \n new line upon every call. When calling strlen
on it, it returns one character more than the actual length. So the last characters
of the read string are \n\0. To strip of the last character, we can go to the end

125
of the string and one character before and assign \0 to it.
Below is one way to do it.
char msg[1024];

fgets(msg, sizeof(msg), fp);

/* replace '\n' with '\0'. */


msg[strlen(msg) - 1] = '\0';
3. Writing to a file
Writing to a file can be done with the fputs, fputc or fprintf functions.
The prototype of fputs is as follows.
int fputs(char *str, FILE *fp);
Below is one example:
#include <stdio.h>

int main()
{
const char *filename = "./test.txt";
char msg[1024];
FILE *fp;
int ret = -1;

fp = fopen(filename, "w");
if (fp) {
fputs("Hello World", fp);
fclose(fp);
ret = 0;
}

return ret;
}
4. Copying a file
Copying a file to another file in C involve reading the input file and writing the
content to a new file.
Below is one example of copying a file:
#include <stdio.h>

int main()
{
const char *file_in = "c/filecopy.c";

126
const char *file_out = "c/filecopy.c.copy";
FILE *f_in;
FILE *f_wr;
char str[1024];

f_in = fopen(file_in, "r");


if (!f_in) {
return -1;
}

f_wr = fopen(file_out, "w");


if (!f_wr) {
fclose(f_in);
return -1;
}

while (fgets(str, sizeof(str), f_in)) {


fputs(str, f_wr);
}

fclose(f_in);
fclose(f_wr);

return 0;
}
5. Number of characters in a file
The function fgetc reads one character from a file. If an end of file is reached, it
returns an EOF marker which the caller can then check and stop reading further.
int fgetc(FILE *fp);
In general fgetc does not have much usecases for high performat applications.
Its use is in writing tools.
Using fgetc we can count the number of characters in a file.
#include <stdio.h>

int main()
{
const char *filename = "c/numchar.c";
FILE *fp;
char a;
int num_chars = 0;

fp = fopen(filename, "r");
if (!fp) {

127
return -1;
}

while (1) {
a = fgetc(fp);
if (a == EOF) {
break;
}
num_chars ++;
}

printf("number of characters %d\n", num_chars);

return 0;
}
6. Finding the file size
The functions fseek and ftell can be used to seek to a certain position and
finding the file offset respectively.
The prototype of fseek is as follows:
int fseek(FILE *fp, long offset, int whence);
The whence parameter is one of SEEK_SET, SEEK_CUR or SEEK_END.
The prototype of ftell is as follows:
long ftell(FILE *fp);
The below example uses both the functions.
#include <stdio.h>

int main()
{
const char *filename = "c/filesize.c";
long int filesize;
FILE *fp;

fp = fopen(filename, "r");
if (!fp) {
return -1;
}

fseek(fp, 0, SEEK_END);
filesize = ftell(fp);

printf("file size %ld\n", filesize);

128
fclose(fp);

return 0;
}

129
Operating with the binary files
We can write arrays, structures as is to the files. They are generally binary data
and the files are sometimes called binary files.
C library provides two functions.
1. fread
2. fwrite
fread
The prototype of fread is as follows.
size_t fread(void *ptr, size_t size, size_t n_memb, FILE *fp);
fwrite
The prototype of fwrite is as follows.
size_t fwrite(const void *ptr, size_t size, size_t n_memb, FILE *fp);
Writing and reading of binary files are used with wb and rb arguments to fopen.
FILE *fp;

fp = fopen("a.bin", "wb"); // opens in binary file in write mode


...
fclose(fp);

fp = fopen("a.bin" ,"rb"); // opens the binary file in read mode


...
fclose(fp);
Below is an example,
#include <stdio.h>

int main()
{
int a[100];
int b[100];
int i;
FILE *fp;

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


a[i] = i;
}

fp = fopen("./test.bin", "wb");
if (!fp) {
return -1;

130
}

fwrite(a, sizeof(a), 1, fp);

fclose(fp);

fp = fopen("./test.bin", "rb");
if (!fp) {
return -1;
}

fread(b, sizeof(b), 1, fp);

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


printf("b[%d] = %d\n", i, b[i]);
}

fclose(fp);

return 0;
}
Below is an example of reading and writing data structures to and from a binary
file,
#include <stdio.h>

struct S {
int a;
int r;
};

struct S s1[10];

int main()
{
struct S s;
int i;
FILE *fp;

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


s1[i].a = i;
s1[i].r = i + 1 * i;
}

fp = fopen("./s.bin", "wb");
if (!fp) {

131
return -1;
}

fwrite(s1, sizeof(s1), 1, fp);


fclose(fp);

fp = fopen("./s.bin", "rb");
if (!fp) {
return -1;
}

while (fread(&s, sizeof(s), 1, fp)) {


printf("s.a %d s.r %d\n", s.a, s.r);
}

fclose(fp);

return 0;
}

132
I/O operations
1. Using fscanf, fgets and fprintf
The functions fscanf, fgets and fprintf allow to perform more than file
operations but also input and output operations on console as well.
There are 3 pointer definitions to access the input and output.

S.No Name Description


1 stdin standard input
2 stdout standard output
3 stderr standard error

fscanf
The prototype of fscanf looks as follows.
int fscanf(FILE *fp, const char *fmt, ...);
The fscanf function can be used to read the input data entered on console. The
stdin can be used as the first argument.
#include <stdio.h>

int main()
{
int a;
char str[100];
int ret;

ret = fscanf(stdin, "%d %s", &a, str);


if (ret != 2) {
printf("incorrect number of arguments\n");
return -1;
}

printf("a=%d str=%s\n", a, str);

return 0;
}
The fscanf can be specifically used to read a pattern set from a file. For example
consider a database with name and age group of friends such as the following.
dev 33
rahul 34
nithin 38
seema 35

133
Below program reads the input file using fscanf and prints the contents on the
screen.
#include <stdio.h>

int main()
{
const char *filename = "./c/file_pattern";
FILE *fp;
int ret;

fp = fopen(filename, "r");
if (!fp) {
return -1;
}

while (1) {
char name[30];
int age;

ret = fscanf(fp, "%s %d", name, &age);


if (ret != 2) {
break;
}
printf("name: %s age: %d\n", name, age);
}

fclose(fp);

return 0;
}
fgets
The prototype of fgets looks as follows.
char *fgets(char *str, int size, FILE *fp);
The function fgets returns the entire line that is read from the fp.
• If the fp is stdin it will return an entire line that is read.
• If the fp is a file it will return an entire line that is read.
Below program shows an example usage of fgets on stdin.
To exit the program press ctrl + D. The ctrl + D is an end of file marker.
Soon as the function fgets encounters it, it returns NULL.
#include <stdio.h>

134
int main()
{
char str[100];
char *err;

while (1) {
err = fgets(str, sizeof(str), stdin);
if (err == NULL) {
printf("stopping program\n");
break;
}

printf("you entered - %s", str);


}

return 0;
}
Below is an example of counting number of lines in a file.
#include <stdio.h>

int main()
{
FILE *fp;
int lines = 0;
char line[1024];

fp = fopen("./c/fgets_lines.c", "r");
if (!fp) {
return -1;
}

while (fgets(line, sizeof(line), fp)) {


lines ++;
}

fclose(fp);

printf("lines: %d\n", lines);

return 0;
}
Below program shows an example of fgets reading from the file.
#include <stdio.h>

135
int main()
{
char *filename = "c/fgets2.c";
FILE *fp;
char str[100];

fp = fopen(filename, "r");
if (!fp) {
return -1;
}

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


fprintf(stderr, "%s", str);
}

fclose(fp);

return 0;
}
Lets look at the below program:
#include <stdio.h>

int main()
{
char *filename = "c/fgets_newline.c";
FILE *fp;
char str[100];

fp = fopen(filename, "r");
if (!fp) {
return -1;
}

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


fprintf(stderr, "line '%s'\n", str);
}
fclose(fp);

return 0;
}
It prints
line '#include <stdio.h>
'
line '

136
'
line 'int main()
'
line '{
'
line ' char *filename = "c/fgets_newline.c";
'
line ' FILE *fp;
'
line ' char str[100];
'
line '
'
The extra newline at each line is the fgets adding a newline soon as it encounters
a new line.
One way to solve it is to strip off the newline at the end of the string by doing
the following:
str[strlen(str) - 1] = '\0';
The above statement strips off the newline character from the buffer returned
by fgets.
fprintf
The prototype of fprintf looks as follows.
int fprintf(FILE *fp, const char *fmt, ...);
Just like fscanf the fprintf prints on the console or to a file. The stdout
pointer can be used to print the values on the console.
Below example prints the values on the console using fprintf.
#include <stdio.h>

int main()
{
char *str = "dev";
int a = 33;

fprintf(stdout, "str=%s a=%d\n", str, a);

return 0;
}
The fprintf function can take a file descriptor so it can also be a file that is
opened in write mode.
Below example prints the age and name of friends in the file using fprintf.

137
#include <stdio.h>

struct S {
char *name;
int age;
} friends[] = {
{"Dev", 33},
{"Rahul", 34},
{"Nithin", 38},
{"Seema", 35},
};

int main()
{
const char *filename = "c/file_write";
FILE *fp;
int i;

fp = fopen(filename, "w");
if (!fp) {
return -1;
}

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


fprintf(fp, "%s %d\n", friends[i].name, friends[i].age);
}

fclose(fp);

return 0;
}

138
Useful macros
1. Minimum of two numbers i
#define MIN(_a, _b) (((_a) < (_b)) ? (_a) : (_b))
2. Maximum of two numbers
#define MAX(_a, _b) (((_a) > (_b)) ? (_a) : (_b))
3. Check if bit is set
#define check_bit(_val, _pos) (((_val) >> (_pos)) & 1u)
4. Swap two variable of same type
#define swap(_a, _b, _type) {\
_type _t = _a; \
_a = _b; \
_b = _t; \
}
5. Sizeof macro for arrays
#define SIZEOF(_a) (sizeof((_a))/sizeof((_a[0])))

139
Useful helper functions
1. stoi
Description:
String to integer conversion with using strtol.
int stoi(const char *str, int *val)
{
char *err = NULL;

*val = strtol(str, &err, 10);


if (err && (err[0] != '\0')) {
return -1;
}

return 0;
}
2. CSV file writer

140
C++ programming

141
Introduction
Compiling C++ programs is as simple as using g++ instead of gcc. Here’s how
to install it in Ubuntu.
sudo apt install gcc-g++
Few conventions in C++:
1. Source File extensions end with cxx or cpp or cc.
2. Header file extensions end with hxx or hpp or h.
3. The standard header files does not have to be included with .h extension.
4. Structure and Enum names can be used without a struct prefix before
them when declaring.
C++ standard is a continuously evolving standard. Right now the standard
C++23 is ongoing. Currently this book tries to differentiate the features present
in each standard, but the idea is that the features in each new standard must be
exploitable for use in the software that is going to be written. Thus, the features
will be mixed and usecases will be drawn upon these features.

142
cout, cerr and cin
Include header file iostream when using cerr, cout and cin.
cerr is much similar to fprintf writing to stderr.
Below is one example use of cerr.
#include <iostream>

int main()
{
std::cerr << "hello world" << std::endl;
return 0;
}
cout is much similar to printf and cin is much similar to fgets.
But the use of cout and cin are very different.
For example,
#include <iostream>

int main()
{
std::cout << "hello world" << std::endl;
return 0;
}
Compile this with g++ as g++ hello_world.cc. As usual the binary a.out is
created after successful compilation.
The above program prints “hello world” on screen when executed.
The std::endl is like the newline \n character. We can as well add \n in the
above string in the program.
Below is one example of the use of cin.
#include <iostream>

int main()
{
std::string str;
int a;

std::cin >> a >> str;


std::cout << "a: " << a << " " << "str: " << str << std::endl;

return 0;
}

143
Unlike the fscanf or scanf one does not have to give format specifier for the
cin.
The strings are taken only one at a time even for cin just like the fscanf.

144
New operators in C++
1. The Reference (&) operator.
The reference operator is similar to the pointer but a reference in general can
never be a NULL or a nullptr.
2. new and delete
new and delete operators are introduced in C++ as a replacement for C
allocation functions. The C allocation functions still work.
Below is an example of new and delete use:
#include <iostream>

int main()
{
int *a = new int;

*a = 3;

printf("a %d\n", *a);

delete a;

return 0;
}
Allocating and Freeing arrays:
The new[] and delete[] operators are used to allocate and free arrays.
#include <iostream>

int main()
{
int *a;
int i;

a = new int[10];

for (i = 0; i < 10; i ++) {


a[i] = i;
}

for (i = 0; i < 10; i ++) {


std::cout << "a[" << i << "]= " << a[i] << std::endl;
}

145
delete[] a;

return 0;
}
Allocating and Freeing structures:
#include <iostream>

struct S {
int a;
};

int main()
{
S *r;

r = new S;

r->a = 3;

std::cout << "r->a: " << r->a << std::endl;

delete r;

return 0;
}
When using C allocated functions malloc, calloc and realloc, the return type
must be typecasted to the asked type.
See below example for typecasts:
#include <iostream>

struct S {
int a;
};

int main()
{
int *r1;
S *r2;
int *r3;

r1 = (int *)malloc(sizeof(int));
if (!r1) {
return -1;

146
}

*r1 = 3;

r2 = (S *)calloc(1, sizeof(S));
if (!r2) {
return -1;
}

r2->a = 9;

r3 = NULL;

r3 = (int *)realloc(r3, sizeof(int));


if (!r3) {
return -1;
}

*r3 = 6;

std::cout << "r1: " << *r1 << " r2: " << r2->a << " r3: " << *r3 << std::endl;

free(r1);
free(r2);
free(r3);

return 0;
}

147
4. Range based for
The range based for loop is a new feature introduced in C++11.
Below is one use of the range based for.
#include <iostream>

int main()
{
for (int a: {1, 2, 3, 4})
std::cout << "a: " << a << std::endl;

return 0;
}
Below is another use of the range based for.
#include <iostream>

int main()
{
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

for (auto i : a) {
printf("%d\n", i);
}

return 0;
}
The range based for loop have the following advantages:
• No missed condition checks thus removing the probability of infinite loops.
• Auto increment and initialization.

148
5. Enum classes
Enum classes allow one to compartmentalize the definitions of enumerations.
enum class fruits {
Apples,
Oranges,
Banana,
};
The enumeration can be declared as,

fruits f;

f = fruits::Apple; // assignment of Apple

fruits f1 = fruits::Apple; // initialiation of enum


Both declarations help in their own respective.
#include <iostream>

enum class fruits {


Apple,
Oranges,
Banana,
};

int main()
{
fruits f = fruits::Apple;

std::cout << (int)f << std::endl;

return 0;
}
The enum comparison is still valid.
For example, below program shows the output that comparison is incorrect.
#include <iostream>

enum Languages {
Telugu,
Tamil,
English,
Hindi,
};

149
int main()
{
Languages l1 = Languages::Telugu;
Languages l2 = Languages::Tamil;

if (l1 != l2) {
std::cout << "Languages aren't same" << std::endl;
}

return 0;
}
The enumerations are not printable with std::cout but like the C enumeration,
they can be typecasted to integer.

150
6. nullptr
C++11 onwards the standard defines nullptr that can be assigned to any type
of pointer. When assigned, the compiler implicitly takes care of assigning the
null pointer. The nullptr does not point to 0 or (void *)0.
For example the following statements are still valid in C++.
int *p = 0; // old use of 0 for null
int *p = NULL; // NULL pointer use in C
In general it is extremely difficult to really find the type of the variable when its
using auto type deduction.
auto res = get_data();

if (res == 0) { // assuming 0 here is a null pointer

}
However, the type understood could be a NULL if the get_data is actually
returning a NULL pointer.
if (res == nullptr) { // always guaranteed the res is a null pointer
}
Data struture pointers can be assigned to nullptr as well.
struct S {
int a;
};

S *s1 = NULL; // still valid


S *s2 = nullptr; // valid
In some of the standard types such as shared_ptr, unique_ptr assigning 0 could
result in ambiguous result. The shared_ptr and unique_ptr are part of the
standard template library. They will be discussed further in their corresponding
sections.
std::shared_ptr<S> s1 = 0; // may work
std::shared_ptr<S> s2 = nullptr;
7. Functions with default arguments
C++ allows functions with default arguments. For example,
int f(int a, int b = 10)
{
return a + b;
}

151
In the above function f, the caller does not have to pass the second argument so
the call f(10) is valid.
Or if explicitly wanting to pass the value, then the second argument can be
passed. For example f(3, 3) is valid and the argument b takes value 3.
#include <iostream>

int f(int a, int b = 10)


{
return a + b;
}

int main()
{
int r = f(10);

std::cout << "r: " << r << std::endl;

r = f(3, 3);

std::cout << "r: " << r << std::endl;

return 0;
}

152
New keywords in C++

153
constexpr

154
explicit

auto The auto keyword provides an automatic type deduction when used.
An example,
auto i = 10;
says its an integer but compiler automatically understands this when the value
assigned to it is an int.
auto i = 10; // an int
auto p = 10.1; // a double
auto t = "c++"; // a const string
We use auto at times when writing a complex type becomes very hard. We will
see in the below sections on more about auto. Remember that not all auto type
deductions are as we expect.
When variables of type auto are used, then the initialization must be followed.
This generally instructs the compiler to derive the type based on the initialized
value.

155
Typecasting

156
static_cast
dynamic_cast
reinterpret_cast

157
Classes
Classes in C++ are similar to the structures in C. The Class is enclosure for
data and operations on the data.
A class would generally look like this.
class <name> {
public:
<variable_type > variable;
<return_type> function_prototype(parameters..);

protected:
<variable_type > variable;
<return_type> function_prototype(parameters..);

private:
<variable_type > variable;
<return_type> function_prototype(parameters..);
};
The below example provides a simple class definition.

class S {
public:
S() { a = 0; }
~S() { }
void set(int a) { a_ = a; }
void get() { return a_; }

private:
int a_;
};
The functions S() and ~S() are constructor and destructor respectively. The
constructor gets called when the class object is instantiated. The destructor is
called when the class object goes out of scope. Lifecycle of the class object is
similar to that of the C variable.
The functions set and get within S are called public member functions. The
variable a_ is a private member variable. In general the private members are
prefixed or postfixed with something that differentiates between a local variable
and a class member. Without it, it gets really hard to understand the variable’s
lifetime.
The sizeof on classes would give the size of the variables (excluding member
functions).
#include <iostream>

158
class S {
public:
int get() { return a; }

private:
int a;
int p;
double r;
};

int main()
{
std::cout << "size: " << sizeof(S) << std::endl;

return 0;
}
Public members can be accessed by the users of the class while private members
are not accessible.
The below declares the class object of S.
S s;
The member functions set and get are accessible as,
S s;

s.set(3);
int val = s.get();
Accessing a_ directly as below results in a compiler error that the variable is
part of the private section of the class.
S s;

int val = s.a_; // results in compiler error


The variable a_ can only be accessible via the get method.
If the public keyword is not mentioned then the scope is by default private.
class S {
S() { a = 0; }
~S() { }
void set(int a) { a_ = a; }
void get() { return a_; }

int a_;
};

159
The above code shows class members are all by default, private. Class with all
members private is legal and compiles until it is instantiated by creating an
object of the class.

160
Constructors and Destructors
The constructor is called when an object of it is created. For example,
class S {
public:
S() { a = 3; }
~S() { }

int get() { return a; }

private:
int a;
};

int main()
{
S s;
}
The declaration S s calls the constructor S(). Constructors and Destructors
will have the same name as the class. The destructor has ~ prefix attached to it.
The destructor gets called soon after the object loses its scope.
Below is an example,
#include <iostream>

class P {
public:
P()
{
std::cout << "default constructor" << std::endl;
a = 3;
}
~P()
{
std::cout << "destructor called" << std::endl;
}

int get() { return a; }

private:
int a;
};

int main()
{

161
P p;

std::cout << "p.a: " << p.get() << std::endl;

return 0;
}
Copy constructor
Copy Assignment operator
Move constructor
this pointer
The this pointer is nothing but self referencing the class member function.
The below program shows the use of this pointer. Download it here.
#include <iostream>

class S {
public:
S() { a_ = 0; }
~S() { }

int get() { return this->a_; }


void set(int a) { this->a_ = a; }

private:
int a_;
};

int main()
{
S s;

s.set(3);
std::cout << "val " << s.get() << std::endl;

return 0;
}
The most important use case is that when the input variable to the member
function and the class variables / functions are same, then to inhibit confusion
and to assist compiler, this can be used.
Below is the example. Download it here.
#include <iostream>

162
struct S {
public:
S() { a = 0; }
~S() { }

int get() { return a; }


void set(int a) { this->a = a; }

private:
int a;
};

int main()
{
S s;

s.set(3);
std::cout << "val " << s.get() << std::endl;

return 0;
}
Virtual functions

163
namespaces
Namespace is a concept to allocate a particular name for one or more classes or
functions.
The using namespace is used to include a particular namespace without includ-
ing its name directly when calling its classes or functions defined in it.
#include <iostream>

using namespace std;

int main()
{
cout << "test\n";

return 0;
}
Which could’ve been the reference of cout with std::cout without the names-
pace.
We can define our own namespaces as well.
namespace math
{

double square(double number) { return number * number; }

}
Defines the function square in namespace math. Below is one way to use it.
Download it here.
#include <iostream>

namespace math
{

double square(double number) { return number * number; }

using namespace math;


using namespace std;

int main()
{
double n;

164
n = square(3);
cout << "number " << n << endl;

return 0;
}
Another way of using it as follows. Download it here.
#include <iostream>

namespace math
{

double square(double number) { return number * number; }

int main()
{
double n;

n = math::square(3);
std::cout << "number " << n << std::endl;

return 0;
}
In order to access the function, we prefix the members with the namespace
followed by the :: operator.

165
Overloading
Overloading is a concept of having many signatures. In general C++ provides
operator overloading and the function overloading.
Overloading is also refered as compile time polymorphism in C++. This means
that the compiler sees the function / operator prototypes and inserts the corre-
sponding function / operator during the compile time.

166
Operator Overloading
Operators such as +, -, *, >, <, <<, >>, <=, >= and / and many others can be
overloaded.

167
Overloading + operator
Below is one example of overloading the + operator. To overload a class or struct,
two arguments are required.
<type> operator+(<type> s1, <type> s2);
Below is an example,
#include <iostream>

struct A {
int val;
};

A operator+(A a, A b)
{
A r;

r.val = a.val + b.val;

return r;
}

int main()
{
A a { .val = 3 };
A b { .val = 3 };

A r = a + b;

std::cout << "r val " << r.val << std::endl;

return 0;
}
Overloading the + operator in class
Below is one example,
#include <iostream>

struct S {
int a;

S() { a = 0; }
S(int a) { this->a = a; }
~S() { }

168
S operator+(const S &s)
{
S r;

r.a = this->a + s.a;

return r;
}
};

int main()
{
S s(3);
S r(3);
S t;

t = s + r;

std::cout << "t.a: " << t.a << std::endl;

return 0;
}

169
Overloading the ++ operator
#include <iostream>

struct S {
int a;

S() { a = 0; }
S(int a) { this->a = a; }

void operator ++() { a ++; }


};

int main()
{
S s(3);

++s;

std::cout << s.a << std::endl;


}
In the above example, two variables a and b of the structure A are added and
another structure r is returned.

170
Overloading « operator
The << operator can be overloaded as well. Below is one example of overloading
the << operator.
The prototype of << is fixed and must look like this,
std::ostream &operator<<(std::ostream &os, <class name>);
The os argument is the output stream, the cout takes the os so the << operator
must return it back.
Thus the arguments must be filled to the os from the class given in the other
argument.
One of the ways to do is as follows.
class S {
int a;
public:
int get() { return a; }
};

std::ostream &operator<<(std::ostream &os, const S &r)


{
os << r.a; // fill the required data to the output stream

return os; // return the output stream


}
Below is one example,
#include <iostream>

struct S {
int a;
double d;
};

std::ostream &operator<<(std::ostream &os, const S &s)


{
os << "s.a: " << s.a << " " << "s.d: " << s.d;

return os;
}

int main()
{
S s { .a = 3, .d = 3.1 };

171
std::cout << s << std::endl;

return 0;
}
Overloading « operator in class
The operator << cannot be overloaded directly in class. We need to friend the
operator to the class and define it outside.
#include <iostream>

class S {
public:
S(int r) : a(r) { }
~S() { }

friend std::ostream &operator<<(std::ostream &os, const S &r);

private:
int a;
};

std::ostream &operator<<(std::ostream &os, const S &r)


{
os << " r.a: " << r.a;

return os;
}

int main()
{
S s1(3), s2(6);

std::cout << s1 << s2 << std::endl;


}

172
Overloading == operator
#include <iostream>

struct S {
int a;
int f;
};

bool operator==(const S &l, const S &r)


{
return (l.a == r.a) && (l.f == r.f);
}

int main()
{
S s1 = { .a = 3, .f = 1 };
S s2 = { .a = 6, .f = 1 };

if (s1 == s2) {
std::cout << "s1 and s2 are same" << std::endl;
} else {
std::cout << "s1 and s2 are not same" << std::endl;
}

return 0;
}
Overloading == in a Class
#include <iostream>

struct S {
int a;
int f;

bool operator==(const S &other)


{
return (this->a == other.a) &&
(this->f == other.f);
}
};

S s1 = { .a = 3, .f = 4 };
S s2 = { .a = 3, .f = 4 };

int main()

173
{
if (s1 == s2) {
std::cout << "s1 and s2 are same" << std::endl;
} else {
std::cout << "s1 and s2 are not same" << std::endl;
}

return 0;
}

174
Overloading * operator
#include <iostream>

struct S {
int a;
};

S operator*(const S &r1, const S &r2)


{
S r;

r.a = r1.a * r2.a;

return r;
}

int main()
{
S r1, r2, r;

r1.a = 3;
r2.a = 6;

r = r1 * r2;

std::cout << "r: " << r.a << std::endl;

return 0;
}
Overloading * operator in class
#include <iostream>

struct S {
S(int r) {
a = new int;
*a = r;
}
~S() {
if (a != nullptr) {
delete a;
}
}

int *a;

175
int operator*() {
return *a;
}
};

int main()
{
S r1(3), r2(6);

std::cout << "r1: " << *r1 << " " << "r2: " << *r2 << std::endl;

return 0;
}

176
Overloading > operator
#include <iostream>

struct S {
int a;
};

bool operator>(const S &a, const S &l)


{
return a.a > l.a;
}

int main()
{
struct S a = { .a = 3 };
struct S b = { .a = 6 };

std::cout << "a is greater than b? " << (a > b) << std::endl;

return 0;
}
Overloading > operator in class
#include <iostream>

struct S {
int a;

bool operator>(const S &r)


{
return this->a > r.a;
}
};

int main()
{
S s1, s2;

s1.a = 1;
s2.a = 2;

std::cout << "s1 > s2: " << (s1 > s2) << std::endl;
std::cout << "s2 > s1: " << (s2 > s1) << std::endl;

return 0;

177
}

178
Overloading < operator
#include <iostream>

struct S {
int a;
};

bool operator<(const S &a, const S &l)


{
return a.a < l.a;
}

int main()
{
struct S a = { .a = 3 };
struct S b = { .a = 6 };

std::cout << "a is greater than b? " << (a < b) << std::endl;

return 0;
}
Overloading < operator in class
#include <iostream>

struct S {
int a;

bool operator<(const S &r)


{
return this->a < r.a;
}
};

int main()
{
S s1, s2;

s1.a = 3;
s2.a = 6;

std::cout << "s1 < s2: " << (s1 < s2) << std::endl;
std::cout << "s2 < s1: " << (s2 < s1) << std::endl;

return 0;

179
}

180
Overloading >= operator in class
#include <iostream>

struct S {
int a;

bool operator>=(const S &r)


{
return this->a >= r.a;
}
};

int main()
{
S r1, r2, r3;

r1.a = 3;
r2.a = 4;
r3.a = 3;

std::cout << "r1 > r2: " << (r1 >= r2)
<< " r2 > r1: " << (r2 >= r1)
<< " r3 == r1: " << (r3 >= r1) << std::endl;
return 0;
}
Overloading & operator in class
#include <iostream>

struct S {
uint32_t a;

uint32_t operator&(const S &r)


{
return this->a & r.a;
}
};

int main()
{
S r1, r2;

r1.a = 0xFF;
r2.a = 0x40;

181
printf("r1 & r2: 0x%02x\n", r1 & r2);

return 0;
}
Overloading | operator in class
#include <iostream>

struct S {
int a;

uint32_t operator|(const S &r)


{
return this->a | r.a;
}
};

int main()
{
S r1, r2;

r1.a = 0x80;
r2.a = 0x40;

printf("r1 | r2: 0x%02x\n", r1 | r2);

return 0;
}
Overloading new and delete

182
Overloading enum classes
Operator overloading can be used for the enum classes.
#include <iostream>

enum Languages {
Telugu = 0x0001,
Tamil = 0x0002,
English = 0x0004,
Hindi = 0x0008,
};

bool operator<(const Languages &a, int l)


{
return static_cast<int>(a) < l;
}

std::ostream &operator<<(std::ostream &os, const Languages &l)


{
std::string r;

switch (l) {
case Languages::Telugu:
r = "Telugu";
break;
case Languages::Tamil:
r = "Tamil";
break;
case Languages::English:
r = "English";
break;
case Languages::Hindi:
r = "Hindi";
break;
default:
r = "Unknown";
break;
}

os << r;

return os;
}

Languages &operator++(Languages &l)


{

183
switch (l) {
case Languages::Telugu:
return l = Languages::Tamil;
break;
case Languages::Tamil:
return l = Languages::English;
break;
case Languages::Hindi:
return l = Languages::Telugu;
break;
default:
return l = Languages::Telugu;
break;
}
}

int main()
{
Languages l1 = Languages::Telugu;
Languages l2 = Languages::Tamil;

if (l1 == l2) {
std::cout << "Languages are same" << std::endl;
} else {
std::cout << "Languages aren't same" << std::endl;
}

if (l1 < l2) {


std::cout << "l1 is less than l2" << std::endl;
}

if (Languages::Hindi < 100) {


std::cout << "hindi less than 100" << std::endl;
}

std::cout << l1 << std::endl;

++l1;

std::cout << l1 << std::endl;

std::cout << std::is_enum_v<Languages> << std::endl;

return 0;
}

184
Function Overloading
Function overloading allows to have more than one signature to a function. Below
is one example,
int F(int a);
int F(int a, int b);
The function F here is overloaded and have the two signatures. The compiler
finds out which variant of F to be called based on the definition.
#include <iostream>

int F(int a) { return a; }


int F(int a, int b) { return a + b; }

int main()
{
std::cout << "F(3): " << F(3) << std::endl;
std::cout << "F(3, 3): " << F(3, 3) << std::endl;

return 0;
}
In general the arguments to the functions are allowed to be overloaded. However,
the return type of functions are not allowed to be overloaded, For example,
#include <iostream>

int F(int a) { return a; }


void F(int a) { }

int main()
{
F(3);
}
Results in a compiler error.
cpp/overload_f.cc:4:6: error: ambiguating new declaration of ‘void F(int)’
4 | void F(int a) { }
| ˆ
cpp/overload_f.cc:3:5: note: old declaration ‘int F(int)’
3 | int F(int a) { return a; }
| ˆ
Function overloading is really useful when a function wants to do different jobs
keeping the name same but different signature.

185
Exception Handling
#include <iostream>

struct S {
S(int r) {
if (r == 0) {
throw std::runtime_error("r is 0");
}
}

int a;
};

int main()
{
try {
S r1(0);
} catch (std::runtime_error &r) {
std::cout << "exception: " << r.what() << std::endl;
}

S r2(3);

return 0;
}
#include <iostream>
#include <exception>

class S : public std::out_of_range {


public:
S(const char *err) : std::out_of_range(err) { }
~S() { }

int a;

void set(int v) {
if (v > 10) {
throw *this;
}
a = v;
}

virtual const char *what() const noexcept {


return "a value out of range";

186
}
};

int main()
{
S r1("");

try {
r1.set(11);
} catch (S &r) {
std::cout << r.what() << std::endl;
}

return 0;
}

187
noexcept
The noexcept is an operator and also a specifier.
noexcept specifier
The noexcept specifier informs the compiler that the particular function /
constructor / destructor does not produce an exception. It also governs some
underlying rules when it comes to inherited classes.
A noexcept specifier can be attached to the end of the function as:
int f(void) noexcept; // says function does not throw an exception
int g(void); // may throw an exception
A function declared with noexcept specifier but throws, results in the library
calling std::terminate. This makes the exception uncatchable.
An overloaded function can have a different exception specification. For example,
below example is valid.
int f(void) noexcept;
int f(std::string arg);
Below program covers almost all the noexcept cases:
#include <iostream>

void f() noexcept { std::cout << "f called" << std::endl; }


void f(std::string arg) { std::cout << "f called with arg: " << arg << std::endl; }

void g() noexcept { throw std::runtime_error("an exception g()"); }

void p() { throw std::runtime_error("an exception p()"); }

int main()
{
f();
f("test");

try {
p();
} catch (std::exception &e) {
std::cout << "caught the exception from function p(): " << e.what() << std::endl;
}

try {
g();
} catch (...) {
std::cout << "caught the exception from function g()\n";

188
}
}
If a base class contains any of member functions with noexcept specifier then
the derived class must also contain the noexcept specification. Without it, this
results in compiler error.
#include <iostream>

class F {
public:
virtual void f() noexcept = 0;
};

class G : public F {
public:
void f() { std::cout << "in f()" << std::endl; }
};

int main()
{
class G g;

g.f();
}
Results in compiler error,
cpp/noexcept_inh.cc:10:22: error: looser exception specification on overriding virtual funct
10 | void f() { std::cout << "in f()" << std::endl; }
| ˆ
cpp/noexcept_inh.cc:5:30: note: overridden function is ‘virtual void F::f() noexcept’
5 | virtual void f() noexcept = 0;
| ˆ
However, a derived class can specify noexcept specifier although the base class
do not have it.
For example the below program compiles.
#include <iostream>

class F {
public:
virtual void f() = 0;
};

class G : public F {
public:

189
void f() noexcept { std::cout << "in f()" << std::endl; }
};

int main()
{
class G g;

g.f();
}

190
Standard library
Standard library or STL in short is a group of helper function that ease up
programming. Nowadays, they are more focussed towards helping programmers
write OS independent software using C++.

191
std::pair
std::pair defines a pair of values. The usecases are when returning more than
one variable from a function or passing to a function as key-value pair.
std::pair can be used to construct a pair or std::make_pair can be used as
well.
auto p = std::pair<std::string, int>("test", 1); // constructs std::pair type in p
auto p = std::make_pair<std::string, int>("test", 1); // makes a pair of type std::pair
Below is one example of the usecase for std::pair:
#include <iostream>

void f(std::pair<const std::string, int> p)


{
std::cout << "first: " << p.first << " second: " << p.second << std::endl;
}

int main()
{
f(std::make_pair("test", 1));
f(std::make_pair<std::string, int>("test", 2));
f(std::pair("test", 1));
}
std::pair also exposes operators ==, !=, < and >. One can use certain operations
for comparison. Here’s one example: Download it here
#include <iostream>

int main()
{
auto r = std::pair<std::string, int>("test", 1);
auto r1 = std::pair<std::string, int>("test", 1);

std::cout << "r == r1: " << (r == r1) << std::endl;

return 0;
}
std::pair can be implemented with templates. See usecases section for the
implementation of std::pair.

192
std::initializer_list

193
std::bitset

194
File streams
std::istream
std::fstream

195
Arrays
std::array defines an array type. The std::array contains the following
methods:
The std::array template looks as follows,
template <typename T>
std::array<T, n>
Most usual way of declaring an array of integers is,
std::array<int, 10> a;
This is similar to declaring int a[10].
1. at Returns the value at the index.
2. operator[] Used to access the element stored at the index.
3. size Returns the size of the array.
Below is an example of std::array.
#include <iostream>
#include <array>

int main()
{
std::array<int, 10> a;
int i;

for (i = 0; i < a.size(); i ++) {


a[i] = i + i;
}

for (i = 0; i < a.size(); i ++) {


std::cout << "i : " << i << " " << "a[i] : " << a.at(i) << std::endl;
}

return 0;
}

196
Strings
std::string defines a string type. Requires <string> but it seems <iostream>
seem to implictly include it.

S. No Method Description
1 c_str() get the C style string
2 front() access the front character
3 back() access the last character
4 size() length of the string
5 clear() clear the string
6 operator[] array operator to index string elements
7 at get character of the string at a position
8 operator+= strcat for std::string
9 operator= assign the value to the string

Below is one of the example,


#include <iostream>

int main()
{
std::string str = "tests";

std::cout << "str: " << str


<< " c_str(): " << str.c_str()
<< " front(): " << str.front()
<< " back(): " << str.back() << std::endl;

return 0;
}
The + operator on the strings is similar to strcat for C strings.
#include <iostream>
#include <string>

int main()
{
std::string r1 = "r1";
std::string r2 = "r2";
std::string r3 = r1 + r2;

printf("r1: %s r2: %s r3: %s\n", r1.c_str(), r2.c_str(), r3.c_str());

return 0;
}

197
Strings can be iterated with the indices just like the C strings.
#include <iostream>
#include <string>

int main()
{
std::string r = "hello world";

for (uint32_t i = 0; i < r.size(); i ++) {


printf("'%c'", r[i]);
}
printf("\n");

for (uint32_t i = 0; r[i] != '\0'; i ++) {


printf("'%c'", r[i]);
}
printf("\n");

return 0;
}
Clear the strings with clear member function.
#include <iostream>
#include <string>

int main()
{
std::string r = "hello world";

printf("%s\n", r.c_str());

r.clear();

printf("%s\n", r.c_str());

return 0;
}

198
Vectors
std::vector defines a vector type. This is similar to the doubly linked list. It
is declared in <vector>.
Below are the supported methods.

S.No Method Description


1 push_back insert an element to the end
2 at write / access an element at the index
3 begin start of the iteration
4 end end of the iteration
5 cbegin start of constant iteration
6 cend end of constant iteration
7 rbegin start of reverse iteration
8 rend end of reverse iteration
9 erase erase one or more elements
10 insert insert one or more elements
11 resize resize the vector
12 clear erase all the elements from the vector

1. push_back
Push an element at the end of the vector.
std::vector<int> v;

v.push_back(1);
v.push_back(2);
2. at
Get the index pointer.
std::vector<int> v;

v.push_back(1);
v.push_back(2);

for (int i = 0; i < v.size(); i ++) {


std::cout << "val: " << v.at(i) << std::endl;
}
std::vector<int> v;

v.push_back(1);
v.push_back(2);

199
v.resize(3); // resize the elements of the vector
v.at(2) = 3; // set an element at index 2
Without the resize call the at assignment will fail resulting in out of bounds
exception.
3. begin
Get the beginning of the vector.
std::vector<int> v;

v.push_back(1);
v.push_back(2);

auto i = v.begin();

std::cout << "val: " << i << std::endl;


4. end
Get the end of the vector.
std::vector<int> v;

v.push_back(1);
v.push_back(2);

auto t = v.end() - 1; // last element of the vector

std::cout << "val: " << t << std::endl;


std::vector<int> v;

v.push_back(1);
v.push_back(2);

for (auto it : v) {
std::cout << "val: " << it << std::endl;
}

std::vector<int>::iterator it;

// iterate from begin to end


for (it = v.begin(); it != v.end(); it ++) {
std::cout << "val: " << *it << std::endl;
}
5. cbegin
Get the beginning of the vector iteration using const.

200
One could use auto or ::const_iterator.
6. cend
Get the end of the vector iteration using const.
One could use auto or ::const_iterator.
std::vector<int> v;

v.push_back(1);
v.push_back(2);

std::vector<int>::const_iterator it_c;

for (it_c = v.cbegin(); it_c != v.cend(); it_c ++) {


std::cout << "val: " << *it_c << std::endl;
}
7. rbegin
Get the beginning of the reverse iteration of vector.
One could use auto or reverse_iterator.
8. rend
Get the end of the reverse iteration of vector.
One could use auto or reverse_iterator.
std::vector<int> v;

v.push_back(1);
v.push_back(2);

std::vector<int>::reverse_iterator it_r;

for (it_r = v.rbegin(); it_r != v.rend(); it_r ++) {


std::cout << "val: " << *it_r << std::endl;
}
9. erase
Remove one or more elements of the vector.
std::vector<int> v;

v.push_back(1);
v.push_back(2);

v.erase(it.begin()); // removes the head element


v.erase(it.begin(), it.end()); // remove all elements from begin to end

201
10. insert
Insert elements at a certain position.
std::vector<int> v;
std::vector<int> v2;

v.push_back(1);
v.push_back(2);

v2.push_back(3);

v.insert(v.end(), v2.begin(), v2.end()); // insert v2 at the end of v


11. resize
Resize the elements of a vector.
std::vector<int> v;

v.push_back(1);

v.resize(3); // resize vector to 3 elements.


12. clear
Clear the elements from the vector.
std::vector<int> v;

v.push_back(1);
v.push_back(2);

v.clear(); // clear all elements in vector


Vectors can be indexed as well with the [] operator. Below is one example,
std::vector<int> v;

v.push_back(1);
v.push_back(2);

for (auto i = 0; i < v.size(); i ++) {


std::cout << v[i] << std::endl;
}

202
Below is an example of the most of above methods,
#include <iostream>
#include <vector>

struct S {
int age;
std::string name;
};

static void it_const_print(const std::vector<S> &group)


{
std::vector<S>::const_iterator it_c;

std::cout << "normal iterator: [" << group.size() << "] " << std::endl;

for (it_c = group.begin(); it_c != group.end(); it_c ++) {


std::cout << "\t name: " << it_c->name << " "
<< "age: " << it_c->age << std::endl;
}
}

static void range_for_print(const std::vector<S> &group)


{
std::cout << "auto iteration: [" << group.size() << "] " << std::endl;

for (auto it : group) {


std::cout << "\t name: " << it.name << " "
<< "age: " << it.age << std::endl;
}
}

static void reverse_for_print(std::vector<S> &group)


{
std::vector<S>::reverse_iterator it_r;

std::cout << "reverse iterator: [" << group.size() << "] " << std::endl;

for (it_r = group.rbegin(); it_r != group.rend(); it_r ++) {


std::cout << "\t name: " << it_r->name << " "
<< "age: " << it_r->age << std::endl;
}
}

static void at_print(std::vector<S> &group)


{

203
int i;

std::cout << "at: [" << group.size() << "] " << std::endl;

for (i = 0; i < group.size(); i ++) {


std::cout << "\t name: " << group[i].name << " "
<< "age: " << group[i].age << std::endl;
}
}

int main()
{
std::vector<S> group;
std::vector<S> group2;

group.push_back({32, "Dev"});
group.push_back({33, "Seema"});
group.push_back({34, "Cap"});

group2.push_back({50, "Hannibal"});
group2.push_back({30, "BA"});
group2.push_back({30, "Peck"});
group2.push_back({30, "Murdoch"});

it_const_print(group);
range_for_print(group);
reverse_for_print(group);
at_print(group);

std::vector<S>::iterator it;

for (it = group.begin(); it != group.end(); it ++) {


if (it->age == 34) {
break;
}
}

// Erasing
if (it != group.end()) {
group.erase(it);
}

std::cout << "merging with another vector: " << std::endl;


group.insert(group.end(),
group2.begin(), group2.end());

204
group.resize(7);
group.at(6).age = 30;
group.at(6).name = "test";

std::cout << "first " << group.begin()->age << std::endl;


std::cout << "last " << (group.end() - 1)->age << std::endl;

range_for_print(group);

group.clear();
range_for_print(group);

return 0;
}
Vectors can contain any types. For example,
std::vector<int> r1; // vector of integers
std::vector<std::string> r2; // vector of strings

struct S {
int p1;
std::string p2;
};

std::vector<S> r3; // vector of strings


A vector of vectors is also possible as well.
std::vector<std::vector<int>> r1; // 2 dimensional array
Below is an example of the 2D array:
#include <iostream>
#include <vector>

int main()
{
std::vector<std::vector<int>> r;
int i;
int j;

for (i = 0; i < 3; i ++) {


std::vector<int> r1;

for (j = 0; j < 3; j ++) {


r1.push_back(j + 1 + i);
}

205
r.push_back(r1);
}

for (auto it1 : r) {


for (auto it2 : it1) {
printf("%d ", it2);
}
printf("\n");
}

return 0;
}

206
Lists
std::list defines a list type. This is similar to the std::vector. Use <list>
header for std::list.
The std::list provides the following operations.

S.No Name Description


1 front Access first element
2 back Access last element
3 empty Check if the list is empty
4 size Returns the number of elements
5 max_size Get the maximum size
6 clear Clear the list
7 insert Insert one or more elements
8 push_back Add an element at the end
9 pop_back Remove an element from the end
10 push_front Add an element at the front
11 pop_front Remove an element at the front
12 erase Erase elements

1. front
Access the first element in the list.
std::list<int> a = {1, 2, 3};

std::cout << "front: " << a.front() << std::endl;


2. back
Access the last element in the list.
std::list<int> a = {1, 2, 3};

std::cout << "back: " << a.back() << std::endl;


3. empty
Check if the list is empty.
std::list<int> a = {1, 2, 3};
std::list<int> r;

std::cout << "a empty: " << a.empty()


<< " r empty: " << r.empty() << std::endl;
4. size
Get the length of the list.

207
std::list<int> a = {1, 2, 3};

std::cout << "a.size: " << a.size() << std::endl;


5. clear
Clear the elements in list.
std::list<int> a = {1, 2, 3};

a.clear();

std::cout << "a.empty: " << a.empty() << std::endl;


6. insert
Insert the elements in list.
std::list<int> r1 = {1, 2, 3};
std::list<int> r2 = {4, 5, 6};

r2.insert(r2.end(), r1.begin(), r1.end());


7. push_back
Add element at the end of the list.
std::list<int> r = {1, 2, 3};

r.push_back(4);
8. pop_back
Remove element at the end of the list.
std::list<int> r = {1, 2, 3};

r.pop_back();
9. push_front
Add element at the beginning of the list.
std::list<int> r = {1, 2, 3};

r.push_front(4);
10. pop_front
Remove the element at the end of the list.
std::list<int> r = {1, 2, 3};

r.pop_front();

208
11. erase
Erases the elements given ranges.
std::list<int> a = {1, 2, 3, 4, 5, 6};

a.erase(a.begin(), a.end()); // erase all elements in the list


a.erase(a.begin(), std::next(a.begin(), 2)); // erase first 2 elements in the list
12. iterators
The interator, constant_iterator and reverse_iterator apply for lists.
std::list<int> r = {1, 2, 3, 4, 5, 6};
std::list<int>::iterator it;
std::list<int>::reverse_iterator rit;
std::list<int>::constant_iterator it_c;

for (it = r.begin(); it != r.end(); it ++) {


std::cout << *it << std::endl;
}

for (rit = r.rbegin(); rit != r.rend(); rit ++) {


std::cout << *rit << std::endl;
}

for (it_c = r.cbegin(); it_c != r.cend(); it_c ++) {


std::cout << *cit << std::endl;
}

for (auto it_a : r) {


std::cout << it_a << std::endl;
}

209
Below is an example of std::list usage.
#include <iostream>
#include <list>

int main()
{
std::list<int> l;
std::list<int> r;
std::list<int>::iterator it;
std::list<int>::reverse_iterator rit;

l.push_front(3);
l.push_back(1);
l.push_back(6);
l.push_back(2);

/* Normal iterator. */
std::cout << "normal iterator: " << std::endl;
for (it = l.begin(); it != l.end(); it ++) {
std::cout << *it << std::endl;
}

/* Reverse iterator. */
std::cout << "reverse iterator: " << std::endl;
for (rit = l.rbegin(); rit != l.rend(); rit ++) {
std::cout << *rit << std::endl;
}

std::cout << "auto iteartor: " << std::endl;


/* Auto iterator. */
for (auto it : l) {
std::cout << it << std::endl;
}

std::cout << "front: " << l.front() << std::endl;


std::cout << "back: " << l.back() << std::endl;

r = l;

std::cout << "normal iterator: " << std::endl;


for (it = r.begin(); it != r.end(); it ++) {
std::cout << *it << std::endl;
}

r.insert(r.end(), l.begin(), l.end());

210
std::cout << "normal iteration after insert: " << std::endl;
for (it = r.begin(); it != r.end(); it ++) {
std::cout << *it << std::endl;
}

r.erase(r.begin(), r.end());

std::cout << "r size: " << r.size()


<< " r max size: " << r.max_size() << std::endl;
std::cout << "l size: " << l.size()
<< " l max size: " << l.max_size() << std::endl;

return 0;
}

211
Queues
std::queue defines a queue type. The std::queue can take any type. It is
generally identified with templates as,
template <typename T> std::queue<T>
The T argument is a template type, the type is deduced when the std::queue
has been declared.
std::queue<int> i; // a queue of ints

struct P {
int p;
};
std::queue<P> p; // queue of structures (P)
The std::queue provides the following operations.

S.No Name Description


1 front Get the first element of the queue
2 back Get the last element of the queue
3 push Push an element in queue
4 pop Pop an element from the queue
5 size Get the size of the elements
6 empty Check if there are any more elements in queue

Below is one usage of queue with simply integer data type.


#include <iostream>
#include <queue>

int main()
{
std::queue<int> q;
int size;

printf("q empty %d\n", q.empty());

q.push(1);
q.push(2);
q.push(3);
q.push(4);
q.push(5);
q.push(6);

printf("number of elements %lu, queue empty %d\n", q.size(), q.empty());

212
printf("front %d back %d\n", q.front(), q.back());

while (1) {
size = q.size();
if (size <= 0) {
break;
}

int val = q.front();


q.pop();
printf("val : %d\n", val);
}
}
The general usecases of queues are the following:
1. Producer and Consumer data sharing with a queue. Producer adds item
in queue, consumer removes item from the queue.
The above case apply to almost all real world problems involving multi threading.
Multi threading is described below.

213
Sets

214
Dequeue

215
Maps
std::map defines a map type.

216
shared_ptr, unique_ptr
The std::shared_ptr is a scoped allocator defined in C++11. The idea of the
scoped allocation is to free the allocated memory automatically when a count of
all of its references to the allocated memory become 0.
Below is one example usage of std::shared_ptr.
#include <iostream>
#include <memory>

struct S {
int s;
};

int main()
{
std::shared_ptr<S> s;

s = std::shared_ptr<S>(new S);

s->s = 1;

std::cout << "s->s: " << s->s << std::endl;

return 0;
}
The allocator std::make_shared is used that can return a pointer of type
std::shared_ptr.
Below is one example,
#include <iostream>
#include <memory>

struct P {
int val;
};

void print(std::shared_ptr<P> p)
{
struct P *p1;

p1 = p.get();
printf("p->val %d deref->val %d\n", p->val, p1->val);
printf("p.val %d\n", (*p).val);
printf("use_count %ld\n", p.use_count());

217
}

int main()
{
std::shared_ptr<P> ptr;

ptr = std::make_shared<P>();
ptr->val = 4;

print(ptr);

return 0;
}
As you can see we do not call any free or delete. The reason being that when
the program goes out of scope (in this case the main function scope) it is already
freed.
Running the valgrind shows 0 leaks.
==208668== HEAP SUMMARY:
==208668== in use at exit: 0 bytes in 0 blocks
==208668== total heap usage: 4 allocs, 4 frees, 74,780 bytes allocated
==208668==
==208668== All heap blocks were freed -- no leaks are possible
Writing shared_ptr class:
To write the shared_ptr we need to consider the following.
• Referencing counting.
• Allocation and freeing.
The allocation part is not available in this example and leave it upto the caller.
The destructor will perform the freeing, this is exactly one of the purposes of
the shared_ptr feature.
#include <iostream>

template <typename T>


class shared_ptr {
public:
explicit shared_ptr() : count_(0), memory_(nullptr) {
}
explicit shared_ptr(T *t) : count_(0) {
memory_ = t;
count_ ++;
}

shared_ptr operator=(const shared_ptr &s) {

218
this->count_ ++;
std::cout << "called" << std::endl;
return *this;
}

shared_ptr(const shared_ptr &t) {


count_ = 0;
memory_ = nullptr;
memory_ = t.memory_;
count_ = t.count_ + 1;
}

~shared_ptr() {
count_ --;
if (memory_ && (count_ == 0)) {
delete memory_;
}
}
void set(T *memory) {
memory_ = memory;
count_ ++;
}

T *get() { return memory_; }

bool unique() const { return count_ == 1; }

T *operator->() const { return memory_; }


T &operator*() const { return *memory_; }

private:
int count_;
T *memory_;
};

struct S {
int s;
};

void K(shared_ptr<S> s)
{
s->s = 6;
}

int main()
{

219
shared_ptr<S> s = shared_ptr<S>(new S);
shared_ptr<int> s1 = shared_ptr<int>(new int);

s->s = 3;
*s1 = 3;

K(s);

std::cout << "s: " << s->s << " " << "s1: " << *s1 << std::endl;

return 0;
}
Here we write a deref operator -> to return the actual underlying pointer. The
caller still assumes that the shared_ptr is still a wrapper which is true. We
also have written the operator * if in case the called type is a basic type such as
an integer or a floating point.
unique_ptr
The std::unique_ptr is a scoped allocator that is similar to shared_ptr. The
feature is introduced in C++14.
#include <iostream>
#include <memory>

struct P {
int val;
};

void print(std::unique_ptr<P> &p)


{
printf("val %d\n", p->val);
}

int main()
{
std::unique_ptr<P> ptr;

ptr = std::make_unique<P>();
ptr->val = 4;

print(ptr);
return 0;
}

220
File systems

221
Threads
C++ implements abstraction of threads based upon the pthreads. The class
std::thread defines the thread interface. Threads defined in C++11 onwards.
Creating a thread is a simple job of declaring a thread object and passing the
function that serves as a thread function.
Below is one example:
#include <iostream>
#include <thread>

void thread_f()
{
std::cout << "in thread" << std::endl;
}

int main()
{
std::thread t(thread_f);

std::cout << "starting thread" << std::endl;


t.join();
std::cout << "joined thread" << std::endl;
}
Compile the above program with -pthread option. Some compilers may not
require this option.
Below are few methods present in std::thread.

S.No Method Description


1 operator= moves thread object
2 joinable check if the thread is joinable
3 get_id return the id of the thread
4 native_handle return the native handle of the
thread
5 hardware_concurrency return the number of threads
returned by the implementation
6 join wait for the thread to finish
7 detach detach the thread
8 swap swaps two thread objects

Below is one example that show usages of the member functions.


#include <iostream>
#include <thread>

222
void thread_f()
{
std::cout << "in thread" << std::endl;
}

int main()
{
std::thread t(thread_f);

std::cout << "starting thread" << std::endl;


std::cout << "joinable: " << ( t.joinable() ? "yes": "no" ) << std::endl;
std::cout << "thread_id: " << t.get_id() << std::endl;
std::cout << "hardware_concurrency: " <<
t.hardware_concurrency() << std::endl;
t.join();
std::cout << "joined thread" << std::endl;
}
Lets see below example, that creates two threads.
#include <iostream>
#include <thread>

void thread_1()
{
std::cout << "in thread_1" << std::endl;
}

void thread_2()
{
std::cout << "in thread_2" << std::endl;
}

int main()
{
std::thread t1(thread_1);
std::thread t2(thread_2);

std::cout << "waiting for threads" << std::endl;

t1.join();
t2.join();

std::cout << "stop" << std::endl;


}

223
When compiling and running this program results in non-sequential outputs.
For example.
in thread_1
waiting for threads
in thread_2
stop
But the expectation is that the main function messages will appear before the
thread function calls.
In general, when threads are created by the operating system, the execution
totally depends on the scheduler.

224
Mutexes
#include <iostream>
#include <thread>
#include <mutex>

std::mutex lock;
static int count;

void thread_1()
{
while (1) {
std::cout << "in thread_1: waiting for lock" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
lock.lock();
std::cout << "in thread_1: acquired" << std::endl;
count ++;
std::cout << "in thread_1: val " << count << std::endl;
std::cout << "in thread_1: released" << std::endl;
lock.unlock();
}
}

void thread_2()
{
while (1) {
std::cout << "in thread_2: waiting for lock" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
lock.lock();
std::cout << "in thread_2: acquired" << std::endl;
count ++;
std::cout << "in thread_2: val " << count << std::endl;
std::cout << "in thread_2: released" << std::endl;
lock.unlock();
}
}

int main()
{
std::thread t1(thread_1);
std::thread t2(thread_2);

t1.join();
t2.join();
}

225
Conditional Variables
std::condition_variable cond;

226
Derived Classes
C++ allows a class to inherit one or more other classes. This is called inheritance.
struct B {
};

struct D : public B {
};
Here the class D inherits the class B. The public member functions in B are
inherited in D. This means they are callable in D without class object. They
can also be overrriden if needed.
For example,
struct B {
B() { a_ = 3; }
~B() { }
int get() { return a_; }

private:
int a_;
};

struct D : public B {
D() { a_ = 6; }
~D() { }
int get() { return a_; }
int get_b() { return B::get(); } // access B::get() directly

private:
int a_;
};
we access the member of D the following way:

D d;

std::cout << "d.get_b(): " << d.get_b() << std::endl;


D d;

std::cout << "d.get():: " << d.get() << std::endl;


This results in accessing a_ within D.
Following is another way of accessing B::get().

227
D d;

std::cout << "d.B::get(): " << d.B::get() << std::endl;


Below is an example of the inheritance with base and derived classes.
#include <iostream>

struct B {
public:
B() { a_ = 3; }
~B() { };
int get() { return a_; }

private:
int a_;
};

struct D : public B {
public:
D() { a_ = 6; };
~D() { };
int get() { return a_; }
int get_b() { return B::get(); }

private:
int a_;
};

int main()
{
D d;

std::cout << "d.a: " << d.get()


<< " d.B::a: " << d.B::get()
<< " b.a: " << d.get_b() << std::endl;

return 0;
}

228
Abstract Classes
class abstract_class {
public:
virtual function_return function_prototype(parameters..) = 0;
}
Below is one example,
class S {
public:
virtual int get() = 0;
};
The implementation inherits the abstract class. For example,
class R : public S {
public:
R() { a_ = 3; }
~R() { }

int get() { return a_; }

private:
int a_;
};
Every definition of the virtual function present in the class S must be implemented
by the derived class.
If not implemented, this generally results in the compilation failure.
Below is one simple example,
#include <iostream>

class S {
public:
virtual int get() = 0;
};

class R: public S { // the public members of S are inherited


public:
R() { a_ = 3; }
~R() { }

int get() { return a_; }

private:
int a_;

229
};

int main()
{
R r;

std::cout << "R: " << r.get() << std::endl;

return 0;
}
Though the above program instantiates R directly, it may not be very useful to
instantiate R. In general abstract classes can be instantiated via other means.
See Design patterns for Factory method.
Much of the uses of inheritance lie in the designs and abstractions. They can be
used to represent the designs in the form of C++.

230
Templates
Templates allow to write software generically. Below is an example of a template.
template <typename T>
class calculator {
};
Where T is the type. The instantiation of the class object for this would be,
class calculator<int> cal;
Defines the class calculator as a template.
There can be more than one template types.
For example, the following is valid.
template <typename T, typename R, typename P>
class calculator {
};
The member functions of the class can be written as follows.
template <typename T>
class calculator {
public:
T add(T a, T b);
T sub(T a, T b);
T mul(T a, T b);
T div(T a, T b);
T mod(T a, T b);
};
The member functions describe that the inputs to the member functions are all
of type T and returns type T.
For example,
calculator<int> cal;
declaration of object means that the member functions also are of same type.
For example, passing other type instead of the same results in compiler error.
We can write the calculator program that is written in C with macros, in C++
with templates as follows:
#include <iostream>

template <typename T>


class calculator {
public:
T add(T a, T b) { return a + b; }

231
T sub(T a, T b) { return a - b; }
T mul(T a, T b) { return a * b; }
T div(T a, T b) { return a / b; }
T mod(T a, T b) { return a % b; }
};

int main()
{
calculator<int> cal;

std::cout << "Add: " << cal.add(3, 3) << std::endl;


std::cout << "Sub: " << cal.sub(3, 3) << std::endl;
std::cout << "Mul: " << cal.mul(3, 3) << std::endl;
std::cout << "Div: " << cal.div(3, 3) << std::endl;
std::cout << "Mod: " << cal.mod(3, 3) << std::endl;

return 0;
}
Here we used calculator<int> for the cal object. This means all the oper-
ations / member functions of the calculator will accept integers. If we used
calculator<double> it would be the double that is being used in all the opera-
tions.
For example, there can be a chance that string could’ve been used such as
calculator<std::string> cal. In this case the compilation results in failure
because the arguments given are integers. Certain overloaded string operations
such as +, - may work, but the other operations which does not have the
overloaded types will result in compilation failure.
A normal function can be overloaded with the templates such as the following
example,
#include <iostream>

template <typename T>


void print(T val)
{
std::cout << "template: val: " << val << std::endl;
}

void print(int v)
{
std::cout << "int: val: " << v << std::endl;
}

int main()
{

232
print(3);
print<std::string>("hello");
print<int>(3);

return 0;
}
Here the print(3) is called directly which by the explicit function and variable
declaration the print(int v) gets called. When the print is called with < and
>, the templatized version gets invoked.
If in case print(int v) is not available, the first call to print(3) actually
results as an implicit call to print<int>(3). The compiler deduces the type
implicitly.
Template with default type
Templates can have default type representing the type to use if in case not given.
For example,
template <typename T, typename R = int>
void f(T a, R b);
Shows the typename R defaults to int. So when the template parameter is not
given for identification, the default is used instead.
For example,
f<double>(1.1, 1); // uses <double, int> instead
f<double, double>(1.1, 2.2): // explicitly specified double, so default does not matter here
Below is one example,
#include <iostream>

template <typename T, typename R = int>


void f(T a, R b)
{
std::cout << "a: " << a << " "
<< "b: " << b << std::endl;
}

int main()
{
f<double>(1.1, 1);
f<double, double>(1.1, 2.2);

return 0;
}
Usecase 1: Implementing std::array

233
Templates can as well have a normal types such as int, float etc.
For example,
template<typename T, int n>
class p {
};

p<int, 10> a;
This means that the passed number 10 is a constant through out the object
lifecycle.
For example, these can be used to define static array.
template<typename T, int n>
class p {
T array_[n]; // define an array of constant size n
};

p<int, 10> a; // here we defined array size as 10


This approach can be used as a method for writing implementation of
std::array.
Array supposed to have the following functionalities.

S.No Name Description


1 operator[] Indexing into an array
2 at Access an element at given position
3 size Get the size of the array
4 clear Clear the array with a given value

Below is an implementation.
#include <iostream>

template <typename T, int n>


class array {
public:
explicit array() = default;
~array() = default;

T &operator[](int index) { return array_[index]; }

T at(int index) { return array_[index]; }

234
int size() { return n; }

void clear(const T val) {


for (auto i = 0; i < n; i ++) {
array_[i] = val;
}
}
private:
T array_[n];
};

int main()
{
array<int, 10> a;

a[1] = 4;

std::cout << "array " << a[1] << std::endl;


}

235
Template overloading
Template overloading is possible just like the function overloading that is dis-
cussed.
Below is one example of template overloading.
template <typename T>
int print(T &val);

template <typename T, typename R>


int print(T &val, R &val2);

int print(int val);


Below is an example usage of the overloaded templates.
#include <iostream>

template <typename T>


int print(T val)
{
std::cout << "val: " << val << std::endl;

return 0;
}

template <typename T, typename R>


int print(T val1, R val2)
{
std::cout << "val1: " << val1 << " " << "val2: " << val2 << std::endl;

return 0;
}

int print(int val)


{
std::cout << "normal val: " << val << std::endl;

return 0;
}

int main()
{
print(3);
print(3, 6);
print(3u);
print<int>(3);

236
}
There are 2 types of calling conventions to invoke the template <typename
T>print.
One is to explicitly use the <int> to indicate the compiler that call the template
version.
Another is to append the type to the integer 3 to inform that its not a signed
integer which inturn inform the compiler to invoke the template version.

237
Appendix B

238
Use cases

239
Useful functions 1. Template min
template <typename T>
T min(T a, T b)
{
return a < b ? a: b;
}
2. Template max
template <typename T>
T max(T a, T b)
{
return a > b ? a: b;
}

240
Safe Queue
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <queue>

template <typename T>


class safe_que {
public:
safe_que(const safe_que &) = delete;
const safe_que &operator=(const safe_que &) = delete;
safe_que(const safe_que &&) = delete;
const safe_que &&operator=(const safe_que &&) = delete;

/**
* @brief - get an instance.
*/
static safe_que *instance() {
static safe_que q;
return &q;
}
~safe_que() { }

/**
* @brief - add an element to the safe queue.
*/
void add(T &elem) {
std::unique_lock<std::mutex> lock(lock_);
items_.push(elem);
cond_.notify_all();
}

/**
* @brief - get the element from the queue.
*/
void get(T &val) {
std::unique_lock<std::mutex> lock(lock_);
cond_.wait(lock);
val = items_.front();
items_.pop();
}

private:
explicit safe_que () { }

241
std::queue<T> items_;
std::mutex lock_;
std::condition_variable cond_;
};

void thread_f()
{
safe_que<int> *q = safe_que<int>::instance();

while (1) {
int data;

/* Get the element from the queue. */


q->get(data);
printf("data %d\n", data);
}
}

int main()
{
safe_que<int> *q = safe_que<int>::instance();
std::thread t(thread_f);
int count = 0;

/* Produce data every 100 msec. */


while (1) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
count ++;

q->add(count);
}
}

242
Usecase 2: Implementing LRU cache
LRU is called as Least Recently Used. It is a cache mechanism that allows to
remove the least used data members. The below algorithm describes based on
the assumption that the cache is finite.
For example consider the following cache.
| A | B | C | D | E |
Each of these elements are associated with a sequence number.
| A |1| B |2| C |3| D |4| | E |5|
This can be represented in structural format as,
template <typename T>
struct lru_cache_item {
T item;
uint32_t seq_no;
};
The elements are structured as,
A,1
B,2
C,3
D,4
E,5
If F needs to be added to the above list, the element A will be evicted because
the sequence number following A is the least.
So the resulting list becomes,
F,6
B,2
C,3
D,4
E,5
If item B wants to be updated (or more specifically location where the element B
is stored needs to be updated), the sequence number belong to it will also be
updated. For example the resulting list becomes,
F,6
B,7
C,3
D,4
E,5
Now, if any new element needs to be added to the list the next element to be
removed is C.

243
#include <iostream>

/**
* Defines a cache line item
*/
template <typename T>
struct lru_cache_items {
T val;
bool is_avail;
uint32_t seq_no;
};

/**
* Template of the lru_cache.
*/
template <typename T, int n>
class lru_cache {
public:
explicit lru_cache()
{
index_ = 0;
seq_no_ = 0;

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


items_[i].is_avail = false;
items_[i].seq_no = 0;
}
}
~lru_cache() { }

lru_cache &push(T &val)


{
/* Add to the cache for the first n items */
if (index_ < n) {
seq_no_ ++;

items_[index_].val = val;
items_[index_].is_avail = true;
items_[index_].seq_no = seq_no_;

index_ ++;
} else {
/**
* Try evicting an item if the item is old.
*
* if the particular cache line's sequence number is oldest, evict it and up

244
*/

/* Find least recently used. */


int i;
int index = -1;
uint32_t least_val = seq_no_;

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


if (items_[i].seq_no < least_val) {
least_val = items_[i].seq_no;
index = i;
}
}

if (index != -1) {
seq_no_ ++;

items_[index].val = val;
items_[index].is_avail = true;
items_[index].seq_no = seq_no_;
}
}

return *this;
}

void update(T &val, int index)


{
/* Update always involve updating sequence number, a way to tell that the
* cache line is hot.
*/
seq_no_ ++;

items_[index].val = val;
items_[index].is_avail = true;
items_[index].seq_no = seq_no_;
}

int get_index(T &val)


{
int i;

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


if (items_[i].val == val) {
break;
}

245
}

return i == n ? -1 : i;
}

T &get_val(int index)
{
return items_[index].val;
}

private:
lru_cache_items<T> items_[n];
uint32_t seq_no_;
uint32_t index_;
};

int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int p1 = 11;
int p2 = 12;
int u1 = 13;
int u2 = 14;
int i;
lru_cache<int, 10> lru;

for (i = 0; i < 10; i ++) {


lru.push(a[i]);
}

lru.update(a[0], 0);
lru.update(a[1], 1);
lru.push(p1);
lru.push(p2);

for (i = 0; i < 10; i ++) {


std::cout << "val: " << lru.get_val(i) << std::endl;
}
}

246
Thread Pool
The thread pool is a group of threads that work on specific jobs that are queued
to them.
The threads are created early in the startup. All the threads could listen on
a single queue or on a multi queue. The main thread assigns the tasks to the
threads and each thread execute these tasks that are queued to them.
So idea of having separate queue for each thread generally makes sense to avoid
any possible starvation when each thread pulls the task from the queue.
Thus we need to define a context for each thread. It may look something like
the below.
struct thread_context {
uint32_t id_; // identifier for thread
std::shared_ptr<std::thread> t_; // actual thread pointer
std::mutex lock_; // lock for the queue
std::condition_variable cond_; // condition variable for the queue
std::queue<work_fn> work_list_; // list of function callbacks
int queue_length_; // length of the queue
bool queued_; // signals if work is queued
bool signalled_; // signal to quit the thread

// constructor
explicit thread_context(uint32_t id);

// queue the work


void queue(work_fn fn);

// get thread id
uint32_t get_id() { return id_; }

// join the thread


void join() { t_->join(); }

// get the queue size


int get_queue_size() { return queue_size_; }

// signal the thread


void signal();
// destructor
~thread_context();
void worker_thread(); // the thread function
};
The worker function callback would look as follows,

247
typedef std::function<void(void)> work_fn;
The id_ is a thread identifier.
We took std::thread as shared_ptr so we can instantiate it later during the
allocation.
The lock_ and cond_ variables are used to sequentialize access to the queue
work_list_.
The queue holds the list of function callbacks that are to be executed.
We use queue_length_ to determine the fairness and optimize the time it takes
for a work callback to execute on a thread.
The variable queued_ is used for synchronization between the main thread and
the worker threads.
The variable signalled_ is used to inform the thread when to quit.
The worker_thread is the worker thread function that executes the queued work
functions.
The constructor thread_context would create the thread as follows.
thread_context::thread_context(uint32_t id)
{
t_ = std::make_shared<std::thread>(&thread_context::worker_thread, this);
}
Now, the main thread or the caller library functions would have to store each
thread context.
class thread_pool {
public:
// initialize thread pool with n_threads
explicit thread_pool(int n_threads);
~thread_pool();

// queue work to the thread pool


void queue(work_fn fn);

// run the thread pool wait for them to finish execution


void run();

// signal the threads to stop


void signal();

private:
// store number of threads
int n_threads_;

248
// vector of threads
std::vector<std::shared_ptr<thread_context>> tc_list_;
};
The users of the thread pool can then simply do,
thread_pool t(4);

t.queue(&work_1);

t.queue(&work_2);

..

t.run();
The method thread_pool::run is simply used to wait for all threads to finish.
This call will return if the threads stop executing. So at some point in time such
as program stop, we call thread_pool::signal to signal the threads to stop.
The job queueing is takes a very generic function that accepts and returns no
parameter.
This is of type std::function so one can use straight functions or use std::bind
to create a callback.
For example, to make a private member function of the below class S to be
called, one can make a callback and pass it to the thread_pool::queue.
struct S {
public:
explicit S() { }
~S() { }

void register_work();

private:
void work()
{
std::cout << "work function" << std::endl;
}
};

void S::register_work()
{
auto callback = std::bind(&S::work, this);
thread_pool tp(4);

tp.queue(callback);

249
}

250
Below is the thread pool implementation.
/**
* @brief - Thread pool implementation within 200 lines.
*
* @author - Devendra Naga (github.com/devendranaga/)
*
* @copyright - 2023-present.
* @license - GPLv2
*/
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
#include <thread>
#include <mutex>
#include <condition_variable>

typedef std::function<void(void)> work_fn;

class TD {
public:
explicit TD(uint32_t id) :
id_(id),
queue_size_(0),
queued_(false),
signalled_(false)
{
t_ = std::make_shared<std::thread>(&TD::thread_fn, this);
}
~TD () { }

void queue(work_fn fn)


{
{
std::unique_lock<std::mutex> l(lock_);
queued_ = true;
queue_size_ ++;
work_list_.push(fn);
cond_.notify_one();
}
}

uint32_t get_id() { return id_; }

void join() { t_->join(); }

251
int get_queue_size() { return queue_size_; }

void signal()
{
std::unique_lock<std::mutex> l(lock_);
signalled_ = true;
cond_.notify_one();
}

private:
uint32_t id_;
int queue_size_;
bool queued_;
bool signalled_;
std::queue<work_fn> work_list_;
std::shared_ptr<std::thread> t_;
std::mutex lock_;
std::condition_variable cond_;

void thread_fn()
{
int queue_size = 0;
work_fn fn = nullptr;

while (1) {
{
fn = nullptr;
std::unique_lock<std::mutex> l(lock_);
if (queue_size == 0) {
cond_.wait(l, [this] { return (queued_ == true) ||
(signalled_ == true); });
if (signalled_) {
break;
}
queued_ = false;
}
queue_size = work_list_.size();
if (queue_size > 0) {
fn = work_list_.front();
work_list_.pop();

printf("remaining items in thread %d %d\n",


id_, queue_size_);

252
}
if (fn) {
fn();
queue_size_ --;
}
}
}
};

class TP {
public:
explicit TP(int n_threads) : n_threads_(n_threads)
{
int i;

for (i = 0; i < n_threads; i ++) {


std::shared_ptr<TD> td;

td = std::make_shared<TD>(i);
td_list_.push_back(td);
}
}

void queue(work_fn fn)


{
int lowest = td_list_.begin()->get()->get_queue_size();
std::vector<std::shared_ptr<TD>>::iterator it;
std::vector<std::shared_ptr<TD>>::iterator lowest_it =
td_list_.end();

for (it = td_list_.begin(); it != td_list_.end(); it ++) {


int q_size = it->get()->get_queue_size();
if (q_size <= lowest) {
lowest = q_size;
lowest_it = it;
}
}

printf("chose lowest id [%d] queue [%d]\n",


lowest_it->get()->get_id(),
lowest_it->get()->get_queue_size());
if (lowest != -1) {
lowest_it->get()->queue(fn);
}
}

253
void run()
{
for (auto it : td_list_) {
it.get()->join();
}
}

void signal()
{
for (auto it : td_list_) {
it.get()->signal();
}
}

private:
int n_threads_;
std::vector<std::shared_ptr<TD>> td_list_;
};

static int count;


std::mutex lock;

void work_1()
{
fprintf(stderr, "executing infinite loop\n");
while (1) {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::unique_lock<std::mutex> l(lock);
fprintf(stderr, "work_1: counter: %d\n", count);
if (count > 1) {
break;
}
}
}
}

void work_2()
{
std::unique_lock<std::mutex> l(lock);
fprintf(stderr, "work_2: counter: %d\n", count);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
count ++;
}

void work_3()

254
{
std::unique_lock<std::mutex> l(lock);
fprintf(stderr, "work_3: counter: %d\n", count);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
count ++;
}

int main()
{
TP t(4);
int i;

t.queue(&work_1);

for (i = 0; i < 10; i ++) {


t.queue(&work_3);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
t.queue(&work_2);
}

t.signal();

t.run();
}

255
Usecase: Implementing std::pair
Below is an implementation of std::pair.
#include <iostream>

template <typename T1, typename T2>


struct pair {
T1 first;
T2 second;

explicit pair(const T1 &t1, const T2 &t2) : first(t1), second(t2) { }


~pair() { }

bool operator==(const pair &r)


{
return (this->first == r.first) && (this->second == r.second);
}
bool operator!=(const pair &r)
{
return !operator==(r);
}
bool operator>(const pair &r)
{
return (this->first > r.first) && (this->second > r.second);
}
bool operator<(const pair &r)
{
return !operator>(r);
}
};

template <typename T1, typename T2>


pair<T1, T2> make_pair(const T1 t1, const T2 t2)
{
pair<T1, T2> p(t1, t2);

return p;
}

int main()
{
pair<std::string, int> p = make_pair<std::string, int>("test", 1);
pair<std::string, int> p1 = make_pair<std::string, int>("test", 1);

std::cout << "first: " << p.first << " second: " << p.second << std::endl;
std::cout << "p == p1: " << (p == p1) << std::endl;

256
std::cout << "p != p1: " << (p != p1) << std::endl;

return 0;
}

257
Event Driven System

Design Patterns

258
Factory Design pattern

259
Singleton Design pattern
The singleton pattern is used when an object is being used by many other classes.
One way to do is to instantiate it statically and return that instance.
Since its been used by many other classes, the instantiation happens statically
within the class itself. For this one generally defines instance member function
that returns the statically declared class object. The constructor is hidden to
prevent any more instantiations by the class declarations.
An example singleton class looks as follows.
class singleton {
public:
static singleton *instance() {
static singleton s;
return &s;
}
~singleton() = default;
singleton(const singleton &) = delete;
const singleton &operator=(const singleton &) = delete;
singleton(const singleton &&) = delete;
const singleton &&operator=(const singleton &&) = delete;

int member(...);

private:
explicit singleton();
}
We delete the copy and move constructors so that only one instance that is
created during the call to the static member function instance is the only
instance that is available.

260
usecase.1: Logging utility
Singleton can be used when writing a logging utility that logs the message
/ debug message to something like console or to a file, but does not require
instantiation everytime when we want to use the object.
An example of it looks as follows:
class log {
public:
static log *instance() {
static log l;
return &l;
}
~log() { }
log(const log &) = delete;
const log &operator=(const log &) = delete;
log(const log &&) = delete;
const log &&operator=(const log &&) = delete;

int info(const char *msg, ...);


int verbose(const char *msg, ...);
int debug(const char *msg, ...);
int warn(const char *msg, ...);
int error(const char *msg, ...);
int fatal(const char *msg, ...);
private:
explicit log() { }
};
The above class is a singleton that has many member functions for logging such
as,
1. info
2. verbose
3. debug
4. warning
5. error
6. fatal
The member functions of this singleton can be accessed from anywhere as long
as they include the header file that this class belongs.
The call can be simply made as :
log *l = log::instance();

l->info("info message\n");
or

261
log::instance()->info("info message\n");
usecase.2: Datastore
Data store is another use of singleton class. Lets see the below class:
class key_val_datastore {
public:
static key_val_datastore *instance() {
static key_val_datastore ds;
return &ds;
}
~key_val_datastore() { }
key_val_datastore(const key_val_datastore &) = delete;
const key_val_datastore &operator=(const key_val_datastore &) = delete;
key_val_datastore(const key_val_datastore &&) = delete;
const key_val_datastore &&operator=(const key_val_datastore &&) = delete;

int write(uint32_t val);


int write(std::string val);
int read(uint32_t &val);
int read(std::string &val);
private:
explicit key_val_datastore() { }
};
Just as in the usecase 1, the data store can be read and written with the member
functions.
Ofcourse there will be parallel accesses, which can be sequentialized with the
use of mutexes.

262
Builder Design pattern
Builder pattern generally involve a class object being returned by every call to
the member function of the returned class object. This allows repeated calls to
build or initialize certain contents within the class object.
For example consider building a server.
1. components
2. assemble of components - this can be done all at once or if the assembly
is different, it can be done one member function at a time specific to the
component.
An example of such a class that have the properties of a builder is as follows.
class pc_assembly {
public:
explicit pc_assembly() { }
~pc_assembly() { }

...
pc_assembly &acquire_components() {
// .. manufacture components..
return *this;
}
pc_assembly &assemble_components() {
// .. manufacture components..
return *this;
}
}
The calling now becomes,
pc_assembly p;

p.acquire_components().assemble_components();
As shown above, such pattern can be repeated many times over until all the
items are built.
Below is an example.
#include <iostream>

class pc_builder {
public:
explicit pc_builder() { }
~pc_builder() { }

pc_builder &assemble_cpu() {

263
std::cout << "cpu aseembling done" << std::endl;
cpu_ = true;
return *this;
}

pc_builder &assemble_gpu() {
std::cout << "gpu assembling done" << std::endl;
gpu_ = true;
return *this;
}

pc_builder &assemble_fan() {
std::cout << "fan assemble done" << std::endl;
fan_ = true;
return *this;
}

pc_builder &assemble_ram() {
std::cout << "ram assemble done" << std::endl;
ram_ = true;
return *this;
}

pc_builder &assemble_components() {
std::cout << "aseembled rest of the components" << std::endl;
components_ = true;
return *this;
}

bool power_on() {
std::cout << "power on ok" << std::endl;
return true;
}

private:
bool cpu_;
bool gpu_;
bool fan_;
bool ram_;
bool components_;
};

int main()
{
pc_builder b;

264
b.assemble_cpu()
.assemble_gpu()
.assemble_fan()
.assemble_ram()
.assemble_components();

b.power_on();
}

265
Adapter Design pattern

266
Appendix C

267
Code organization for software development
When writing software in C and C++ it is important to understand the following
concepts. These are generally applicable for the Building Large software section
as well.
1. Splitting the functionality into many files
Sometimes a core functionality may have many parts. For example consider
writing a GUI application that shows packet data in realtime (such as wireshark).
This require the continuous data input from a backend, there is a communication
between backend to the GUI frontend, there is also display style.
So each of this functionality could be split into many files.
1. backend.c -> implements data read and pass to proxy
2. proxy.c -> implements proxying between backend and GUI front end.
3. display.c -> implements display of the data with selected style.
So generally this technique allows the programmer to identify which place to
look when there is an issue with something such as specifically backend.
This methodology generally defined at the architecture level where in the archi-
tecture dictates the blocks required to meet the desired result. Each of these
blocks will become the .c and .h files.
2. Write data structures always in a header file.
3. Write function prototypes always in a header file.

268
Building large software
1. Write small functions that can be seen within 80 rows.
Debugging time. It generally is the case when a large software is implemented,
debugging and fixing the software becomes more complex. With the years to
maintain it, it is always not possible to remember the way a particular feature is
written in a way that solved the problem. Even the commenting does not solve
in many cases over years.
The importance is to write software in a better way avoiding the writing of
complex functions. The reason of 80 rows being is that the function can be fit
in 80 lines.
2. Write meaningful function names and variable names.
3. Follow programming style of the code base.
4. Split functionality into many files (C and H).
5. Reduce the usage of global and static variables.
6. Reduce the usage of stack when not needed.
7. Allocate and free - follow the sequence.
8. Use scoped allocators where possible.
9. Pre-Allocate everything if possible.
10. Avoid complex use of double pointers.
11. Try doing Context based programming.
12. Use const where possible.
13. Do not write overly long macros. Change them to functions.
14. Do not inline every function, almost never inline unless required.
15. Use indexing / Lookup table technique where possible.
See structures-> Lookup table with structures section. The most common
approach of accessing or finding an element in a list of elements is to index
directly into the array.
This is generally used in the hot path of a software program to avoid looping in
a list and use indexing to directly access the element at the index. This is one
of the optimization techniques.
16. Not passing array size as arguments
In general when passing an array to a function, size of the array must be passed
as an argument.
int f(int *array);

269
The caller wouldn’t know how long it has to iterate over the elements. Calling
sizeof(array) would only result in calculating the size of a pointer. Even if
the prototype is the following, the sizeof would result in calculating the size of
a pointer.
int f(int array[])
So the correct prototype becomes,
int f(int *array, int array_size);
17. Every Pointer is not required to be validated

270
creating libraries
When writing software for a large scale project, sometimes some of the utility
functions or common routines need to be called often. Sometimes, many appli-
cation tend to replicate these and roll out their own implementation of these
functions. This is generally has the following problems:
1. Code bloat with repeat of many functions doing same thing.
2. If there is a problem in one function, the other callers that are not using
this function will not get a benefit when the function’s problem is solved.
3. Increased program size.
4. Increased development times.
These are some of the reasons why libraries concept is introduced.
The Compiler provides a way to generate libraries out of a group of C or C++
source files. They are group of object files and the functions they offer, contain
prototypes in the respective .h files.
These libraries can then be linked with the other object files during the linkage
time to create the final binary / library.
There are static and dynamic libraries.
Static libraries are the ones that when linked, copies the function directly to the
target binary. This increases the size of the binary considerably.
Dynamic libraries on the other hand, keeps a reference of the function in the
target binary. This may not increase the size of the binary. However, during
the runtime, the loader sees the reference of the function and loads the function
when it gets called. This adds additional runtime over head.
Check cmake section about creating static and dynamic libraries.

271
creating binaries

272
cmake
cmake is a scripting language that can be used to create what are known as
CMake Files. Each of these can be used to compile the group of source files to
generate target libraries or binaries.

273
1. Creating a basic CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(Example)

set(SRC
./file_1.c
./file_2.c)

add_executable(file_ops ${SRC})
The above cmake file is created to make an executable file called file_ops.
The source files file_1.c and file_2.c are part of the SRC definition.
The call add_executable instructs to create the binary file_ops with the given
files identified by SRC.
The items project and cmake_minimum_required are not mandatory but cmake
warns when we do not keep them. They generally help you guide about what
project you are building to and the type of cmake features you are using.
Below is the command to generate the target binary,
mkdir build/
cd build/
cmake ..
make
cmake build generates a lot of intermediate artifacts and dirties the directory.
So, better create a directory called build and run cmake from there.
If you do not have cmake installed, on Ubuntu run the following command:
sudo apt install make cmake

274
2. Creating library
cmake_minimum_required(VERSION 3.9)
project(Example_Lib)

set(LIB_SRC
./file_1.c)

add_library(file ${LIB_SRC})
The add_library instructs the cmake to create a static library libfile.a.
Below example creates a shared library.
cmake_minimum_required(VERSION 3.9)
project(Example_Lib)

set(LIB_SRC
./file_1.c)

add_library(file SHARED ${LIB_SRC})


The SHARED specifier creates a .so file called libfile.so. Without mentioning
SHARED, the add_library generates a .a file by default.

275
3. Linking with libraries
The target_link_libraries is used to link the library to the target binary.
Below is one example:
cmake_minimum_required(VERSION 3.9)
project(Example_Bin)

set(SRC
./file.c)

set(LIB_SRC
./file_1.c)

add_library(file ${LIB_SRC})
add_executable(file_ops ${SRC})
target_link_libraries(file_ops file)

276
4. Adding CFLAGS and CPPFLAGS
set(CMAKE_C_FLAGS "-Wall -Werror")
set(CMAKE_CXX_FLAGS "-Wall -Werror")
The flag CMAKE_C_FLAGS and CMAKE_CXX_FLAGS takes all the compiler option
that can be passed to gcc/g++ or clang.

277
5. adding C++ standard
set(CMAKE_CXX_STANADARD "11")
The macro CMAKE_CXX_STANDARD sets the C++ standard when compiling.

278
Data Structures

279
Linked Lists
Linked list is a chain of elements terminated with a NULL pointer.
Each item in chain is called the node. Each node contains data and a pointer to
the next item in the list. The last node pointer will be NULL.
Linked list structure looks as follows:
|--------| |--------| |--------|
| item 1 |--->| item 2 |--->| item 3 |--> .... -> NULL
|--------| |--------| |--------|
The list always ends with a NULL pointer.
The linked list structure looks as follows.
struct linked_list {
void *elem;
struct linked_list *next;
}
the data pointer holds the data and the next pointer links to the next element
in the list.
Below are some of the general operations on the linked list.

S.No Name Description


1 add Add an element to the list at the end
2 add_head Add an element to the list at the head
3 delete delete an element from the list
4 find find an element in the list
5 count count the number of elements in the
list
6 for_each iterate through each eement in the list
7 print print all the elements of the list
8 clean clean all the linked list and free up the
memory allocated

Lets define two global variables head and tail.


static struct linked_list *head;
static struct linked_list *tail;
We use two pointers head and tail. The head is used to iterate over each
element from the beginning and tail is used to add element at the end.

280
1. add
Adding an element can be as simple as adding an element at the end.
If there are no elements in the list, add the element at the head. If there are
elements in the list, add the element after tail. When ever an element is added
point the tail to the last element.
int add(void *data)
{
struct linked_list *node;

node = calloc(1, sizeof(struct linked_list));


if (!node) {
return -1;
}

/* Assign the element */


node->elem = data;

if (!head) {
head = node;
tail = node;
} else {
tail->next = node;
tail = node;
}

return 0;
}
Iterating over the elements is simple as using a while or do..while or for loop.

281
2. add_head
Adding an element at the head is done as follows:
1. Set node->next to head.
2. Make head point to node.
int add_head(void *data)
{
struct linked_list *node;

node = calloc(1, sizeof(struct linked_list));


if (!node) {
return -1;
}

node->elem = data;

if (!head) {
head = node;
tail = node;
} else {
node->next = head;
head = node;
}

return 0;
}

282
3. delete
Deleting an element from list need be considered two possibilities.
1. If the given elem is at the head.
1. Save the head pointer at node.
2. Move the head to the next element.
3. Free up the saved node.
2. If the given elem is somewhere in the middle.
1. Set prev and node to head.
2. Iterate over each element in the link.
3. If the elem pointers are matched skip the node.
4. Point the prev->next to node->next, moving the link.
5. Free up the node.
There is a special case here if elem to delete is in the middle, the node can
potentially be the tail pointer. Correct the tail pointer to old element prev.
int delete(void *elem)
{
struct linked_list *node;
struct linked_list *prev;

node = head;
prev = node;

if (head->elem == elem) {
head = head->next;
free(node);
return 0;
} else {
while (node != NULL) {
if (node->elem == elem) {
prev->next = node->next;
if (node == tail) {
tail = prev;
}
free(node);
return 0;
}
prev = node;
node = node->next;
}
}

return -1;
}

283
4. find
The find iterates over each element and compares the elem pointer with the
given pointer and returns true if both are same.
bool find(void *elem)
{
struct linked_list *node;

for (node = head; node != NULL; node = node->next) {


if (node->elem == elem) {
return true;
}
}

return false;
}
There is another way to perform find. This is to call the custom callback that
returns true if matched and false if not. Below is an example:
bool find(bool (*callback)(void *elem))
{
struct linked_list *node;
bool found = false;

for (node = head; node != NULL; node = node->next) {


found = callback(node->elem);
if (found == true) {
break;
}
}

return found;
}

284
5. count
The count iterates over each link and increments the counter.
int count()
{
struct linked_list *node;
int n = 0;

for (node = head; node != NULL; node = node->next) {


n ++;
}

return n;
}
The count can be a local variable and incremented during add and delete
operations. It can then be returned directly in the count function.

285
6. for_each
The for_each iterates over each link and calls the callback. The caller must
pass the callback and the caller will get the element as the data.
void for_each(void (*callback)(void *elem))
{
struct linked_list *node;

for (node = head; node != NULL; node = node->next) {


if (callback) {
callback(node->elem);
}
}
}

286
7. print
Print the contents of the list by iterating over each element in the list.
void print()
{
struct linked_list *node;

printf("elements:\n");
for (node = head; node != NULL; node = node->next) {
int *data = node->elem;

printf("%d\n", *data);
}
}

287
8. clean
Cleaning up of linked list is required to free up the memory used by the linked
list.
Here’s one way to cleanup a linked list.
1. Take two pointers: node and prev.
2. node points to head and prev points to node.
3. Move node forward. free the prev pointer.
4. Set prev pointer back to node.
5. Repeat until node reaches end.
void clean()
{
struct linked_list *node;
struct linked_list *prev;

node = head;
prev = head;

while (node) {
node = node->next;
free(prev);
prev = node;
}
}
A while loop can be used as well for iteration.
struct linked_list *node = head;

while (node) {
node = node->next;
}
However, the for loop looks more complete as the assignment, condition and
increment are all in one place.

288
Below is one full example:
#include <stdio.h>
#include <stdlib.h>

struct linked_list {
void *elem;
struct linked_list *next;
};

static struct linked_list *head;


static struct linked_list *tail;

int add(void *data)


{
struct linked_list *node;

node = calloc(1, sizeof(struct linked_list));


if (!node) {
return -1;
}

node->elem = data;

if (!head) {
head = node;
tail = node;
} else {
tail->next = node;
tail = node;
}

return 0;
}

int add_head(void *data)


{
struct linked_list *node;

node = calloc(1, sizeof(struct linked_list));


if (!node) {
return -1;
}

node->elem = data;

289
if (!head) {
head = node;
tail = node;
} else {
node->next = head;
head = node;
}

return 0;
}

int delete(void *elem)


{
struct linked_list *node;
struct linked_list *prev;

node = head;
prev = node;

if (head->elem == elem) {
head = head->next;
free(node);
return 0;
} else {
while (node != NULL) {
printf("%p %p\n", node->elem, elem);
if (node->elem == elem) {
prev->next = node->next;
if (node == tail) {
tail = prev;
}
free(node);
return 0;
}
prev = node;
node = node->next;
}
}

return -1;
}

bool find(void *elem)


{
struct linked_list *node;

290
for (node = head; node != NULL; node = node->next) {
if (node->elem == elem) {
return true;
}
}

return false;
}

int count()
{
struct linked_list *node;
int n = 0;

for (node = head; node != NULL; node = node->next) {


n ++;
}

return n;
}

void for_each(void (*callback)(void *elem))


{
struct linked_list *node;

for (node = head; node != NULL; node = node->next) {


if (callback) {
callback(node->elem);
}
}
}

void print()
{
struct linked_list *node;

printf("elements:\n");
for (node = head; node != NULL; node = node->next) {
int *data = node->elem;

printf("%d\n", *data);
}
}

void clean()
{

291
struct linked_list *node;
struct linked_list *prev;

node = head;
prev = head;

while (node) {
node = node->next;
free(prev);
prev = node;
}
}

int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int e = 50;
int f = 60;

add(&a);
add(&b);
add(&c);
add(&d);
add(&e);
add(&f);

print();

delete(&a);
delete(&f);

add(&f);

print();

clean();
}

292
Doubly Linked Lists
Doubly linked list is a chain of elements terminated with a NULL pointer.
Each item in chain is called the node. Each node contains data and two pointers.
One pointer points to the next elements and another points backwards.
|--------| |--------|
NULL<---| item 1 |---->| item 2 |--->.... ---> NULL
|--------|<----|--------|<---.....
The doubly linked list structure looks as follows.
struct DL {
void *data;
struct DL *prev;
struct DL *next;
};
The data pointer holds the data and the prev pointer points to the previous
node in the list and the next pointer points to the next element in the list.

S.No Name Description


1 dl_add_head Add an element to the list at the head
2 dl_delete_item Delete an element from the list
3 dl_find_fwd Find an element in the list with
forward iteration
4 dl_find_rv Find an element in the list with
reverse iteration
5 dl_for_each_fwd Iterate over all the element with
forward iteration
6 dl_for_each_rv Iterate over all the elements with
reverse iteration
7 dl_count Count the number of elements in the
list
8 dl_free_fwd Remove all the elements with forward
iteration
9 dl_free_rv Remove all the elements with reverse
iteration

Similar to the linked list, we create two elements, head and tail.
struct DL *head;
struct DL *tail;

293
1. dl_add_head
int dl_add_head(void *data)
{
struct DL *node;

node = calloc(1, sizeof(struct DL));


if (!node) {
return -1;
}

node->data = data;

if (!head) {
head = node;
tail = node;
} else {
tail->next = node;
node->prev = tail;
tail = node;
}

return 0;
}

294
2. dl_delete_item
bool dl_delete_item(void *data, void (*callback)(void *data))
{
struct DL *node;
struct DL *prev;

if (head->data == data) {
node = head;
head = head->next;
head->prev = NULL;
if (callback) {
callback(node->data);
}
free(node);

return true;
} else if (tail->data == data) {
node = tail;
tail = tail->prev;
tail->next = NULL;
free(node);

return true;
} else {
node = head;
while (node) {
if (node->data == data) {
node->prev->next = node->next;
node->next->prev = node->prev;
if (callback) {
callback(node->data);
}
free(node);

return true;
}
node = node->next;
}
}

return false;
}

295
3. dl_find_fwd
bool dl_find_fwd(bool (*callback)(void *data))
{
struct DL *node;

for (node = head; node != NULL; node = node->next) {


if (callback(node->data)) {
return true;
}
}

return false;
}

296
4. dl_find_rv
bool dl_find_rv(bool (*callback)(void *data))
{
struct DL *node;

for (node = tail; node != NULL; node = node->prev) {


if (callback(node->data)) {
return true;
}
}

return false;
}

297
5. dl_for_each_fwd
void dl_for_each_fwd(void (*callback)(void *data))
{
struct DL *node;

for (node = head; node != NULL; node = node->next) {


if (callback) {
callback(node->data);
}
}
}

298
6. dl_for_each_rv
void dl_for_each_rv(void (*callback)(void *data))
{
struct DL *node;

for (node = tail; node != NULL; node = node->prev) {


if (callback) {
callback(node->data);
}
}
}

299
7. dl_count
int dl_count()
{
struct DL *node;
int count = 0;

for (node = head; node != NULL; node = node->next) {


count ++;
}

return count;
}

300
8. dl_free_fwd
void dl_free_fwd(void (*callback)(void *data))
{
struct DL *node = head;
struct DL *prev;

while (node) {
prev = node;
if (callback) {
callback(prev->data);
}
node = node->next;
free(prev);
}
}

301
9. dl_free_rv
void dl_free_rv(void (*callback)(void *data))
{
struct DL *node = tail;
struct DL *prev;

while (node) {
prev = node;
if (callback) {
callback(prev->data);
}
node = node->prev;
free(prev);
}
}

302
Below is a full example of doubly linked list.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

struct DL {
void *data;
struct DL *prev;
struct DL *next;
};

struct DL *head = NULL;


struct DL *tail = NULL;

int dl_add_head(void *data)


{
struct DL *node;

node = calloc(1, sizeof(struct DL));


if (!node) {
return -1;
}

node->data = data;

if (!head) {
head = node;
tail = node;
} else {
tail->next = node;
node->prev = tail;
tail = node;
}

return 0;
}

bool dl_find_fwd(bool (*callback)(void *data))


{
struct DL *node;

for (node = head; node != NULL; node = node->next) {


if (callback(node->data)) {
return true;
}

303
}

return false;
}

bool dl_find_rv(bool (*callback)(void *data))


{
struct DL *node;

for (node = tail; node != NULL; node = node->prev) {


if (callback(node->data)) {
return true;
}
}

return false;
}

int dl_count()
{
struct DL *node;
int count = 0;

for (node = head; node != NULL; node = node->next) {


count ++;
}

return count;
}

bool dl_delete_item(void *data, void (*callback)(void *data))


{
struct DL *node;
struct DL *prev;

if (head->data == data) {
node = head;
head = head->next;
head->prev = NULL;
if (callback) {
callback(node->data);
}
free(node);

return true;
} else if (tail->data == data) {

304
node = tail;
tail = tail->prev;
tail->next = NULL;
free(node);

return true;
} else {
node = head;
while (node) {
if (node->data == data) {
node->prev->next = node->next;
node->next->prev = node->prev;
if (callback) {
callback(node->data);
}
free(node);

return true;
}
node = node->next;
}
}

return false;
}

void dl_for_each_fwd(void (*callback)(void *data))


{
struct DL *node;

for (node = head; node != NULL; node = node->next) {


if (callback) {
callback(node->data);
}
}
}

void dl_for_each_rv(void (*callback)(void *data))


{
struct DL *node;

for (node = tail; node != NULL; node = node->prev) {


if (callback) {
callback(node->data);
}
}

305
}

void dl_free_fwd(void (*callback)(void *data))


{
struct DL *node = head;
struct DL *prev;

while (node) {
prev = node;
if (callback) {
callback(prev->data);
}
node = node->next;
free(prev);
}
}

void dl_free_rv(void (*callback)(void *data))


{
struct DL *node = tail;
struct DL *prev;

while (node) {
prev = node;
if (callback) {
callback(prev->data);
}
node = node->prev;
free(prev);
}
}

void print(void *data)


{
printf("val %d\n", *(int *)data);
}

int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int i;

for (i = 0; i < 10; i ++) {


dl_add_head(&a[i]);
}

306
printf("forward[%d]: \n", dl_count());
dl_for_each_fwd(print);

dl_delete_item(&a[0], NULL);
dl_delete_item(&a[2], NULL);
dl_delete_item(&a[9], NULL);

printf("reverse[%d]: \n", dl_count());


dl_for_each_rv(print);

// dl_free_fwd(NULL);
dl_free_rv(NULL);

return 0;
}

307
Circular Linked Lists
Circular lists is similar to the linked list and the difference is that the last node
points the first node.
|--------| |--------| |--------|
| item 1 |----->| item 2 |----> .... | item n |
|--------| |--------| |--------|
^ |
|--------------------------------<<----|
Below are the features that can be implemented with the use of circular lists.

S.No Name Description


1 add_tail Add an element at the tail
2 for_each Traverse the list
3 count Count the number of elements
4 delete Delete an element from the list
5 free_all Free the list

308
1. add_tail

309
2. for_each

310
3. count

311
4. delete

312
5. free_all

313
Below is a full example of Circular list.
#include <stdio.h>
#include <stdlib.h>

struct circular_list {
void *data;
struct circular_list *next;
};

static struct circular_list *head;


static struct circular_list *tail;

int add_tail(void *data)


{
struct circular_list *item;

item = calloc(1, sizeof(struct circular_list));


if (!item) {
return -1;
}

item->data = data;

if (!head) {
head = item;
tail = item;
tail->next = head;
} else {
tail->next = item;
tail = item;
item->next = head;
}

return 0;
}

void print(void (*callback)(void *data))


{
struct circular_list *item = head;

if (!item) {
return;
}

do {

314
callback(item->data);
item = item->next;
} while (item != head);
}

void for_each(void (*callback)(void *data))


{
struct circular_list *item = head;

if (!item) {
return;
}

do {
if (callback)
callback(item->data);
item = item->next;
} while (item != head);
}

int count()
{
struct circular_list *item = head;
int count = 0;

if (!item) {
return count;
}

do {
count ++;
item = item->next;
} while (item != head);

return count;
}

int delete(void *data, void (*callback)(void *data))


{
struct circular_list *item = head;
struct circular_list *prev = item;

if (head->data == data) {
head = head->next;
tail->next = head;
if (callback)

315
callback(item->data);
free(item);
return 0;
}

prev = item;
item = item->next;

while (item != head) {


if (item->data == data) {
if (callback)
callback(item->data);
prev->next = item->next;
if (item == tail) {
tail = prev;
}
free(item);
return 0;
}
prev = item;
item = item->next;
}

return -1;
}

void free_all(void (*callback)(void *data))


{
struct circular_list *item;
struct circular_list *prev;

item = head;
prev = head;

do {
prev = item;
item = item->next;
if (callback)
callback(prev->data);
free(prev);
} while (item != head);
}

void print_data(void *data)


{
printf("%d\n", *(int *)data);

316
}

int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int i;

for (i = 0; i < 10; i ++) {


add_tail(&a[i]);
}

printf("print all elements:\n");


print(print_data);

delete(&a[0], NULL);

printf("after deleting the first element:\n");


print(print_data);

delete(&a[9], NULL);

printf("after deleting the last element:\n");


print(print_data);

free_all(NULL);

return 0;
}

317
Circular Doubly Linked Lists

318
Stack
Stacks are another data structure where the data entered first is data retrieved
last or data entered in last is the data retrieved first.
|----------------|
| item n | <-- Add and Retrieve
|----------------|
| ... |
|----------------|
| item 2 |
|----------------|
| item 1 |
|----------------|
Below are some operations that can be done with the stack data structure.

S.No Name Description


1 top Get the top element of thes stack
2 push Push an element into the stack
3 pop Pop an element out of the stack
4 empty Clear elements in the stack
5 size Get the total size of elements in the stack

Stacks can be implemented with Linked lists as well.


struct stack {
void *elem;
struct stack *next;
};

static struct stack *head;


static int count;
For the stack functionality, the elements are added at head and removed at head.
Stacks are generally useful to quickly access the latest element first. Such as
when you are using git stash functionality.

319
1. top
Get the top element in the stack. This does not remove the element from the
stack.
void *top()
{
void *elem = NULL;

if (head) {
elem = head->elem;
}
return elem;
}

320
2. push
Push an element in the stack. Add new elements always at the head.
int push(void *elem)
{
struct stack *node;

node = calloc(1, sizeof(struct stack));


if (!node) {
return -1;
}

node->elem = elem;

if (!head) {
head = node;
} else {
node->next = head;
head = node;
}

count ++;

return 0;
}

321
3. pop
Return an element from the top of the stack and remove it from the stack.
void *pop()
{
struct stack *node;
void *elem = NULL;

if (head) {
elem = head->elem;
node = head;
head = head->next;
free(node);
count --;
}

return elem;
}

322
4. empty
Remove elements from the stack.
void empty(void (*callback)(void *elem))
{
struct stack *node;
struct stack *prev;

node = head;
prev = head;

while (node) {
prev = node;
node = node->next;
if (callback) {
callback(prev->elem);
}
free(prev);
}
}

323
5. size
Return the size of the stack (number of elements in stack).
int size()
{
return count;
}

324
Below is one full implementation of stack example:
#include <stdio.h>
#include <stdlib.h>

struct stack {
void *elem;
struct stack *next;
};

static struct stack *head;


static int count;

void *top()
{
return head->elem;
}

int push(void *elem)


{
struct stack *node;

node = calloc(1, sizeof(struct stack));


if (!node) {
return -1;
}

node->elem = elem;

if (!head) {
head = node;
} else {
node->next = head;
head = node;
}

count ++;

return 0;
}

void *pop()
{
struct stack *node;
void *elem = NULL;

325
if (head) {
elem = head->elem;
node = head;
head = head->next;
free(node);
count --;
}

return elem;
}

int size()
{
return count;
}

void empty(void (*callback)(void *elem))


{
struct stack *node;
struct stack *prev;

node = head;
prev = head;

while (node) {
prev = node;
node = node->next;
if (callback) {
callback(prev->elem);
}
free(prev);
}
}

int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int f = 6;

push(&a);
push(&b);
push(&c);

326
push(&d);
push(&e);
push(&f);

printf("size : %d\n", size());

while (1) {
int *elem = pop();
if (elem == NULL) {
break;
}
printf("%d\n", *elem);
}

empty(NULL);

return 0;
}

327
Queue
The queue adds elements at the last and retrieves them at the first. For this we
use two pointers head and tail.
|-------------|
| item 1 | <-- First (Remove elements)
|-------------|
| item 2 |
|-------------|
| ... |
|-------------|
| item n | <-- Last (Add elments)
|-------------|
The below structure definition is as follows:
struct queue {
void *elem;
struct queue *next;
};

static struct queue *head;


static struct queue *tail;
static int count;
The following are operations of queue:

S.No Name Description


1 front Get the front element in the queue
2 back Get the back element in the queue
3 empty Empty the queue
4 size Get the number of elements in the queue
5 push Push an element in the queue
6 pop Pop an element from the queue

328
1. front
Retrieve an element at the beginning of the queue.
void *front()
{
void *elem;

if (head) {
elem = head->elem;
}

return elem;
}

329
2. back
Retrive the last element of the queue.
void *back()
{
void *elem;

if (tail) {
elem = tail->elem;
}

return elem;
}

330
3. empty
Empty the queue.
The logic here is the following.
1. take two pointers prev and cur.
2. point prev and cur pointers to head.
3. in a loop:
1. prev = cur;
2. node = node->next;
3. call a callback so that user can free his data
4. free(prev)
void empty(void (*callback)(void *elem))
{
struct queue *node;
struct queue *prev;

node = head;
prev = head;

while (node) {
prev = node;
node = node->next;
if (callback) {
callback(prev->elem);
}
free(prev);
}
count = 0;
}

331
4. size
Return the number of elements in the queue.
int size()
{
return count;
}

332
5. push
Push an element in the queue. Add at the last.
1. Take two pointers head and tail.
2. if no elements (i.e. head is NULL) add the element and point to both head
and tail.
3. if an element present, point the tail->next to the new element.
4. since the new element added, tail becomes the new element. (tail =
node).
int push(void *elem)
{
struct queue *node;

node = calloc(1, sizeof(struct queue));


if (!node) {
return -1;
}

node->elem = elem;

if (!head) {
head = node;
tail = node;
} else {
tail->next = node;
tail = node;
}

return 0;
}

333
6. pop
Remove an element at the beginning and move the head to the next element.
void *pop()
{
struct queue *node;
void *elem = NULL;

if (head != NULL) {
node = head;
elem = node->elem;
head = head->next;
free(node);
}

return elem;
}

334
Below is an implementation of queue.
#include <stdio.h>
#include <stdlib.h>

struct queue {
void *elem;
struct queue *next;
};

static struct queue *head;


static struct queue *tail;
static int count;

void *front()
{
void *elem;

if (head) {
elem = head->elem;
}

return elem;
}

void *back()
{
void *elem;

if (tail) {
elem = tail->elem;
}

return elem;
}

void empty(void (*callback)(void *elem))


{
struct queue *node;
struct queue *prev;

node = head;
prev = head;

while (node) {
prev = node;

335
node = node->next;
if (callback) {
callback(prev->elem);
}
free(prev);
}
count = 0;
}

int size()
{
return count;
}

int push(void *elem)


{
struct queue *node;

node = calloc(1, sizeof(struct queue));


if (!node) {
return -1;
}

node->elem = elem;

if (!head) {
head = node;
tail = node;
} else {
tail->next = node;
tail = node;
}

return 0;
}

void *pop()
{
struct queue *node;
void *elem = NULL;

if (head != NULL) {
node = head;
elem = node->elem;
head = head->next;
free(node);

336
}

return elem;
}

int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int e = 50;
int f = 60;

push(&a);
push(&b);
push(&c);
push(&d);
push(&e);
push(&f);

printf("size : %d\n", size());


printf("Front: %d\n", *(int *)front());
printf("Back: %d\n", *(int *)back());

while (1) {
int *elem = pop();
if (!elem) {
break;
}
printf("%d\n", *elem);
}

empty(NULL);

return 0;
}

337
Ring Buffer

338
Tree

339
Merkel Trees

340
Hash Tables
Hash tables are a list of arrays which can be accessed by indexing into them
with a given key.
For example, Consider we have the following lists.
| 1 | 2 | 3 | 4 |
| 5 |
| 6 | 7 | 8|
In this list of lists, to access element 5 we should first search the first list, then
the second till 5 is reached.
In hash tables, each element in the list is assigned a key when adding to the
list. So when a query is made on that element, also the key is given as input to
directly find out the particular element at that index.
To do this we hash the key, and modulo it with the number of lists that are
present. The resulting number is the index of the hash table.
A hash can simply be a computation or a random number generator doing certain
maths operations. Key can be anything too.
For example,
const char *key = "first item";

int hash(const char *k)


{
int i;
int r = 0;

for (i = 0; k[i] != '\0'; i ++) {


r += k[i];
}

return r % hash_size;
}
In general the hashes must look random, we explain more below.

341
Below is an example of the hash tables.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

struct LL {
void *data;
struct LL *next;
};

#define HASH_TABLE_LEN 30

struct HT {
struct LL *head;
struct LL *tail;
};

struct HT ht[HASH_TABLE_LEN];

int hash(const char *str)


{
int v = 0;
int i;

for (i = 0; str[i] != '\0'; i ++) {


v += str[i];
}

return v % HASH_TABLE_LEN;
}

int add_item(const char *key, void *data)


{
struct LL *item;
int r = hash(key);

item = calloc(1, sizeof(struct LL));


if (!item) {
return -1;
}

item->data = data;

if (!ht[r].head) {

342
ht[r].head = item;
ht[r].tail = item;
} else {
ht[r].tail->next = item;
ht[r].tail = item;
}

return 0;
}

bool find(const char *key, void *data)


{
struct LL *item;
int r = hash(key);
int iters = 0;

for (item = ht[r].head; item != NULL; item = item->next) {


iters ++;
if (item->data == data) {
printf("took %d iterations\n", iters);
return true;
}
}

return false;
}

void free_ht()
{
int i;

for (i = 0; i < HASH_TABLE_LEN; i ++) {


struct LL *item;
struct LL *prev;

item = ht[i].head;

while (item) {
prev = item;
item = item->next;
free(prev);
}
}
}

struct table {

343
char *key;
int a;
} list [] = {
{"hash_1", 1},
{"hash_2", 2},
{"hash_3", 3},
{"hash_4", 4},
{"hash_5", 5},
{"hash_6", 6},
{"hash_7", 7},
{"hash_8", 8},
{"hash_9", 9},
{"hash_10", 10},
{"hash_11", 11},
{"hash_12", 12},
{"hash_13", 13},
{"hash_14", 14},
{"hash_15", 15},
{"hash_16", 16},
{"hash_17", 17},
{"hash_18", 18},
{"hash_19", 19},
{"hash_20", 20},
{"hash_21", 21},
{"hash_22", 22},
{"hash_23", 23},
{"hash_24", 24},
{"hash_25", 25},
{"hash_26", 26},
{"hash_27", 27},
{"hash_28", 28},
{"hash_29", 29},
{"hash_30", 30},
{"hash_31", 31},
{"hash_32", 32},
{"hash_33", 33},
{"hash_34", 34},
{"hash_35", 35},
{"hash_36", 36},
{"hash_37", 37},
{"hash_38", 38},
{"hash_39", 39},
{"hash_40", 40},
{"hash_41", 41},
{"hash_42", 42},
{"hash_43", 43},

344
{"hash_44", 44},
{"hash_45", 45},
{"hash_46", 46},
};

void print()
{
int i;

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


printf("index %d\n", i);

struct LL *item;

for (item = ht[i].head; item != NULL; item = item->next) {


struct table *t = item->data;
printf("\tval [%s] [%d]\n", t->key, t->a);
}
}
}

int main()
{
int i;

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


add_item(list[i].key, &list[i]);
}

print();

printf("find %s in %s\n", list[10].key, find(list[10].key, &list[10]) ? "True": "False")


printf("find %s in %s\n", list[12].key, find(list[12].key, &list[12]) ? "True": "False")
printf("find %s in %s\n", list[45].key, find(list[45].key, &list[45]) ? "True": "False")
printf("find %s in %s\n", list[33].key, find(list[33].key, &list[33]) ? "True": "False")
printf("find hash_50 in %s\n", find("hash_50", &list[12]) ? "True": "False");

free_ht();

return 0;
}

345
Search and Sorting

346
Sorting
Sorting is a process through which elements are arranged in an order, more
generally in ascending order. There are various ways to sort elements. Below
are few of the approaches that will be mentioned here.
1. Bubble Sort
2. Insertion Sort
3. Merge Sort
4. Quick Sort

347
Bubble Sort
The largest elements in the bubble sort bubbble their way to the end.
Bubblesort algorithm work like the following:
1. compare two elements (one and next) and swap them if the first is greater
than the second.
2. keep going until the largest element reaches the end. Now we do not have
to compare the last element.
3. Repeat the steps 1 and 2 till all the elements are sorted.
A pseudo implementation of this looks as follows.
for sorted = 0; sorted < max - 1; sorted ++ {
for j = 0; j < max - sorted - 1; j ++ {
if (a[j] > a[j + 1]) {
swap(a[j], a[j + 1]);
}
}
}
As we can see we loop till array length - 1 as we are checking the next element
(+1) to avoid the array out of bounds.
Below is an implementation of bubble sort.
#include <iostream>

template <typename T>


void bubble_sort(T *a, int n)
{
int i;
int j;
int iters = 0;

for (i = 0; i < n - 1; i ++) {


for (j = 0; j < n - i - 1; j ++) {
if (a[j] > a[j + 1]) {
T t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
iters ++;
}
}
}

int main()

348
{
int a[10] = {4, 2, 1, 9, 8, 7, 3, 6, 5, 10};

bubble_sort<int>(a, sizeof(a) / sizeof(a[0]));

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


std::cout << a[i] << std::endl;
}

return 0;
}
Complexity: worst case O(nˆ2).
One can reduce the iterations by looking at the inner loop. If there are any
elements to be sorted, set a variable otherwise break the loop since elements are
all sorted out.
The modification looks as follows:
#include <iostream>

template <typename T>


void bubble_sort(T *a, int n)
{
int i;
int j;
int iters = 0;
bool sorted = false;

for (i = 0; i < n - 1; i ++) {


sorted = false;
for (j = 0; j < n - i - 1; j ++) {
if (a[j] > a[j + 1]) {
T t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
sorted = true;
}
iters ++;
}
if (!sorted) {
break;
}
}

std::cout << "iters: " << iters << std::endl;


}

349
int main()
{
int a[10] = {91, 19, 1, -1, 11, -18, 29, 7, 9, 10};

bubble_sort<int>(a, sizeof(a) / sizeof(a[0]));

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


std::cout << a[i] << std::endl;
}

return 0;
}
Using bubblesort to sort linked lists
Linked lists can be sorted with the bubblesort technique.
When we are sorting the same algorithm will be used but the lists must be
checked and swapped instead of arrays as shown above.
To sort the lists we have few scenarios:
1. If the first element (head) needs to be swapped.
Make the smallest element point to the head element
swap_head():
tmp = next_node;
cur->next = tmp;
next_node->next = cur;

if (cur == head) // head element needs swapping


head = next_node; // head becomes smaller node
2. If the element to be swapped is in the middle.
We need 3 pointers: 1. prev, cur and next.
Order the pointers back as follows:
tmp = next->next;
prev->next = next; // now prev->next links to next than cur
cur->next = next->next; // cur->next links to next element of next
next->next = cur; // next->next points to the cur element
Below is the full example of bubblesort on linked lists. Use the linked list code
in the Datastructures section above for the list management (for add/ insert /
delete).
struct linked_list *swap(struct linked_list *cur,
struct linked_list *next,

350
struct linked_list *prev)
{
struct linked_list *tmp = next->next;

cur->next = tmp;
next->next = cur;

if (cur == head) {
head = next;
}

if (prev) {
prev->next = next;
}

return next;
}

void bubble_sort(bool (*compare_cb)(void *cur, void *next))


{
int n = count();
int i;
int j;
struct linked_list *node;
struct linked_list *prev = NULL;

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


node = head;
prev = NULL;

for (j = 0; (node != NULL) && (j < n - i - 1); j ++) {


struct linked_list *next = node->next;

if ((next != NULL) && compare_cb(node->elem, next->elem)) {


node = swap(node, next, prev);
}

prev = node;
node = node->next;
}
}
}

bool compare_fn(void *cur, void *next)


{
if (*(int *)cur > *(int *)next) {

351
return true;
}

return false;
}

int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int e = 50;
int f = 60;

add(&b);
add(&a);
add(&e);
add(&f);
add(&c);
add(&d);

print();

bubble_sort(compare_fn);

print();

clean();
}

352
Insertion Sort
#include <iostream>

template <typename T>


void swap(T &a, T &b)
{
T t;

t = a;
a = b;
b = t;
}

template <typename T>


void insert_sort(T *items, size_t len)
{
int j;
int i;
int iterations = 0;

for (j = 1; j < len; j ++) {


T key = items[j];
i = j - 1;
while (i >= 0) {
if (items[i] > key) {
swap(items[i + 1], items[i]);
}
i --;
iterations ++;
}
}
}

int main()
{
int a[6] = {5,2,4,6,1,3};
int i;

insert_sort(a, 6);

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


printf("a[%d] = %d\n", i, a[i]);
}

return 0;

353
}

354
Quick Sort

355
Merge Sort

356
Searching
Searching in computer programming refers to finding some text string, number
or pattern a large group of text or numbers. Sometimes the search yields efficient
results if the data is sorted.
A general searching on an array look as follows.
int search(int array[], int size, int item)
{
int i;

for (i = 0; i < size; i ++) {


if (array[i] == item) {
return i;
}
}

return -1;
}
While it looks easy, the time and compute complexity goes high if the number
of items to search grows. For example if the array contains elements from 1 to
1000, the iterations it takes finding 1000 in above program is 1000.
So there are few searching algorithms in place to efficiently search an item in a
list of items.

357
Binary search
Binary search requires the numbers to be sorted in order. Without the numbers
being sorted one cannot use binary search on the data.
The binary search algorithm tries to find a matching element by running a divide
and conquer method.
Below is how the algorithm works:
1. A sorted group of elements are first divided into two equal groups.
2. Check the mid point of the array matches the queried element. If yes,
return the index of mid point.
3. If not matched, then do the following:
1. Check if the midpoint is greater than the queried item. If yes, move
the search to lower half.
2. If no, move the search to higher half.
4. Repeat steps 2 and 3 till an element found or until lower half and higher
half indices are matched.
Take an example of sorted elements.
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
assign low mid and high variables.
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
^ ^ ^
| | |
Low Mid High
low = 0 high = 9 mid = 4
Now the item to search is 6, then
1. 5 is less than 6 so low = mid + 1 = 5, high = 9, mid = (5 + 9) / 2 = 7
| 6 | 7 | 8 | 9 | 10 |
^ ^ ^
| | |
Low Mid High
2. 8 is greater than 6 so low = 5, high = mid - 1 = 8, mid = (5 + 8) / 2 = 6
| 6 | 7 | 8 | 9 |
^ ^ ^
| | |
Low Mid High
3. 7 is greater than 6 so low = 5, high = mid - 1 = 5, mid = (5 + 5) / 2 = 5
| 6 |
^

358
|
Low = Mid = High
So it takes 3 iterations to find number 6.

359
Below is a templatized binary search example in C++.
#include <iostream>

template <typename T>


int binary_search(const T *items, int n, const T item)
{
int low = 0;
int high = n - 1;
int mid = (low + high) / 2;

while (low <= high) {


if (items[mid] == item) {
return mid;
}

if (items[mid] < item) {


low = mid + 1;
}
if (items[mid] > item) {
high = mid - 1;
}
mid = (low + high) / 2;
}

return -1;
}

int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int i;

i = binary_search(a, 10, 3);

if (i != -1) {
printf("a[%d] = %d\n", i, a[i]);
} else {
printf("search not found\n");
}

return 0;
}
Well the comparison operations such as ==, < and > operations are specific to
integers, one can also write the custom operators for ==, < or > when passing
other types. Such as for example, if integers are part of the structure and they

360
require sorting.
struct S {
int i;
};

bool operator==(const S &lhs, const S &rhs)


{
if (lhs.i == rhs.i) {
return true;
}

return false;
}
And the calling function thus can use,
S s3 = { .i = 3 };
binary_search<S>(a, 10, s3);

361
Changelog
17/09/2023:
1. updated the data operator <<, >, < for classes.
2. Cleaned up pages in book.
18/09/2023:
1. Update std::list.

362

You might also like