LPA Advanced C Programming Course SEP2024
LPA Advanced C Programming Course SEP2024
If you have any questions of queries, please add your feedback in the Q&A section of the course on Udemy.
Best regards,
Tim Buchalka
Learn Programming Academy
•it is not required to enroll in the first class if you already have basic
knowledge of the C programming language
•if you are new to “C” programming, I would suggest that you first enroll
in my “C programming for Beginners” class
ADVANCED C PROGRAMMING COURSE INTRODUCTION
Welcome to Class! ACP-0020-3
Topics
•storage classes
•auto, register, static, and extern
•type qualifiers
•const, volatile, and restrict
•bit manipulation
•binary numbers and bits
•bitwise operators (logical and shifting)
•bitmasks and bitfields
•macros
•overview (vs. functions, when to use)
•predefined macros
•creating your own macros
• advanced pointers
• double pointers (pointers to pointers)
• function pointers
• more on void pointers
•data structures
•Linked lists, stacks, queues, and Trees
• hands on coding
•Powerpoint slides
•a way to present abstract concepts and definitions that I am not able to
demonstrate in code (as easily)
•Not just reading from slides!
•Providing insight via experience
•Providing thorough explanations
• designated initializers
• allow you to initialize the elements of an array, union, or struct explicitly by subscript or name
• restricted pointers
• a type qualifier that can be used only for pointers
•inline functions
•supplies a hint for the compiler to perform optimizations
•I want it to be clear that we may touch on some concepts from C11 in this class, but, it will
be minimal
•static assertions
•evaluated during translation at a later phase than #if and #error
•let you catch errors that are impossible to detect during the preprocessing phase
•no-return functions
•declares a function that does not return
•suppresses compiler warnings on a function that doesn't return
•enables certain optimizations that are allowed only on functions that don't return
• on the Choose Download Site page, choose a download site you think might be relatively close to you. Click Next.
• Click Next to connect to the download site and download the packages you selected, and click Finish when the installation is complete
• In the System Variables panel of the Environment Variables dialog, select the Path variable and click
Edit
• Add the path to the cygwin-directory\bin directory to the Path variable, and click OK
• By default, cygwin-directory is C:\cygwin (for 32 bit Cygwin distribution) or
• C:\cygwin64 (for 64 bit Cygwin distribution)
• Directory names must be separated with a semicolon
• Click OK in the Environment Variables dialog and the System Properties dialog.
•https://code.visualstudio.com/
•Optional for windows (Follow the wizard, very straight forward, for
installation)
C FOR BEGINNERS
Overview
Overview
•Windows
•C compiler (Cygwin)
•IDE’s (Integrated Development Environment)
•CodeLite
C FOR BEGINNERS
Overview
Overview
•Mac
•C compiler (developer tools)
•IDE’s (Integrated Development Environment)
•CodeLite
•xcode
C FOR BEGINNERS
Overview
Overview
•Linux
•Compiler comes installed
•IDE’s (Integrated Development Environment)
•CodeLite
•vi, gedit, compile from command line
C FOR BEGINNERS
Overview
Installing the C Compiler
Jason Fedin
INTERMEDIATE C PROGRAMMING
Installing the C Compiler (Windows)
Overview
•on the Choose Download Site page, choose a download site you think might be relatively close to you. Click Next.
•on the Select Packages page you select the packages to download
•Click the + next to Devel to expand the development tools category
•You may want to resize the window so you can see more of it at one time
•Select each package you want to download by clicking the Skip label next to it, which reveals the version number of
the package to download
•at a minimum, select
•gcc-core: C compiler
•gdb: The GNU Debugger
•make: the GNU version of the 'make' utility
•Packages that are required by the packages you select are automatically selected as well.
•Click Next to connect to the download site and download the packages you selected, and click Finish when the
installation is complete
INTERMEDIATE C PROGRAMMING
Installing the C Compiler (Windows)
Setting your path up
•Open the Control Panel:
•On Windows XP select Start > Settings > Control Panel and double-click System.
•On Windows 7, type var in the Start menu's search box to quickly find a link to Edit the system
environment variables
•In the System Variables panel of the Environment Variables dialog, select the Path
variable and click Edit
•Add the path to the cygwin-directory\bin directory to the Path variable, and click OK
•By default, cygwin-directory is C:\cygwin (for 32 bit Cygwin distribution) or
•C:\cygwin64 (for 64 bit Cygwin distribution)
•Directory names must be separated with a semicolon
•Click OK in the Environment Variables dialog and the System Properties dialog.
INTERMEDIATE C PROGRAMMING
Installing the C Compiler (Windows)
Verifying the Installation
INTERMEDIATE C PROGRAMMING
Installing the C Compiler (Windows)
Using The Command Line
Interface
Jason Fedin
C FOR BEGINNERS
Using the Command Line Interface
Using the Command Line Interface
C FOR BEGINNERS
Using the Command Line Interface
Overview
Jason Fedin
•most programs in the real world that you will have to develop will not be as
small or as simple
•it is imperative to learn the proper techniques for dealing with larger programs
•you need to divide the problem into multiple sub problems and then try to
tackle it one by one
•C provides all the features necessary for the efficient development of large
programs
•the most common beginner mistake is to jump in directly and try to write all the
necessary code into a single file and later try to debug or extend later
•this approach is doomed to fail and would usually require re-writing from scratch
•all the functions that the program used were included in one file
•except for the system functions, such as printf() and scanf()
•standard header files such as <stdio.h> and <stdbool.h> were also included for
definitions and function declarations
•when you start dealing with larger programs, they must be organized around multiple
files
•programs that contain more than 100 statements or so
• large programming applications frequently require the efforts of more than one programmer
• as the number of statements in the program increases, so does the time it takes to edit the program and to
subsequently recompile it
• having everyone work on the same source file, or even on their own copy of the same source file, is unmanageable
• C programs do not require that all the statements for a particular program be contained in a single file
• you can enter your code for a particular module into one file, for another module into a different file, and so on
• the term module refers either to a single function or to a number of related functions that you choose to group logically
• with Code::Blocks and Visual Studio code, working with multiple source files is easy
• you simply have to identify the particular files that belong to the project on which you are working, and the software
handles the rest for you
• when changes are made to a file, only that file need be re-compiled to rebuild the program
• if you check any Open-Source project (GitHub) you can see how the large program is “decentralized”
into many numbers of sub-modules
• each individual module contributes to a specific critical function of the program
•all functions from each section will usually live in a single file
•to tell the system that these three modules actually belong to the same
program
•you include the names of all three files when you enter the command to
compile the program
•the above has the effect of separately compiling the code contained in mod1.c,
mod2.c, and main.c
•the above indicates that the compiler identified an error on line 10 of file mod2.c
•in the function foo
•no messages are displayed for mod1.c and main.c because no errors are found compiling
those modules
• some C compilers keep these object files around and do not delete them when you compile more than one file at a
time
• in the previous example, because mod1.c and main.c had no compiler errors, the corresponding .o files (mod1.o and
main.o) would still be around
• replacing the c from the filename mod.c with an o tells the C compiler to use the object file that was produced
• the following command line could be used with a compiler that does not delete the object code files
• do you not have to re-edit mod1.c and main.c if no errors are discovered by the compiler, but you also don’t have to
recompile them
•you can compile each module separately and using the –c command-line option
•option tells the compiler not to link your file
•does not produce an executable
•retains the intermediate object file that it creates
$ gcc –c mod2.c
•compiles the file mod2.c, placing the resulting executable in the file mod2.o
•the last line above lists only object files and no source files
•the object files are linked together to produce the executable output file ‘myProgram’
• the above will compile a program consisting of six modules, in which only the module legal.c needs to
be recompiled
• Code::blocks and Visual Studio code have this knowledge of what needs recompilation, and they only
recompile files as necessary
• the process of incremental compilation can be automated by using a tool called make which we will
discuss in the next lecture
• this powerful utility allows you to specify a list of files and their dependencies in a special file known as a Makefile
• if make finds that your source (.c) file is newer than your corresponding object (.o) file
• automatically issues the commands to recompile the source file to create a new object file
• you can even specify source files that depend on header files
• you can specify that a module called datefuncs.o is dependent on its source file datefunc.c as well as the header file
date.h
• if you change anything inside the date.h header file, the make utility automatically recompiles the datefuncs.c file
• based on the fact that the header file is newer than the source file
$(OBJ): $(SRC)
$ make
gcc -c -o mod1.o mod1.c
gcc -c -o mod2.o mod2.c
gcc -c -o main.o main.c
gcc mod1.o mod2.o main.o -o myProgram
$
•make compiled each individual source file and then linked the resulting
object files to create the executable
$ make
gcc -c -o mod1.o mod1.c
gcc -c -o mod2.o mod2.c
mod2.c: In function 'foo2':
mod2.c:3: error: 'i' undeclared (first use in this function)
mod2.c:3: error: (Each undeclared identifier is reported only once
mod2.c:3: error: for each function it appears in.)
make: *** [mod2.o] Error 1
$
• make found there was an error in compiling mod2.c and stopped the make process, which is its
default action
$ make
gcc -c -o mod2.o mod2.c
gcc -c -o main.o main.c
gcc mod1.o mod2.o main.o -o myProgram
$
•you can use the Make utility to start using make for your own programs
• if a function from one file needs to call a function contained inside another file
• a function call can be made in the normal fashion
• arguments can be passed and returned in the usual way
• in the file that calls the function, you should always make certain to include a prototype declaration
• so that the compiler knows the function’s argument types and the type of the return value
• it is important to remember that even though more than one module might be specified to the
compiler at the same time on the command line
• the compiler compiles each module independently
• means that no knowledge about structure definitions, function return types, or function
argument types is shared across module compilations by the compiler
• it is up to you to ensure that the compiler has sufficient information about such things to correctly
compile each module
• an external variable is one whose value can be accessed and changed by another module (file)
int moveNumber = 0;
• declare the above, at the beginning of your program, outside of any function
• its value could be referenced by any function within that program
• moveNumber is defined as a global variable
• the statement defines the variable moveNumber not just as a global variable, but also as an external
global variable
• the value of moveNumber can now be accessed and modified by the module in which the preceding
declaration appears
• other modules can also access the value of moveNumber by incorporating a similar extern declaration
in the file
•if you need to define a global variable whose value does not have to be accessed from
another file
•declare the variable to be static
•a cleaner approach to programming
• you can then simply include the file in any program that needs to use those definitions
• no where is the usefulness of the #include facility greater than in developing programs that have been divided into
separate program modules
• if more than one programmer is working on developing a particular program, include files provide a means of
standardization
• each programmer is using the same definitions, which have the same values
• each programmer is also spared the time-consuming and error-prone task of typing these definitions into each file
that must use them
• by centralizing the definition of common data structures into one or more include files
• you eliminate the error that is caused by two modules that use different definitions for the same data structure
• if a change has to be made to the definition of a particular data structure, it can be done in one place only—inside
the include file
• in this lecture, we will discuss these three types with a focus on the heap and stack
• you have to decide when to use memory from the stack vs heap or static memory based on each problem you are
trying to solve
• the stack is a "LIFO" (last in, first out) data structure that is managed and optimized by the CPU
• a linear data structure
• there is no need to manage the memory yourself
• variables are allocated and freed automatically
• the stack grows and shrinks as variables are created and destroyed inside a function
• every time a function declares a new variable, it is "pushed" onto the stack
• every time a function exits, all of the variables pushed onto the stack by that function, are freed (deleted)
• once a stack variable is freed, that region of memory becomes available for other stack variables
• there is a limit on the size of variables that can be stored on the stack
• if a program tries to put too much information on the stack, stack overflow will occur
• happens when all the memory in the stack has been allocated, and further allocations begin overflowing into other sections of memory
• stack overflow also occurs in situations where recursion is incorrectly used
•a key to understanding the stack is the notion that when a function exits, all of
its variables are popped off of the stack
•thus stack variables are local in nature
•stack variables only exist while the function that created them is running
•a common bug is attempting to access a variable that was created on the stack
inside some function, from a place in your program outside of that function (i.e.
after that function has exited)
•when you need variables like arrays and structs that can change size dynamically
•arrays that can grow or shrink as needed
•the lifetime of a variable is the time period during which variable exist in computer memory
•some exist briefly, some are repeatedly created and destroyed, and others exist for the
entire execution of a program
•the scope of the variable is where the variable can be referenced in a program
•some can be referenced throughout a program, others from only portions of a program
•the four storage-class specifiers can be split into two storage durations
•automatic storage duration
•static storage duration
•the value of a local variable can only be accessed by the function in which the
variable is defined
•Its value cannot be accessed by any other function
•If an initial value is given to a variable inside a function, that initial value is
assigned to the variable each time the function is called
•C++ has repurposed the auto keyword for a quite different use, so simply not
using auto as a storage-class specifier is better for C/C++ compatibility
•you can, however, make your intentions perfectly clear by explicitly using the
keyword auto before the definition of the variable
•or that it is important not to change the variable to another storage class
ADVANCED C PROGRAMMING COURSE STORAGE CLASS
Automatic Variables ACP-0300-5
Why use Auto?
•automatic storage is a means of conserving memory
•automatic variables exist only when they are needed
•they are created when the function in which they are defined is
entered
•they are destroyed when the function is exited
•why have variables stored in memory and accessible when in fact they
are not needed?
•to specify the storage class for a variable, the following syntax is to be followed
•the following declaration indicates that double variables x and y are automatic
local variables
•they exist only in the body of the function in which the declaration appears
auto double x, y;
•an extern variable is a global variable initialized with a legal value where it is
declared in order to be used elsewhere
•an extension to the concept of the global variable
•the main purpose of using extern variables is that they can be accessed between
two different files which are part of a large program
•functions contained in separate files can communicate through external variables
•the extern storage class is used to give a reference of a global variable that is visible
to ALL the program files
int moveNumber = 0;
•declare the above, at the beginning of your program, outside of any function
•its value could be referenced by any function within that program
•moveNumber is defined as a global variable
• the statement defines the variable moveNumber not just as a global variable, but also as an external global variable
• the value of moveNumber can now be accessed and modified by the module in which the preceding declaration
appears
• other modules can also access the value of moveNumber by incorporating a similar extern declaration in the file
• the first way is to declare the variable outside of any function, not preceded by the keyword extern
int moveNumber;
• the second way to define an external variable is to declare the variable outside of any function, placing
the keyword extern in front of the declaration
• explicitly assigning an initial value to it
•an extern function can be called from a file where it is not defined
•where it does not need to be defined in a header file
•the definition of the foo function effectively becomes global to any file
in the program
•can be called from outside the file
•when applied to functions, the static function can be called only from
within the same file as the function appears
• static variables have a property of preserving their value even after they are out of their scope
• static variables preserve the value of their last use in their scope.
• no new memory is allocated because they are not re-declared
• their scope is local to the function to which they were defined
• making local variables static allows them to maintain their values between function calls
• does not create and destroy the local variable each time it comes into and goes out of scope
•the initial value specified for a static variable must be a simple constant
or constant expression
•static variables also have default initial values of zero, unlike automatic
variables, which have no default initial value
•static variables are allocated memory on the heap, not on the stack
• if you need to define a global variable whose value does not have to be accessed from another
file
• declare the variable to be static
• a cleaner approach to programming
•a static function can be called only from within the same file as the
function appears
•the definition of the foo function effectively becomes local to the file in
which it is defined
•cannot be called from outside the file
• whatever might be the case, all structure members should reside in the same memory segment
• the value for the structure element is fetched by counting the offset of the element from the beginning address of
the structure
• separating out one member alone to a data segment defeats the purpose of a static variable
• the register storage class is used to define local variables that should be stored in a register instead of RAM
(memory)
• makes the use of register variables to be much faster than that of the variables stored in the memory
during the runtime of the program
• the register storage class should only be used for variables that require quick access
• the variables which are most frequently used in a C program
• if a function uses a particular variable heavily
• the keyword register hints to the compiler that a given variable can be put in a register
• it is the compiler’s choice to put it in a register or not
• MIGHT be stored in a register depending on hardware and implementation restrictions
• generally, compilers themselves do optimizations and put the variables in register
•both local variables and formal parameters can be declared as register variables
•this storage class declares register variables which have the same functionality as that of
the auto variables
•the lifetime of register variable remains only when control is within the block
•the variable stored in a register has a maximum size equal to the register size
https://www.pinterest.com/pin/195484440055998416/
•Our first challenge is to write a small program that declares the following
variables
•an int variable with block scope and temporary storage
•a global double variable that is only accessible inside this file
•a global float variable that is accessible within the entire program
•a float local variable with permanent storage
•a register int variable
•a function that is only accessible with the file it is defined
#include <stdio.h>
int sum (int num) {
/**
* find sum a number
*/
}
int main() {
printf("%d ",sum(25));
printf("%d ",sum(15));
printf("%d ",sum(30));
return 0;
}
output
25 40 70
•in c programming you can use this directive to define symbolic, or manifest, constants in
a program
•by convention, #define names are defined using only uppercase letters
and underscores
#define YES 1
• the name YES can subsequently be used anywhere in the program where the constant 1 could be used
• it is the same as doing a search and replace with a text editor
• the preprocessor replaces all occurrences of the defined name with its associated text
gameOver = YES;
•the following defines the name TWO_PI as the product of 2.0 and 3.141592654
#define TWO_PI 2.0 * 3.141592654
•you can subsequently use this defined name anywhere in a program where the
expression 2.0 × 3.141592654 would be valid
•you could replace the return statement of a circumference function with the following
statement
return TWO_PI * r;
•in a program, you can then write more readable statements, such as
while ( listPtr != NULL )
•to set up a while loop that will execute as long as the value of listPtr is not equal to the
null pointer
• e.g.
• when you define an array, you must specify the number of elements in the array
• subsequent program statements will likely use the knowledge of the number of elements contained inside the array
float dataValues[1000];
• there is a good chance that you will see statements in the program that use the fact that dataValues contains 1,000
elements
for ( i = 0; i < 1000; ++i )
• you would use the value 1000 as an upper bound for sequencing through the elements of the array
• suppose that you had to increase the size of the dataValues array from 1,000 to 2,000 elements
• would necessitate changing all statements that used the fact that dataValues contained 1,000 elements
• you can subsequently define the dataValues array to contain MAXIMUM_DATAVALUES elements
float dataValues[MAXIMUM_DATAVALUES];
• Statements that use the upper array bound can also make use of this defined name
for ( i = 0; i < MAXIMUM_DATAVALUES; ++i )
• you can now easily change the size of the dataValues array to 2,000 elements by simply changing the
definition
#define MAXIMUM_DATAVALUES 2000
• if the program is written to use MAXIMUM_DATAVALUES in all cases where the size of the array was used
• the preceding definition could be the only statement in the program that would have to be changed
• it might be necessary to use constant values that are related to the particular computer on which the program
is running
• might have to do with the use of a particular computer memory address, a filename, or the number of bits
contained in a computer word
• in situations in which the program must be written to make use of machine-dependent values
• the #define statement can help isolate machine-dependent values from the program as much as possible
• would be easier to port to another machine
• the above defines PI as a symbol that is to be replaced in the code by the string 3.14159f
• we could have defined PI as a variable, but to tell the compiler that its value is fixed and must not be
changed
• you can fix the value of any variable when you declare it by prefixing the type name with the keyword
const
• the advantage of defining Pi in this way is that you are now defining it as a constant numerical value with a
specified type
• when using #define, PI is just a sequence of characters that replaces all occurrences of PI in your code
Counter j, n;
• the C compiler treats the declaration of the variables j and n as normal integer variables
• you can also use typedef to help make a program more portable
• used to create synonyms for the basic data types
• a program requiring 4-byte integers may use type int on one system and type long on another
• programs designed for portability often use typedef to create an alias for 4-byte integers, such as Integer
• the alias Integer can be changed once in the program to make the program work on both systems
#define Counter int; // has the same results as using a typedef as in the previous slide
•remember, the typedef statement does not actually define a new type
•only a new type name
• use typedefs for types that combine arrays, structs, pointers, or functions
• a variable-length array is an array whose length, or size, is defined in terms of an expression evaluated at
execution time
• enable you to work with arrays in your programs without having to give them a constant size
• the term variable in variable-length array does not mean that you can modify the length of the array after
you create it
• once created, a VLA keeps the same size
• the term variable means that you can use a variable when specifying the array dimensions when first
creating the array
•you can check for support for variable length arrays using this code
#ifdef __STDC_NO_VLA__
printf("Variable length arrays are not supported.\n");
exit(1);
#endif
“USING VLA’S IS ACTIVELY STUPID! It generates much more code, and much slower code
(and more fragile code), than just using a fixed key size would have done.”
•Torvalds goes on to say that Linux is free of VLAs and he’s proud of that fact
•there is a lot of demo/academic code on VLAs and even less material out there in the C
training world
•when using a structure, we can declare an array without a dimension and whose size is
flexible in nature
•a flexible array member’s size is variable (can be changed be at runtime)
struct s {
int arraySize;
int array[];
}; // end struct s
• some argue that you can just declare an array of size 0 or 1 and reallocate
• in previous standards of the C language, it was common to declare a zero-sized array member instead of a flexible
array member
• using non-standardized constructs to support flexible array members can yield undefined behavior
• bad practice and any undefined-behavior should be avoided
• a struct with a flexible array member reduces the number of allocations for it by ½
• instead of 2 allocations for one struct object you need just 1
• meaning less effort and less memory occupied
• you save the storage for one additional pointer
• if you have to allocate a large number of struct instances, you can measurably improve the runtime and memory usage
of your program (by a constant factor)
•a is called the real part, and bi is called the imaginary part of the
complex number
•Modulus
•the modulus of a complex number a + bi is √(a 2 + b 2)
•Equality
•the complex numbers a + bi and c + di are equal if a equals c and b equals d
•Addition
•the sum of the complex numbers a + bi and c + di is (a + c) + (b + d)i
•Division
•the result of dividing the complex number a + bi by c + di is (ac
+ bd) / (c2 + d2) + ((bc − ad)/(c2 + d2))i
•Conjugate
•the conjugate of a complex number a + bi is a − bi
• you can test whether your compiler supports complex arithmetic using preprocessor directives
#ifdef __STDC_NO_COMPLEX__
printf("Complex arithmetic is not supported.\n");
#else
printf("Complex arithmetic is supported.\n");
#endif
• when the code executes, you will see output telling you whether complex arithmetic is supported
ADVANCED C PROGRAMMING COURSE ADVANCED DATA TYPES
Complex Number Types ACP-0480-5
Complex and Imaginary types in C
• float _Complex
• stores a complex number with real and imaginary parts as type float
• double _Complex
• stores a complex number with real and imaginary parts as type double
• float _Imaginary
• stores an imaginary number as type float
• double _Imaginary
• stores an imaginary number as type double
double _Complex z1; // Real and imaginary parts are type double
•the _Complex keyword was chosen for the same reasons as type _Bool
•to avoid breaking existing code
•with the complex.h header included, you can declare the variable z1 like this
double complex z1; // Real and imaginary parts are type double
•casting an imaginary value to a complex type produces a complex number with a zero
real part and a complex part the same as the imaginary number
•casting a value of an imaginary type to a real type other than _Bool results in 0
•casting a value of an imaginary type to type _Bool results in 0 for a zero imaginary value
and 1 otherwise
•you append an f to these function names when you are working with float
complex values (crealf() and cimagf())
•you append a lowercase l when you are working with long double complex
values (creall() and cimagl())
ADVANCED C PROGRAMMING COURSE ADVANCED DATA TYPES
Complex Number Types ACP-0480-9
Complex Functions
•the conj() function returns the complex conjugate of its double complex
argument
•the conjf() and conjl() functions return the complex conjugate for the other two
complex types
•you can write arithmetic expressions involving complex and imaginary values
using the arithmetic operators +, - , *, and /
•you can also use the operators !, ++, --, &&, ||, ==, != and unary & with complex
numbers
ADVANCED C PROGRAMMING COURSE ADVANCED DATA TYPES
Complex Number Types ACP-0480-10
Creating Complex Numbers
• to construct complex numbers you need a way to indicate the imaginary part of a number
• there is no standard notation for an imaginary floating point constant
• complex.h defines two keywords that can be used to create complex numbers
• a representation of the complex number “0+1i”
• the above (I) can causes problems if you want to use the identifier I for something else
• #include <complex.h>
• #undef I
•the C90 standard required the elements of an initializer to appear in a fixed order
•the same as the order of the elements in the array or structure being initialized
•the c99 standard allows you to initialize the elements in random order
•specifying the array indices or structure field names they apply to
•can be very useful if you have a struct that contains a large number of fields and you
initially just want to set a few of them
is equivalent to
• to initialize a range of elements to the same value, write ‘[first … last] = value’
• we will discuss the const, volatile, and restrict type qualifiers in the upcoming lectures
• these properties are declared with the keywords const and volatile, which create qualified types
•one of the motivations for the const attribute in the language is that it
allows the compiler to place your const variables into read-only
memory
• const variables are actual variables like any other normal variable
• we can pass them around, typecast them and any other thing that can be done with a
normal variable
• another advantage of using a const over a #define macro is that a const variable provides for
type checking by the compiler
• it is provided so that a program can tell the compiler to suppress various kinds of optimizations
• prevents the compiler from optimizing away seemingly redundant assignments to a variable
• prevents the compiler from repeated examination of a variable without its value seemingly
changing
• the reason for having this type qualifier is mainly because of the problems that are
encountered in real-time or embedded systems programming
• programs that have a lot of threading
• programs where resources are scarce
•the optimizer must be careful to reload the variable every time it is used instead
of holding a copy in a register
val2 = x;
• a smart (optimizing) compiler might notice that you use x twice without changing its value
• would temporarily store the x value in a register
• when x is needed for val2, it can save time by reading the value from a register instead of from the original
memory location
• this optimization is not desired if x is changed between the two statements by some other agency
• you would use the volatile keyword to ensure that the compiler does not optimize and instead has a stored
value for each variable
• if the volatile keyword is not used in the declaration, the compiler can assume that a value has not changed
between uses, and it can then attempt to optimize the code
*outPort = 'O';
*outPort = 'N';
• a smart compiler might notice two successive assignments to the same location
• because outPort is not being modified in between, the compiler would remove the first assignment
from the program
• without the restrict keyword, the compiler has to assume the worse case
• that some other identifier might have changed the data in between two uses of a pointer
• with the restrict keyword used, the compiler is free to look for computational shortcuts
• if a programmer uses restrict keyword and violate the above condition, result is undefined behavior
•tells the compiler that, for the duration of the scope in which intPtrA
and intPtrB are defined
•they will never access the same value
0 10101
1 0101010
10 1011110101
01 0110101110
111000 000111
ADVANCED C PROGRAMMING COURSE BIT MANIPULATION
Binary Numbers and Bits ACP-0680-2
Binary Numbers
• every binary number has a corresponding Decimal value (and vice versa)
• Examples:
• in general, the "position values" in a binary number are the powers of two
128 64 32 16 8 4 2 1
--------------------------------------------------------------------------------------------------------------------
0 1 1 0 1 0 0 1
1X1 =1
0X2 =0
0X4 =0
1X8 =8
0 X 16 =0
1 X 32 = 32
1 X 64 = 64
0 X 128 =0
----
Answer: 105
128 64 32 16 8 4 2 1
----------------------------------------------------------------------------------------------------------------
1 0 0 1 1 1 0 0
0X1 =0
0X2 =0
1X4 =4
1X8 =8
1 X 16 = 16
0 X 32 =0
0 X 64 =0
1 X 128 = 128
----
Answer: 156
• the rightmost bit of a byte is known as the least significant or low-order bit, whereas the leftmost bit is
known as the most significant or high-order bit
• the advantage of grouping bits into bytes, words, and so on is that it makes them easier to handle
BIT _Bool 1 0 to 1
Byte char 8 –128 to 127
Word short int 16 –32,768 to 32,767
Long long int 32 –2,147,483,648 to 2,147,483,647
•In twos complement notation, the value −1 is represented by all bits being equal to 1
11111111
• a hardware device is often controlled by sending it a byte or two in which each bit has a particular meaning
• operating system information about files often is stored by using particular bits to indicate particular items
• C’s ability to provide high-level language facilities while also being able to work at a level typically reserved for assembly
language makes it a preferred language for writing device drivers and embedded code
• a bitwise operation operates on one or more binary numbers at the level of their individual bits
• used to manipulate values for comparisons and calculations
• substantially faster than division, several times faster than multiplication, and sometimes
significantly faster than addition
• they operate on each bit independently of the bit to the left or right
• do not confuse them with the regular logical operators (&&, ||, and !), which operate on values
• all of the logical operators listed in the table (with the exception of the ones complement operator ~)
are binary operators
• take two operands
• one major use of the bitwise AND, &, and the bitwise OR, |, is in operations to test and set individual bits in an
integer variable
• can use individual bits to store data that involve one of two choices
• you could use a single integer variable to store several characteristics of a person
• store whether the person is male or female with one bit
• use three other bits to specify whether the person can speak French, German, or Italian
• another bit to record whether the person’s salary is $50,000 or more
• in just four bits you have a substantial set of data recorded
• to set the low-order bit of an int called w1 to 0, you can AND w1 with an int consisting of all 1s except for a
single 0 in the rightmost bit
w1 &= 0xFFFFFFFE;
w1 &= ~1;
• w1 gets ANDed with the correct value on any machine because the ones complement of 1 is calculated and
consists of as many leftmost one bits as are necessary to fill the size of an int (31 leftmost bits on a 32-bit
integer system)
• the bitwise negation operator (~) inverts each bit in its operand, converting 1s to 0s, and vice versa
• the bitwise AND operator (&) forms a value from two operands
• each bit in the value is set to 1 if both corresponding bits in the operands are 1
• otherwise, the bit is set to 0
• the bitwise OR operator (|) also forms a value from two operands
• each bit in the value is set to 1 if either or both corresponding bits in the operands are 1
• otherwise, the bit is set to 0
• the bitwise EXCLUSIVE OR operator (^) acts similarly, except that the resulting bit is set to 1 only if one or the other, but
not both, of the corresponding bits in the operands is 1
• for the right-shift operator, the vacated bits are set to 0 if the value is unsigned
w1 = w1 << 1;
int x= 1;
int y;
y= x<< 2; /* assigns 4 to y*/
x<<= 2; /* changes x to 4 */
• left shifting has the effect of multiplying the value that is shifted by two
•right shifting an unsigned value always results in 0s being shifted in on the left (through the high-order
bits)
•If w1 is an unsigned int, which is represented in 32 bits, and w1 is set equal to 4151832098
w1 >>= 1;
• each bit is moved two places to the right, and the vacated places are filled with 0s
• right shifting has the effect of dividing the value that is shifted by two
• a mask can be used to set multiple bits in a byte to either on, off or inverted from on to off (or vice versa) using a single
bitwise operator
• imagine you want to create a program that holds a state, which is based on multiple values that are one(true) or
zero(false)
• can store these values in different variables (booleans or integers)
• or instead use a single integer variable and use each bit of its internal 32 bits to represent the different true and
false values
00000101
• the the first bit (reading from right to left) is true, which represents the first variable
• the 2nd is false, which represents the 2nd variable. The third true. And so on.
•to avoid information peeking around the edges, a bit mask should be
at least as wide as the value it’s masking
•the above would cause all the bits of flags (except bit 1) to be set to 0
•any bit combined with 0 using the & operator yields 0
•bit number 1 will be left unchanged
•If the bit is 1, 1 & 1 is 1
•if the bit is 0, 0 & 1 is 0
•we are “using a mask” because the zeros in the mask hide the corresponding bits in flags
• you can think of the 0s in the mask as being opaque and the 1s as being transparent
• the expression flags & MASK is like covering the flags bit pattern with the mask
• only the bits under MASK’s 1s are visible
•sets bit number 1 in flags to 1 and leaves all the other bits unchanged
•any bit combined with 0 by using the | operator is itself
•any bit combined with 1 by using the | operator is 1
if (flags == MASK)
puts("Wow!"); /* doesn't work right */
•even if bit 1 in flags is set to 1, the other bit setting in flags can make the comparison
untrue
•you must first mask the other bits in flags so that you compare only bit 1 of flags with
MASK
• you can pack information into the bits of a byte if you do not need to use the entire byte to represent the data
• flags that are used for a boolean true or false condition can be represented in a single bit on a computer
• two methods are available in C that can be used to pack information together to make better use of memory
• bit fields and bitwise operators
• you could use an unsigned int/long variable to hold the same information
• OR you could use a structure the same size as unsigned int to hold state information
•flags that are used for a boolean true or false condition can be represented in a single bit
on a computer
•each bit in the int can be set to 1 (true) or 0 (false)
10111001
•we can access the desired bits of the int using the bit operators provided by C
•first bit is true, second bit is false, third bit is true (each bit represents a flag)
•we are essentially storing eight different values in a single int
• storing the values of the flags f1, f2, and f3 only requires three bits of storage
• one bit for the true/false value of each flag
• storing the value of the int type requires eight bits of storage
• the total amount of storage needed to store the five data values is 29 bits
• you could define an integer variable that could be used to contain all five of these values
•you can then arbitrarily assign specific bits or fields inside packed_data to
be used to store the five data values
•packed_data has three unused bits
•to set the type field to the value n, where n is between 0 and 255
•to ensure that n is between 0 and 255, you can AND it with 0xff before it is shifted
• to save yourself of having to calculate the bitmask and also to make the operation independent of the size of an integer
• you could instead use the below statement to set the type field to zero
• combining the statements described previously, you can set the type field of packed_data to the value contained in the eight
low-order bits of n
packed_data = (packed_data & ~(0xff << 18)) | ((n & 0xff) << 18);
• you can see how complex the above expression is for accomplishing the relatively simple task of setting the bits in the type
field to a specified value
• you store the intensity of each color in its own unsigned char variable and use a bit mask to set/read
• the code uses the right-shift operator to move the 8-bit color value to the low-order byte
• then uses the mask technique to assign the low-order byte to the desired variable
• the long color variable can be used to calculate three other colors (without using additional variables)
• you can pack information into the bits of a byte if you do not need to use the entire byte to represent the data
• flags that are used for a boolean true or false condition can be represented in a single bit on a computer
• two methods are available in C that can be used to pack information together to make better use of memory
• bit fields and bitwise operators
• you could use an unsigned int/long variable to hold the same information
• OR you could use a structure the same size as unsigned int to hold state information
•bit fields enable better memory utilization by storing data in the minimum number of
bits required
•format enables you to allocate a specified number of bits for a data item
•can easily set and retrieve its value without having to use masking and shifting
•bit-field members of structures are accessed exactly as any other structure member
•it is possible to specify an unnamed bit field to be used as padding in the structure
•an unnamed bit field with a zero width is used to align the next bit field on a new storage unit
boundary.
• the flags f2 and f3 are similarly defined as being a single bit in length
• the member type is defined to occupy eight bits
• the member index is defined as being 18 bits long
• the fields of a variable defined to be of type packed_struct can now be referenced in the same convenient way normal
structure members are referenced
• you can easily set the type field of packed_data to 7 with the simple statement
packed_data.type = 7;
• or you could set this field to the value of n with the similar statement
packed_data.type = n;
• you do not need to worry about whether the value of n is too large to fit into the type field
• only the low-order eight bits of n will be assigned to packed_data.type
n = packed_data.type;
•bit fields can be used in normal expressions and are automatically converted to integers
i = packed_data.index / 5 + 1;
if ( packed_data.f2 )
struct table_entry {
int count;
char c;
unsigned int f1:1;
unsigned int f2:1;
};
• you can initialize a bit-field structure by using the same syntax regular structures use
•a goto statement causes program control to jump to a particular line of code in your
program
•this branch is made immediately and unconditionally upon execution of the goto
•to identify where in the program the branch is to be made, a label is needed
•a label is a name that is formed with the same rules as variable names
•the label is placed directly before the statement to which the branch is to be made and
must appear in the same function as the goto
Form:
•for these reasons, goto statements are not considered part of good
programming style
• most gotos are used for helping ifs, simulating if elses, controlling loops, or are just there because you
have programmed yourself into a corner
• instead of skipping to the end of a loop and starting the next cycle using a goto statement, use
continue
• instead of leaving a loop using a goto statement, use break
• actually, break and continue are specialized forms of a goto statement
• the advantages of using them are that their names tell you what they are supposed to do
• also they do not use labels and thus, there is no danger of putting a label in the wrong place
• If you are used to using goto statements, try to train yourself not to
• you have a direct exit from the complete nest of loops without any complicated decision making in the outer loop levels
•the null statement has the effect of doing nothing, but exists for syntactical
reasons
•it is useful when the syntax of the language calls for a statement but no
expression evaluation
•all of the operations are performed inside the looping-conditions part of the while statement
•the null statement is needed because the compiler takes the statement that follows the
looping expression as the body of the loop
•without the null statement, whatever statement that follows in the program is treated as the
body of the program loop by the compiler
• the next for statement counts the number of characters that appear in the standard input
• the following loop copies the character string pointed to by from to the one pointed to by to
•in this case the inner else and null statement keeps the outer else
from binding to the inner if
•a binary operator that evaluates its first operand and discards the result
•then evaluates the second operand and returns this value (and type)
•because all operators in C produce a value, the value of the comma operator is
that of the rightmost expression
•the comma operator can be used to separate multiple expressions anywhere that a
valid C expression can be used
•the comma operator exists because there are times when you do not want to
separate expressions with semicolons
• normal program flow in C follows function calls and branching constructs (if, while, etc.)
• functions setjmp and longjmp introduce another kind of program flow
• suppose there is an error deep down in a function nested in many other functions and error handling makes sense only in the top
level function
• would be very tedious and awkward if all the functions in between had to return normally and evaluate return values
• would be very tedious if you used a global error variable to determine that further processing doesn't make sense or even
would be bad
• you can use setjmp and longjmp for error handling so that you can jump out of deeply nested call chain without needing to deal
with handling errors in every function in the chain
• int setjmp(jmp_buf j)
• use the variable j to remember where you are now
• must be called first
• often referred to as “unwinding the stack,” because you unroll activation records from the stack until
you get to the saved one
• the header file <setjmp.h> needs to be included in any source file that uses setjmp or longjmp
•you can only longjmp back to somewhere you have already been,
where you did a setjmp, and that still has a live activation record
#include <stdio.h>
• this include file contains function declarations and macro definitions associated with the I/O routines from the standard library
• I would like to cover the following I/O functions in this section so that you have a complete understanding of input and output in C
• when a C program is executed, three files are automatically opened by the system for use by the program
• stdin, stdout, and stderr (defined in <stdio.h>)
• stdin identifies the standard input of the program and is normally associated with your terminal window
• all standard I/O functions that perform input and do not take a FILE pointer as an argument get their input
from stdin
• stdout refers to the standard output, which is normally also associated with your terminal window
• stderr identifies the standard error file
• where most of the error messages produced by the system are written and is also normally associated with
your terminal window
• has the effect of reading a single character from the file data
– subsequent characters can be read from the file simply by making additional calls to the getc() function
• this function returns the corresponding integer value (ASCII value of read character) on success
• this function will return the value EOF (special int value used to indicate failure) when the end of file is reached
• the getchar() function requires no arguments, and it returns the code for the character read from the input stream as type int
int getchar(void);
#include <stdio.h>
int main()
{
printf("%c", getchar());
return(0);
}
•when reading from the keyboard as opposed to the file, most systems have a way
to simulate an end-of-file condition from the keyboard
•you cannot just type the letters E O F, and you can’t just type –1
•pressing Ctrl+D at the beginning of a line causes the end-of-file signal to be
transmitted in Linux
•some interpret a Ctrl+Z anywhere as an end-of-file signal
INTERMEDIATE C PROGRAMMING INPUT AND OUTPUT
Input and Output ACP-0920-7
input functions - getchar with EOF example
#include <stdio.h>
int main(void) {
int ch;
• the value returned by getchar() is assigned to an int and not a char variable
– works fine because C allows you to store characters inside ints
• always remember to store the result of getchar() inside an int so that you can properly detect an end-of-file condition
– if you store the result of the getchar() function inside a char variable, the results are unpredictable
• on systems that do sign extension of characters, the code might still work okay
fgetc (fp);
fp – file pointer
INTERMEDIATE C PROGRAMMING INPUT AND OUTPUT
Input and Output ACP-0920-9
input functions – fgetc example
/* Open, Read and close a file: Reading char by char */
# include <stdio.h>
int main( ) {
FILE *fp ;
char c ;
if ( fp == NULL ) {
printf ( "Could not open file myFile.c" ) ;
return 1;
}
# include <stdio.h>
int main( ) {
FILE *fp ;
char c ;
if ( fp == NULL ) {
printf ( "Could not open file myFile.c" ) ;
return 1;
}
• this function returns a value of type int that corresponds to the character pushed back into the stream, or a special character, EOF, if the operation fails
• you can push a succession of characters back into an input stream, but only one character is guaranteed
– you should check for EOF this if you are attempting to return several characters to a stream
• this function is useful when you are reading input character by character and do not know how many characters make up a data unit
– might be reading an integer value, but do not know how many digits there are
void eatspaces(void)
{
char ch = 0;
while(isspace(ch = (char)getchar())); // Read as long as there are spaces
ungetc(ch, stdin); // Put back the nonspace character
}
• the while loop continues to read characters as long as they are whitespace characters, storing each character in ch
• the first nonwhitespace character that is read will end the loop, and the character will be left in ch
• the call to ungetc() returns the nonwhitespace character back to the stream for future processing
putc(‘\n’, stdout);
OR
• has the effect of writing a newline character into the file identified by the FILE pointer outputFile
• buffer is a pointer to a character array where the line that is read in will be stored
• n is an integer value that represents the maximum number of characters to be stored into buffer, including the null
character
• stream is the pointer to object that identifies the stream where characters are read from
• usually used with a file stream, however, standard input stream is also acceptable
• reads characters from the specified file until a newline character has been read or until n-1 characters have been read
(whichever occurs first)
• a null character is written immediately after the last character read into the array
• returns the value of buffer if the read is successful
• returns the value NULL if an error occurs on the read or if an attempt is made to read past the end of the file
• the fgets function protects against overflowing the string and creating a security hazard
• not recommended for performance reasons
• fgets is deprecated because the function cannot tell whether a null character is included in the string it
reads
• if a null character is read it will be stored in the string along with the rest of the characters read
• since a null character terminates a string, this will end your string prematurely, right before the first
null character
• only use fgets if you are certain the data read cannot contain a null character
• otherwise, use getline
•the getline function is the preferred method for reading lines of text
from a stream (including standard input)
•the other standard functions, including gets, fgets, and scanf, are too
unreliable
• the first parameter is a pointer to a block allocated with malloc or calloc (type char **)
• the address of the first character position where the input string will be stored
• this pointer type (a pointer-pointer) causes massive confusion
• will automatically enlarge the block of memory as needed (realloc)
• there is never a shortage of space (why getline is so safe)
• will contain the line read by getline when it returns
• the third parameter is simply the stream from which to read the line
• if an error occurs, such as end of file being reached without reading any bytes, getline returns -1
• otherwise, returns the number of characters read (up to and including the newline, but not the final null
character)
• 32 bytes of storage are assigned to memory location buffer via the malloc() function
• handles the (rare) condition when memory isn’t available. Odds are low that would happen in this program, but it’s good
programming practice to check
• the getline() function uses the address of buffer, bufsize, and then stdin for standard input
• because variable characters is a size_t variable type, the %zu placeholder is used in the printf() function
• as with the fgets() function, getline() reads and stores the newline character as part of the string
• It is simpler than printf, since you do not need to include a newline character
• the difference between puts and printf is that when using printf the argument is interpreted as a
formatting string
• result will be often the same (except for the added newline) if the string doesn't contain any control
characters (%)
• if you cannot rely on that you should not use puts
• the puts function is safe and simple, but not very flexible as it does not give us an option of formatting
our string
• when using sprintf (), you can combine several data variables into a character array
• instead of printing on the console, you store output data to a char buffer (convert)
• the first parameter is a char pointer that specifies where to send output (buffer to put the data in)
• terminates the string with a null character
• the function returns the number of characters stored in the string, not including the terminating null
• this function will behave unpredictably if the string to which it is printing overlaps any of its arguments
• you need to make sure the size of the buffer to be written to is large enough to avoid buffer overruns
• sprintf is unsafe because it doesn't check the length of the destination buffer
• can cause the function to overflow the destination buffer when the result of the format string is unexpectedly long
• leads to security issues and application instability
• overflows can cause unexpected results
• writes the indicated error message to stderr if the file data cannot be opened for reading
• if the standard output has been redirected to a file, this message still appears in your window
• the additional arguments should point to already allocated objects of the type specified by their corresponding
format specifier within the format string
• returns the number of arguments that are successfully read and assigned (on success)
• returns the value EOF, if the end of the file is reached before any of the conversion specifications have been
processed
• reads in the next integer value from the file “myFile” and stores it in the variable i
int sscanf(const char *str, const char * control_string [ arg_1, arg_2, ... ]);
sscanf (buffer,”%s %d”,name,&age);
•the first argument is a pointer to the string from where we want to read the data
•returns the number of items read from the string and -1 if an error is encountered
• if you only use fscanf() on stdin, it will read them off different lines if it does not find the second
value on the first line you entered
• if you read a line that you are unable to parse with sscanf() after having read it using fgets()
your program can simply discard the line and move on
• If you read a line using fscanf(), when it fails to convert fields, it leaves all the input on the
stream
• so, if you failed to read the input you wanted, you would have to go and read all the data you
want to ignore yourself
• you can use fscanf() by itself, however, you may be able to avoid some headaches by using
fgets() + sscanf()
• the below is a common question asked by many students in the Q&A (so mention this, another tidbit, unrelated)
• when scanf() converts an int for example, it will leave behind a \n on the input stream (assuming there was one, like
from pressing the enter key)
• will cause a subsequent call to fgets() to return immediately with only that character in the input
• a really common issue for new programmers
• you can use it with an update stream (any of the read-write modes), provided that the most recent
operation using the stream was not input
• we typically use a variadic function when we do not know the total number of arguments that will be used for
a function
• one single function could potentially have n number of arguments
• a variadic function will contribute to the flexibility of the program that you are developing
• the concept of a variadic function is already used in several C’s built-in functions
• in printf when you want to print one number or many numbers
• printf(" the one number = %d", nOneNumber);
• printf(" the first number = %d, the second number =%d ", nOneNumber, nSecondNumber);
• if you look at the stdio.h header, you can see that this was implemented using variadic functions
• you may come up with a need to do this yourself from time to time, so the standard library stdarg.h provides
you with routines to write some of your own variadic functions
•common practice is to have some number that will tell us how many arguments
there are (as the first argument)
ADVANCED C PROGRAMMING COURSE ADVANCED FUNCTION CONCEPTS
Variadic Functions ACP-1120-3
Creating a variadic function
• when creating a variadic function, you must understand how to reference the variable number of arguments
used inside the function
• you do not know how many there are and you cannot possibly give them names
• you can solve this problem indirectly, through pointers
• the stdarg.h library header provides you with routines that are implemented as macros (look and operate like
functions)
• you need to use these when implementing your own function with a variable number of arguments
• va_list
• used in situations in which we need to access optional parameters and it is an argument list
• represents a data object used to hold the parameters corresponding to the ellipsis part of the parameter
list
• va_start
• will connect our argument list with some argument pointer
• the list specified in va_list is the first argument and the second argument is the last fixed parameter
•va_end
•used in situations when we would like to stop using are variable
argument list (cleanup)
•va_copy
•used in situations for which we need to save our current location
•discussed in next lecture
• call va_start() with this as the first argument and specify the last fixed parameter v2 as the second argument
• effect of the call to va_start() is to set the variable parg to point to the first variable argument that is passed to the
function when it is called
• still do not know what type of value this represents
• If the first argument is 10.0, the above code for tic works fine
• if the argument is 10, the code may not work
• the automatic conversion of double to int that works for assignment doesn’t take place here
va_end(ap); // clean up
• must call va_start() to initialize the value of the variable argument list pointer in your function
• this pointer must be declared as type va_list
• you must have a way to determine when the list of arguments is exhausted
• for example, the last argument in the variable argument list could have a fixed value called a sentinel value that can be
detected because it’s different from all the others
• OR the first argument could specify the count of the number of arguments in total or in the variable part of the argument
list
• you must call va_end() before you exit a function with a variable number of arguments
• If you fail to do so, the function will not work properly
• it is possible that you may need to process a variable argument list more than once
• may be useful to preserve a copy of the va_list type variable
• use va_copy() - two arguments are both type va_list variables, copies the second argument to the first
va_list parg;
va_list parg_copy;
va_copy(parg_copy, parg);
• do not use the va_list object parg_copy as the destination for another copy operation before you have executed va_end() for parg_copy
• recursive functions can be effectively used to succinctly and efficiently solve problems
• commonly used in applications in which the solution to a problem can be expressed in terms of
successively applying the same solution to subsets of the problem
• you are unlikely to come across a need for recursion very often
• provides considerable simplification of the code needed to solve particular problems
• it takes a great deal of practice writing recursive programs before the process will appear natural
void Looper(void) {
printf("Looper function called.\n");
Looper(); // Recursive call to Looper()
}
• a function that calls itself must contain a conditional test (base case) that terminates the
recursion
ADVANCED C PROGRAMMING COURSE ADVANCED FUNCTION CONCEPTS
Recursion ACP-1180-3
Example
int factorial(int n) {
//Factorial of 0 is 1 (base case is 0, return 1)
if(n==0) return(1);
return(n*factorial(n-1));
}
• when a recursive function is called with a base case, the function simply returns a result
• the recursion step can result in many more such recursive calls as the function keeps working on the smaller problem
• for recursion to terminate, the sequence of smaller and smaller problems must converge on the base case
• when the function recognizes the base case, the result is returned to the previous function call
• a sequence of returns ensues all the way up the line until the original call of the function eventually returns the final result
5! = 5 x 4 x 3 x 2 x 1 = 120
• in the general case, the factorial of any positive integer n greater than zero is equal to n
multiplied by the factorial of n - 1
n! = n x (n - 1)!
• the expression of the value of n! in terms of the value of (n-1)! is called a recursive definition
• the definition of the value of a factorial is based on the value of another factorial
•it would be a good idea for you to trace through the operation of the factorial()
function with a pencil and paper
•assume that the function is initially called to calculate the factorial of 4
•list the values of n and result at each call to the factorial() function
void print(int n) {
if (n < 0) return;
• C99 added the concept of inline functions to try and avoid the amount of overhead that comes
along with invoking a function
• the point of making a function inline is to hint to the compiler that it is worth making some form of
extra effort to call the function faster than it would otherwise
• usually the compiler will substitute the code of the function into its caller (eliminating the need
for a call and return sequence)
• the program no longer calls that function, the compiler replaces every call to an inline
function with the code body of that function
• the inline declaration is only advice to the compiler, which can decide to ignore it
• may cause the compiler to replace the function call with inline code and/or perform some other
sorts of optimizations, or it may have no effect
• making a function inline has no effect on the logic of the program from the user’s perspective
• it is suggested to only declare functions as inline if they are short and called frequently
• for a long function, the time consumed in calling the function is short compared to the time spent executing the
body of the function
• no great savings in time using an inline version
• there are different places to create inline function definitions (same file or header file)
• for the compiler to make inline optimizations, it has to know the contents of the function definition
• the inline function definition has to be in the same file as the function call (internal linkage)
• should always use the inline function specifier along with the static storage-class specifier (using extern less portable)
• inline functions are usually defined before their first use in a file (definition also acts as a prototype)
// foo.h
#ifndef FOO_H_
#define FOO_H_
• an inline function is an exception to the rule of not placing executable code in a header file
• because the inline function has internal linkage, defining one in several files does not cause problems
•the purpose of this specifier is to inform the user and the compiler that a particular
function will not return control to the calling program when it completes execution
•informing the user helps to prevent misuse of the function
•informing the compiler may enable it to make some code optimizations
•just like the inline function specifier, the _Noreturn function specifier is a hint to the
compiler
•using the _Noreturn function specifier does not stop a function from returning to its
caller
•only a promise made by the programmer to the compiler to allow it some more
degree of freedom to generate optimized code
•the degree of acceptance is implementation defined
•note that this specifier is different from the void return type
•a typical void function does return to the calling function
•it just does not provide an assignable value
•if a function specified with the _Noreturn function specifier violates its promise and
eventually returns to its caller (by using an explicit return statement or by reaching end of
function body_
•the behavior is undefined
•You MUST NOT return from the function
•compilers are encouraged, but not required, to produce warnings or errors when a
_Noreturn function appears to be capable of returning to its caller
ADVANCED C PROGRAMMING COURSE ADVANCED FUNCTION CONCEPTS
_Noreturn Functions ACP-1260-3
Using _Noreturn
•the _Noreturn keyword appears in a function declaration
•the _Noreturn specifier may appear more than once in the same
function declaration
•the behavior is the same as if it appeared once
int main(void) {
my_exit();
return 0;
}
void func(void) {
printf("In func()...\n");
} /* Undefined behavior as func() returns */
int main(void) {
func();
return 0;
}
int main(void) {
printf("Ready\n");
foo();
•it is your responsibility to ensure that the data in a union is referenced with the
proper data type
•referencing data in a union with a variable of the wrong type is a logic error
•you could use a union to represent a table that stores a mixture of types in
some order
• every time you create an instance of a struct, the computer will lay out the fields in memory, one after
the other
• allocates storage space for all its members separately
• if you have a union called quantity, with fields called count, weight, and volume
• whether you set the count, weight, or volume field, the data will go into the same space in memory
https://www.studytonight.com/c/unions-in-c.php
• you should use a structure when your construct should be a group of other things
• you should use a union when your construct can be one of many different things but only one at a
time
• unions are typically used in situations where space is premium but more importantly for exclusively
alternate data
• unions share a common storage space where structures store several data types simultaneously
• a structure can hold an int and a double and a char
• a union can hold an int or a double or a char
https://www.geeksforgeeks.org/difference-structure-union-c/
• the union tag is optional and each member definition is a normal variable definition
• at the end of the union's definition, before the final semicolon, you can specify one or more union variables but it is optional
• the union definition is normally placed in a header and included in all source files that use the union type
• the above does not define data to contain three distinct members called i, f, and str
• it defines data to contain a single member that is called either i, f, or str
• a variable of Data type can store an integer, a floating-point number, or a string of characters
• a single variable (same memory location) can be used to store multiple types of data
• the memory occupied by a union will be large enough to hold the largest member of the union
• Data type will occupy 20 bytes of memory space because this is the maximum space which can be
occupied by a character string
int main() {
union car car1, car2, *car3;
return 0;
}
union car {
int i_value;
float f_value;
} car1, car2, *car3;
int main( ) {
union Data data;
printf( "Memory size occupied by data : %d\n", sizeof(data));
return 0;
}
struct owner {
char socsecurity[12];
};
struct leasecompany {
char name[40];
char headquarters[40];
};
struct car_data {
char make[15];
int status; /* 0 = owned, 1 = leased */
union {
struct owner owncar;
struct leasecompany leasecar;
};
};
struct {
char *name;
enum symbolType type;
union {
int i;
float f;
char c;
} data;
} table [entries];
• the member type might be used to keep track of the type of value stored in the member data
• you could assign it the value INTEGER if it contained an int
• FLOATING if it contained a float
• CHARACTER if it contained a char
• this information would enable you to know how to reference the particular data member of a particular array element
• the dot operator is used with a union name to specify a member of that union
name.member
union {
int code;
float cost;
} item;
item.code = 1265; // assigns a value to the code member of the structure item
• the indirection operator is used with a pointer to a union to identify a member of that union
union {
int code;
float cost;
} item, * ptrst;
ptrst = &item;
ptrst->code = 3451; // assigns an int value to the code member of item
•a program called the preprocessor is invoked before any code gets compiled in the C
programming language
•a separate step in the compilation process
•not part of the compiler
2. includes all of the files from various libraries that the program needs to compile
•#include directive (includes the contents of the library file specified)
•we will learn about the following operators in the next section
•continuation operator (\)
•concatenation operators
•(#) when used within a macro definition, converts a macro parameter into a string
constant
•(##) within a macro definition combines two arguments
•permits two separate tokens in the definition to be joined into a single token
•defined()
•simplifies the writing of compound expressions in certain macro directives
• if you had a large program that had many dependencies on specific hardware or software
• you might end up with many defines whose values would have to be changed when the program was
moved to another computer system
• you can help reduce this problem by incorporating the values of these defines for each different machine
into the program by using the conditional compilation capabilities of the preprocessor
• conditional compilation enables you to control the execution of preprocessor directives and the compilation of
program code
• each of the conditional preprocessor directives evaluates an identifier or a constant integer expression
• cast expressions, sizeof expressions and enumeration constants cannot be evaluated in preprocessor
directives
•most compilers also permit you to define a name to the preprocessor when
the program is compiled
•use the special option –D to the compiler command
#ifdef identifier
• the #endif directive ends the scope of the #if , #ifdef , #ifndef , #else , or #elif directives
#endif newline
#ifndef identifier
•Example
#ifndef SIZE
#define SIZE 100
#endif
#if constant_expression
• the operand must be a constant integer expression that does not contain any increment (++), decrement (- -),
sizeof , pointer (*), address (&), and cast operators
• you can also use relational and logical operators with the #if directive
• the constant expression is subject to text replacement and can contain references to identifiers defined in
previous #define directives
• if an identifier used in the expression is not currently defined, the compiler treats the identifier as though it
were the constant zero
•with an #else directive, everything from the #else to the #endif is done if the
identifier is not defined
• the above has the effect of defining DATADIR to "/uxn1/data" if the symbol UNIX has been previously
defined and to "\usr\data" otherwise
• you are allowed to put one or more spaces after the # that begins a preprocessor statement
• a value can also be assigned to the defined name on the command line
gcc -D DATADIR=/c/my_data
#elif constant_expression
• many include files include other files, so you may include a file explicitly that another include file has already included
• this is a problem because some items that appear in include files, such as declarations of structure types, can appear
only once in a file
• prevents multiple definitions of the same variable/function/macro
• the standard C header files uses the #ifndef technique to avoid multiple inclusions
• one problem is to make sure the identifier you are testing has not been defined elsewhere
• use the filename as the identifier (using uppercase, replacing periods with an underscore, and using an underscore)
#ifndef _STDIO_H
#define _STDIO_H
// contents of file
#endif
• the definition in the first header file included becomes the active definition and subsequent definitions in other header
files are ignored
• the first time the preprocessor encounters this include file, THINGS_H_ is undefined, so the program proceeds to
define THINGS_H_ and to process the rest of the file
• the next time the preprocessor encounters this file, THINGS_H_ is defined, so the preprocessor skips the rest of the
file
• ensures that the contents of a header file cannot be included more than once into a source file
• using an include directive makes it impossible for the contents of MyHeader.h to appear more than once in a source file
• you should always protect code in your own header files in this way
•on some occasions, you might need to cause a defined name to become undefined
•cancels an earlier #define definition
#undef name
#undef LINUX
• this directive is most useful for programs that are unusually large or that need to take advantage of the capabilities of a particular
compiler
• for example, while C99 was being developed, it was referred to as C9X, and one compiler used the following pragma to turn on
C9X support
#pragma c9x on
#pragma token_name
• there are only a limited list of supported tokens for each standard/compiler
• the set of commands that can appear in #pragma directives is different for each compiler
• need to reference the compiler documentation
• a pragma not recognized by the implementation is ignored
• we will be studying the following #pragmas (which are available in the gcc compiler)
• #pragma GCC dependency
• #pragma GCC poison
• #pragma GCC system_header
• #pragma once
• #pragma GCC warning
• #pragma GCC error
• #pragma message
•this pragma is useful if the current file is derived from the other file,
and should be regenerated
•to enforce this, you can poison the identifier with this pragma
•followed by a list of identifiers to poison
•if any of those identifiers appear anywhere in the source after the directive,
an error will be displayed by the compiler
•system headers are header files that come with the OS or compiler
• the #error directive causes the preprocessor to issue an error message that includes any text in the directive
• error message is a sequence of characters separated by spaces
• you do not have to enclose the text in quotes
• the message is optional
Example
#if __STDC_VERSION__ != 201112L // should fail if compiler used is an older standard and succeed when it uses C11
#error Not C11
#endif
• gcc test.c
test.c:8:2: error: #error Not C11
#error Not C11
Incomplete code
#error *** Jason - Function incomplete. Fix before using ***
Compiler-dependent code
#include <float.h>
#include <limits.h>
#if (INT_MAX != 32767)
#error *** This file only works with 16-bit int.
Do not use this compiler! ***
Conditionally-compiled code
#ifdef OPT_1
/* Do option_1 */
#elif defined OPT_2
/* Do option_2 */
#else
#error *** You must define one of OPT_1 or OPT_2 ***
#endif
#warning message
•the #line preprocessor directive is used to set the file name and the line number of
the line following the directive to new values
•used to set the __FILE__ and __LINE__ macros
#line linenum
•when you first learn about macros, you probably think that they are nothing more than a
function call with some strange syntax
•and you would mostly be right, they “behave” similar to normal functions
•macros are a text processing feature and are “expanded” and replaced by macro
definitions
•there are no spaces in the macro name, however, spaces can appear in the
replacement string (macro_value)
• as we have learned in the last lecture, macros are essentially functions, but, with different syntax
• behave just like a function
• however, there are some huge differences (under the hood) and without understanding these
differences, you might be using either one when you should not be
• you must understand that macros are pre-processed which means that all the macros would be
processed before your program compiles
• functions are not preprocessed, they are compiled
• so, now the question becomes, what are the differences and when should I use one vs. the other
• there is no hard-and-fast rule
•if you intend to use a macro instead of a function primarily to speed up a program
•first try to determine whether it is likely to make a significant difference
•a macro that is used once in a program probably will not have any noticeable
improvement in running time
•a macro inside a nested loop is a much better candidate for speed improvements
•many systems offer program profilers to help you pin down where a program
spends the most time
•functions are preferred over macros when writing large chunks of code
• macros are somewhat trickier to use than regular functions because they can have odd side effects if you are not
careful
• some compilers limit the macro definition to one line, and it is probably best to observe that limit, even if your
compiler does not
•when you add the inline keyword in front of a function, you are hinting to the compiler
to embed the function body inside the caller (just like a macro)
•however, the inline keyword is merely a hint to the compiler, it is not a strict rule and
the compiler can decide to ignore the hint
•macros will always have their place and are not going away
• the below definition does not look like a function (symbolic constant)
NONFMAC
/* some text here */
• it gets a bit trickier with the function macro because of the (identifiers) or formal parameters
• function-like macro definitions have one or more arguments in parentheses, and these arguments then
appear in the replacement portion
• to create a macro with arguments, put them in parentheses separated by commas after the macro name
z = SQUARE(2);
• looks like a function call, but it does not necessarily behave identically
•use should use parentheses around each argument and around the definition
as a whole
•ensures that the enclosed terms are grouped properly in an expression
(avoid operator precedence)
•the backslash (\) operator allows for the continuation of a macro to the next
line when the macro is too long for a single line
•a macro is always a single, logical line
#define min(x, y) \
((x)<(y) ? (x) : (y))
•the above macro definition continues on the second physical line with the first
nonblank character found
•you can position the text on the second line to wherever you feel is the most
readable
•the \ must be the last character on the line, immediately before you press Enter
•result is seen by the compiler as a single, logical line
int main(void) {
printf("Here is the boot drive path: %s\n", BOOT_DRIVE);
return 0;
}
•the above will display as output "C:/" if either WINDOWS or WINDOWSNT is defined
•output will be "D:/" otherwise
#define str(x) # x
causes the subsequent invocation
str (testing)
to be expanded into
"testing“
•the # operator must be used in a macro with arguments because the operand of # refers
to an argument of the macro
printint (count);
is expanded into
printf ("count" " = %i\n", count);
•which, after string concatenation is performed on the two adjacent strings, becomes
printf ("count = %i\n", count);
•the # operator gives you a means to create a character string out of a macro argument
•a space between the # and the parameter name is optional
• usually both will be identifiers, or one will be an identifier and the other a preprocessing number
• when pasted, they make a longer identifier
• two tokens that don’t together form a valid token cannot be pasted together
• you cannot concatenate x with + in either order
• this operator is most useful when one or both of the tokens comes from a macro argument
• if either of the tokens next to an ‘##’ is a parameter name
• replaced by its actual argument before ‘##’ executes
into
#define eat( what ) puts( "I'm eating " #what " today." )
eat( fruit )
struct command {
char *name;
void (*function) (void);
};
• it would be cleaner not to have to give each command name twice, once in the string constant and once in the function name
• a macro which takes the name of a command as an argument can make this unnecessary
• the string constant can be created with stringizing, and the function name by concatenating the argument with ‘_command’
• __FILE__
• represent the current file name (string)
• __LINE__
• represent the current line number of the current source code (an integer constant)
• __func__
• the name of any function when placed inside a function of the current file
• not part of the standard
•__TIME__
•the time the source file was compiled (a string literal of the
form "hh:mm:ss")
•__STDC__
•used to indicate if the compiler supports standard C by
returning the value 1
ADVANCED C PROGRAMMING COURSE MACROS
Predefined Macros ACP-1560-3
__FILE__ and __LINE__
• the __FILE__ macro represents the name of the current source file as a string literal
• typically a string literal comprising the entire file path
• "C:\\Projects\\Test\\MyFile.c"
• the __LINE__ macro results in an integer constant corresponding to the current line number
• can use this in combination with the __FILE__ macro to identify where in the source code a
particular event or error has been detected
• if fopen() fails, there will be a message specifying the source file name and the line number within the
source file where the failure occurred
ADVANCED C PROGRAMMING COURSE MACROS
Predefined Macros ACP-1560-4
__func__
•you can always obtain the name of any function in the code that represents the
function body by using the identifier __func__
#include <stdio.h>
void my_function_name(void) {
printf("%s was called.\n", __func__);
}
•this function just outputs its own name within the format string
• a similar macro, __TIME__, provides a string containing the value of the current time in the form
hh:mm:ss
• digits for hours, minutes, and seconds, separated by colons
• the time is when the compiler is executed, not when the program is run
• tou could use this macro to record in the output when your program was last compiled with this
statement:
#include <stdio.h>
int main(void) {
#if (__STDC__ == 1)
printf("Implementation is ISO-conforming.\n");
#else
printf("Implementation is not ISO-conforming.\n");
#endif
/* ... */
return 0;
}
• in this lecture, I would like to describe more details about specific options that you can pass to the compiler that will
help with the process of debugging, optimization and other enhancements
• the compiler that we are using is the gcc compiler (everyone should have installed this)
• a very powerful and popular C compiler for various Linux distributions
• able to use on windows because of Cygwin
• a Unix-like environment and command-line interface for Windows (also includes the bash shell)
• able to use a front end version named clang on mac
• when you invoke GCC, it normally does preprocessing, compilation, assembly and linking
• some of the options to the compiler allow you to stop this process at an intermediate stage
• for example, the -c option says not to run the linker
• other options are passed on to one stage of processing
• some options control the preprocessor and others the compiler itself
• other options control the assembler and linker
https://codeforwin.org/2017/08/c-compilation-process.html
• for the most part, the order you use does not matter
• order does matter when you use several options of the same kind
• for example, if you specify -L more than once, the directories are searched in the order
specified
• many options have long names starting with -f or with -W--for example
• -fforce-mem, -fstrength-reduce, -Wformat and so on
ADVANCED C PROGRAMMING COURSE ADVANCED DEBUGGING, ANALYSIS, AND COMPILER OPTIONS
GCC Compiler Options (part 1) ACP-1620-4
GCC Compiler Options
Jason Fedin
• without any optimization option, the compiler’s goal is to reduce the cost of compilation and to make debugging
produce the expected results
• turning on optimization flags makes the compiler attempt to improve the performance and/or code size at the expense
of compilation time and possibly the ability to debug the program
• the compiler performs optimization based on the knowledge it has of the program
• compiling multiple files at once to a single output file mode allows the compiler to use information gained from all of
the files when compiling each of them
• you can invoke GCC with -Q --help=optimizers to find out the exact set of optimizations that are enabled at each level
• -O1
• optimizing compilation takes somewhat more time, and a lot more memory for a large function
• -O2
• optimize even more
• GCC performs nearly all supported optimizations that do not involve a space-speed tradeoff
• ss compared to -O, this option increases both compilation time and the performance of the generated code
• -O3
• O3 turns on all optimizations specified by -O2 and also turns on more
• -Ofast
• Disregard strict standards compliance. -Ofast enables all -O3 optimizations. It also enables optimizations that are not valid for all standard-compliant
programs. It turns on -ffast-math, -fallow-store-data-races and the Fortran-specific -fstack-arrays, unless -fmax-stack-var-size is specified, and
-fno-protect-parens.
• -Og
• Optimize debugging experience. -Og should be the optimization level of choice for the standard edit-compile-debug cycle, offering a reasonable level of optimization while
maintaining fast compilation and a good debugging experience. It is a better choice than -O0 for producing debuggable code because some compiler passes that collect debug
information are disabled at -O0
•PATH
•for searching the executables and run-time shared libraries (.dll, .so)
•CPATH
•for searching the include-paths for headers
•searched after paths specified in -I<dir> options
•C_INCLUDE_PATH can be used to specify C headers if the particular language was
indicated in pre-processing
•LIBRARY_PATH
•for searching library-paths for link libraries
•it is searched after paths specified in -L<dir> options
ldd a.out
linux-vdso.so.1 (0x00007ffccddde000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9b389cf000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9b38fc2000)
ADVANCED C PROGRAMMING COURSE ADVANCED DEBUGGING, ANALYSIS, AND COMPILER OPTIONS
GCC Compiler Options (part 2) ACP-1630-5
Debugging with the
preprocessor
Jason Fedin
•by using these directives, you can arrange for blocks of code to be included in your program
to assist in debugging
•the addition of tracing code of your own can still be very useful
•you have complete control of the formatting of data to be displayed for debugging purposes
•can even arrange for the kind of output to vary according to conditions or relationships
within the program
•in this lecture, I will show you how to use the preprocessor to allow for the conditional
inclusion of debugging statements in your program
• gdb is a powerful interactive debugger that is frequently used to debug programs compiled with
GNU’s gcc compiler
• GDB can do four main kinds of things to help you catch bugs in the act
• start your program, specifying anything that might affect its behavior
• make your program stop at a predetermined location
• examine what has happened, when your program has stopped (display variables)
• change things in your program (set variables)
• so you can experiment with correcting the effects of one bug and go on to learn about
another
• you can trace your program’s execution and even execute it one line at a time
•GDB can run on most popular UNIX and Microsoft Windows variants,
as well as on Mac OS X
•your C program must be compiled with the gcc compiler using the -g
option to make full use of gdb’s features
•causes the C compiler to add extra information to the output file
•variable and structure types, source filenames, and C statement
to machine code mappings
ADVANCED C PROGRAMMING COURSE ADVANCED DEBUGGING, ANALYSIS, AND COMPILER OPTIONS
Debugging with gdb (part 1) ACP-1660-3
Debugging with gdb
Jason Fedin
•a breakpoint can be set at any line in your program by simply specifying the line number
to the command
•If you specify a line number but no function or filename, the breakpoint is set on that line
in the current file
•if you specify a function, the breakpoint is set on the first executable line in that
function
ADVANCED C PROGRAMMING COURSE ADVANCED DEBUGGING, ANALYSIS, AND COMPILER OPTIONS
Debugging with gdb (part 2) ACP-1670-2
Core files
Jason Fedin
• a segmentation fault is a specific kind of error caused by accessing memory that “does not belong to you”
• a piece of code tries to do read and write operation in a read only location in memory or freed block of memory
• this core file contains a snapshot of the contents of the process’s memory at the time it terminated
• this file is used for debugging purposes (used to assist in diagnosing and fixing errors)
• core dumps allow a user to save a crash for later or off-site analysis, or comparison with other crashes
• core dumps can be used to capture data freed during dynamic memory allocation and may thus be used to retrieve
information from a program that is no longer running
• your system might be configured to disable the automatic creation of this core file, often due to the large size of these files
• to enable writing core files you use the ulimit command
• ulimit -c unlimited
• may have to type in “bash” to make sure you are in a shell
• otherwise command may not be recognized
• analyzing a core file does not work well without debugging information added to the executable (gcc -g)
•Valgrind delivers the most accurate results and is well suited for multi-threaded
applications
•slow execution of the application under test disqualifies it for larger, longer running
applications
• the process provides an understanding of the code structure, and can help to ensure that the code adheres to industry standards
• this type of analysis addresses weaknesses in source code that might lead to vulnerabilities
• could also be achieved through manual code reviews
• software metrics and reverse engineering can be described as forms of static analysis
•the big difference is where they find defects in the development lifecycle
•static analysis identifies defects before you run a program (e.g., between coding
and unit testing)
•dynamic analysis identifies defects after you run a program (e.g., during unit
testing)
•some coding errors might not surface during unit testing
•there are defects that dynamic testing might miss that static code analysis can
find
ADVANCED C PROGRAMMING COURSE ADVANCED DEBUGGING, ANALYSIS, AND COMPILER OPTIONS
Static Analysis ACP-1710-3
Benefits of the Best Static Code Analysis Tools
• the best static code analysis tools offer speed, depth, and accuracy.
• Speed
• It takes time for developers to do manual code reviews
• automated tools are much faster
• Depth
• testing cannot cover every possible code execution path, but a static code analyzer can
• Accuracy
• manual code reviews are prone to human error, however, automated tools are not
• they scan every line of code to identify potential problems
• helps you ensure the highest-quality code is in place (before testing begins)
•in some situations, a tool can only report that there is a possible defect
ADVANCED C PROGRAMMING COURSE ADVANCED DEBUGGING, ANALYSIS, AND COMPILER OPTIONS
Static Analysis ACP-1710-5
Some tools available
• there are a ton of tools available that perform static analysis
• the two tools that I have experience with are coverity and codesonar
• the U.S. Food and Drug Administration uses CodeSonar to detect defects in fielded medical devices
• NASA uses CodeSonar in its Study on Sudden Unintended Acceleration in the electronic throttle control systems of
Toyota vehicles
• by not relying on pattern matching or similar approximations, CodeSonar's static analysis engine is extraordinarily
deep, finding 3-5 times more defects on average than other static analysis tools
• like a compiler, CodeSonar does a build of your code using your existing build environment, but instead of creating
object code, CodeSonar creates an abstract model of your entire program
• from the derived model, CodeSonar’s symbolic execution engine explores program paths
• reasoning about program variables and how they relate
• advanced theorem-proving technology prunes infeasible program paths from the exploration
• checkers perform static code analysis to find common defects, violations of policies, etc
• operate by traversing or querying the model, looking for particular properties or patterns that indicate defects
• an astronomical number of combinations of circumstances must be modeled and explored, so CodeSonar employs a
variety of strategies to ensure scalability
• procedure summaries are refined and compacted during the analysis, and paths are explored in an order that
minimizes paging
• we understand the distinction between the pointer itself and what it points to
• this will help with understanding the concept of pointers to pointers
• we now have to distinguish between the pointer, what it points to, and what the pointer that it points
to points to
• when a pointer holds the address of another pointer, this is known as a pointer-to-pointer or double
pointer
• in theory, we might also end up with pointers to pointers to pointers, or pointers to pointers to pointers to
pointers
• although triple and more pointers are hardly ever used and should not be
•with a double pointer, the first pointer contains the address of the
second pointer, which points to the location that contains the actual
value
int **var;
int x = **var;
*ipp = ip2;
*ipp = &k;
http://c-faq.com/~scs/cclass/int/sx8.html
• you must be careful though when you use pointers of pointers to point to 2 dimensional arrays
• better to use a pointer to a 2 dimensional array instead
void test() {
double **a;
int i1 = sizeof(a[0]); //i1 == 4 == sizeof(double*)
double matrix[ROWS][COLUMNS];
int i2 = sizeof(matrix[0]); //i2 == 240 == COLUMNS * sizeof(double)
}
int (*myPointerTo2DimArray)[ROWS][COLUMNS]
•a function pointer can be used directly as the function name when calling the function
•they are less error prone than normal pointers cause you will never allocate or deallocate
memory with them
•you just need to understand what they are and to learn their syntax
• sorting an array involves comparing two elements to see which comes first
• the qsort() function from the C library is designed to work with arrays of any kind as long as you
tell it what function to use to compare elements
• takes a pointer to a function as one of its arguments
• uses that function to sort the type (whether it be integer, string, or structure)
• another common application for function pointers is to create what is known as dispatch tables
• you can create tables that contain pointers to functions to be called
• you might create a table for processing different commands that will be entered by a user
• each entry in the table could contain both the command name and a pointer to a function to
call to process that particular command
• whenever the user enters a command, you can look up the command inside the table and
invoke the corresponding function to handle it
•the declaration for a pointer to a function looks a little strange and can be confusing
•when you declare a data pointer, you have to declare the type of data to which it
points
•when declaring a function pointer, you have to declare the type of function pointed to
•to specify the function type, you specify the function signature (the return type for
the function and the parameter types for a function)
•points to functions that have one parameter of type int and that return
a value of type int to the calling function
•you can only use this particular pointer to point to functions with
these parameters and return value
•if you want a pointer to functions that accepts a float argument and
returns float values
•you need to declare another pointer with the required characteristics
ADVANCED C PROGRAMMING COURSE ADVANCED POINTERS
Function Pointers ACP-2180-8
Assignment
int (*pfunction) (int);
● to set your function pointer pointing to an existing specific function, you simply assign the name of the function to it
int lookup(int);
pfunction = lookup; or pfunction = &lookup;
● stores a pointer to this function inside the function pointer variable pfunction
● writing a function name without a subsequent set of parentheses is treated in an analogous way to writing an array
name without a subscript
● compiler automatically produces a pointer to the specified function
● an ampersand is permitted in front of the function name, but it’s not required
● if the lookup() function has not been previously defined in the program, it is necessary to declare the function before
the preceding assignment can be made
● you can call the function by applying the function call operator to the pointer
● listing any arguments to the function inside the parentheses
● calls the function pointed to by pfunction, storing the returned value inside the variable
value
● you use the function pointer name just like a function name to call the function that it
points to
● no dereference operator is required
void qsort(void *base, size_t num_elements, size_t element_size, int (*compare)(void const *, void const *));
• the argument base points to the array to be sorted and num_elements indicates how long the array is, with element_size the size in bytes of
each array element
• the final argument, compare is a pointer to a function
• qsort calls the compare function which is user defined to compare the data when sorting
• qsort maintains it's data type independence by giving the comparison responsibility to the user
• the compare function must return certain (integer) values according to the comparison result
• less than zero, if first value is less than the second value
• zero, if first value is equal to the second value
• greater than zero, if first value is greater than the second value
typedef struct {
int key;
struct other_data;
} record;
• assuming that we have an array of array_length records suitably filled with date we can call qsort like this
•void pointers are pointers that point to a value that has no type
void * pointer_name;
• vp is a void pointer, so you can assign the address of any type of variable to it
vp = &a; // ok
vp = ip; // ok
vp = fp; // ok
void *vp;
int a = 100;
vp = &a;
printf("%d", *vp); // wrong
(int *)vp
• the type of vp temporarily changes from void pointer to pointer to int or (int*)
• we already know how to dereference a pointer to int, just precede it with indirection operator (*)
*(int *)vp
• type casting changes type of vp temporarily until the evaluation of the expression, everywhere else in the program vp is
still a void pointer
•you will be able to write code that you can scale and manage simply and
efficiently
•a library consists of one or more object files, which consist of object code
•C functions that can be shared by more than one application should be broken out of
an application's source code and compiled and bundled into a library
•libraries may call functions in other libraries such as the Standard C or math libraries
to do various tasks
•a programmer has the interface file (headers) to figure out how to use a library
• allows application vendors a way to simply release an API to interface with an application
• version management
• libraries can cohabitate old and new versions on a single system
• component specialization
• specialized developers can focus on their core competency on a single library
• after generating the object code, the compiler also invokes the linker
• linking is the processes of collecting and combining multiple object files in order to create a single executable file
• one of the main tasks for linker is to make the code of library functions (eg printf(), scanf(), sqrt(), ..etc) available to your
program
• static linking is the result of the linker making copy of all used library functions to the executable file
• dynamic linking does not require the code to be copied, it is done by just placing the name of the library in the binary file
• linking happens when the program is run, when both the binary file and the library are in memory
• once everything is bundled into your application, you don’t have to worry that the client will
have the right library (and version) available on their system
• static linking creates larger binary files that utilize more disk space and main memory
• once the library is linked and the program is created, you have no way of changing any of the
library code without rebuilding the whole program
• more time consuming to fix bugs
•during compilation of the library, the machine code is stored on your machine
•when you recompile a program that uses this library, only the new code in the
program is compiled
•does not recompile the library into the executable file like in static linking
•the main reason for using dynamic linking of libraries is to free your software
from the need to recompile with each new release of a library
•dynamic linking trades off more efficient use of the disk and a quicker linking phase for a
small runtime penalty
• static libraries
• uses static linking (compile-time, becomes part of the application)
• each process gets its own copy of the code and data
• known as an archive
• there is also the concept of a dynamically loaded/unloaded shared object library that is linked during
execution using the dynamic linking loader system functions
•you can Identify your libraries by looking at the header files you have used
•a good hint is to study the #includes that your program uses
•each header file that you include potentially represents a library against which
you must link
• static libraries are larger in size because the file has to be recompiled every time when adding
new code to the executable
• for shared objects, only one copy of the shared library is kept in memory
• making it much faster to compile programs and significantly reducing the size of the
executable program
• shared objects also may have compatibility issues if a library is changed without recompiling
the library into memory
•the compiler option -Lpathname is used to tell the linker a list of other
directories in which to search for libraries
•if you put your archive somewhere else, like /my_lib?
•the name that follows the -l option needs to match part of the archive
name
•if your archive is called libawesome.a, you can compile your program
with the -lawesome switch
•another problem occurs if you mention the static libraries before your own
code
•won’t be any undefined symbols yet, so nothing will be extracted
•when your object file is processed by the linker, all its library references will
be undefined
• you can use the ldd command on linux to list all of the shared objects for a given
binary/executable
• ldd name-of-executable
•the compiler option -Lpathname is used to tell the linker a list of other directories in
which to search for libraries
•LD_LIBRARY_PATH and LD_RUN_PATH can also be used to provide this information
•better to use the -Lpathname -Rpathname options at linktime instead
•the name that follows the -l option needs to match part of the library name
•if your archive is called libawesome.so, you can compile your program with the
-lawesome switch
• do not confuse the terms static loading vs static linking and dynamic loading vs dynamic linking
•what if we didn’t want to link libraries at compile time (via copy or sharing) and
load symbols at program start-up time
•instead, load them ourselves as needed during runtime
• it is one of the 3 mechanisms by which a computer program can use some other software
• the other two are static linking and dynamic linking (both load at program start up)
• unlike static linking and dynamic linking, dynamic loading allows a computer program to start
up in the absence of these libraries
• can discover available libraries, and to potentially gain additional functionality
• the main difference of dynamic linking to a shared object is that the libraries are not
automatically loaded at program start-up
• the Pluggable Authentication Modules (PAM) system uses DL libraries to permit administrators to
configure and reconfigure authentication
• (DL) libraries are also useful for implementing interpreters that wish to occasionally compile their code
into machine code and use the compiled version for efficiency purposes
• all without stopping
• the Apache Web Server's *.dso "dynamic shared object" plugin files are libraries which are loaded at
runtime with dynamic loading
• also used in implementing computer programs where multiple different libraries may supply the
requisite functionality
• where the user has the option to select which library or libraries to provide
• if libraries depend on each other (e.g., X depends on Y), then you need to load the dependees
first (load Y first, and then X)
• if filename begins with ``/'' (i.e., it's an absolute path), dlopen() will just try to use it
• otherwise, it will search for the library in the LD_LIBRARY_PATH environment variable and
other directories
•the return value of dlopen() is a ''handle'' that should is used by the other DL library
routines
•will return NULL if the attempt to load does not succeed (should always check for
this)
•if the same library is loaded more than once with dlopen(), the same file handle is
returned
• the handle is the value returned from dlopen, and symbol is a NIL-terminated string
• do not store the result of dlsym() into a void* pointer
• you will have to cast it each time you use it (and you'll give less information to other people
trying to maintain the program)
• dlsym() will return a NULL result if the symbol was not found
•it is not a problem for the same program to load the same
library multiple times
ADVANCED C PROGRAMMING COURSE STATIC LIBRARIES AND SHARED OBJECTS
Dynamically loading a shared object ACP-1920-10
Assert
Jason Fedin
• the assert() macro enables you to insert tests of arbitrary expressions in your program
• a run-time check
• the program will be terminated with a diagnostic message if a specified expression is false during execution
• error message is written to the standard error stream (stderr)
• displays the test that failed, the name of the file containing the test, and a line number
• abort() function is invoked which terminates the program
• assertions are not meant as a substitute for error handling during normal runtime conditions
• use should be limited to finding logic errors
•you can see that the function name and the line number of the code are
identified as well as the condition that was not met
• this code snippet will cause all assert() macros in your code to be ignored
• with some nonstandard systems, assertions are disabled by default, in which case you can enable them by
undefining NDEBUG
• by including the directive to undefine NDEBUG, you ensure that assertions are enabled for your source file
• #undef must appear before the #include directive for assert.h to be effective
• the assert.h header makes static_assert an alias for the C keyword _Static_assert
• more compatible with C++, which uses static_assert as its keyword for this feature
• the static_assert() macro enables you to output an error message during compilation
• message includes a string literal that you specify
• whether or not the output is produced depends on the value of an expression that is a compile time constant
static_assert(constant_expression, string_literal);
• when the constant expression evaluates to zero, compilation stops and the error message is output
•CHAR_MIN is defined in limits.h and is the minimum value for type char
•when char is an unsigned type, CHAR_MIN will be zero
•when it is a signed type it will be negative.
•the above will cause compilation to be halted and an error message that includes your
string to be produced when type char is signed
ADVANCED C PROGRAMMING COURSE USEFUL C LIBRARIES
Assert ACP-2320-6
stdlib.h
Jason Fedin
• these standard library functions help make your code more portable and more efficient
•More specifically
•exit
•atexit
•abort
•qsort
•at times, you might want to force the termination of a program earlier than the above
•when an error condition is detected by a program
•input error
•a file to be processed by the program cannot be opened
• the above statement has the effect of terminating (exiting from) the current program
• the integer value status is called the exit status, and has the same meaning as the value returned from main()
• EXIT_FAILURE (symbolic constant) represents an integer value that you can use to indicate the program has
failed (non-zero)
• EXIT_SUCCESS (symbolic constant) represents an integer value that you can use to indicate it has succeeded
(0)
• when a program terminates simply by executing the last statement in main, its exit status is undefined
• you should exit or return from main() with a defined exit status
• the functions registered by atexit() should be type void functions taking no arguments
• cannot have arguments and cannot return a value
•the abort function follows the philosophy of “fail hard and fail often”
•this function causes a noticeable failure because it will dump core and generate a
core file
•note: will not cause the program to terminate if SIGABRT is being caught by a
signal handler passed to signal and the handler does not return
void abort(void);
• first, the array is divided into two parts, with every value in one partition being less than every
value in the other partition
• process continues until the array is fully sorted
• the name for the C implementation of the quick sort algorithm is qsort()
• sorts a data array pointed to by a function pointer (comparison function) passed into the
qsort function
• qsort() and the comparison function use void pointers for generality
• you have to tell qsort() explicitly how large each element of the array is
• you have to convert its pointer arguments to pointers of the proper type for your application
•a pointer to a function that returns an int and that takes two arguments
•each of which is a pointer to type const void
•these two pointers point to the items being compared
•more specifically
•atoi, atof, atol, strtod, strtof, strtol, itoa
•rand and srand
•system
•getenv
•all routines skip leading whitespace characters in the string and stop their scan
upon encountering a character that is invalid for the type of value being
converted
• random numbers can be used for many types of applications from security to use in games
• can be used to generate UUID, Certificates, Passwords etc.
• in order to generate random numbers, you generally need a hardware-based source which
generates random signals
• these signals can then be used to generate a random number
• seed values are used to make a random start from the application point of view
• we generally use functions like time or network traffic as a seed value because they are
changing regularly
•more specifically
•system
•getenv
• gives the command contained in the character array pointed to by command to the system for
execution
• the value returned is -1 on error, and the return status of the command otherwise
• causes the system to create a directory called /usr/tmp/data (assuming you have the proper
permissions to do so)
•the function getenv searches for the environment string that is passed in
•returns a null-terminated string with the value of the requested environment
variable, or NULL if that environment variable does not exist
•the memcpy() and memmove() functions offer you almost the same
convenience for other kinds of arrays
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);
•both of these functions copy n bytes from the location pointed to by s2 to the
location pointed to by s1
•both return the value of s1
• if you use memcpy() when there are overlapping ranges then the behavior is undefined
• it might work or it might not
• these functions have no way of knowing what type of data is being copied
• so, they use the third argument to indicate the number of bytes to be copied
• remember, for an array, the number of bytes is not, in general, the number of elements
• if you are copying an array of 10 double values
• you would use 10*sizeof(double), not 10, as the third argument
NULL
•macro is the value of a null pointer constant
• these functions provide output in various forms from the hardware timer in your PC
• you can use these functions to obtain the current time and date
• you can use these functions to calculate the time elapsed between two events
• you can use these functions to measure how long the processor has been occupied performing a
calculation
• examples include the ‘clock()’ function which returns amount of processor time used by the program
• the ‘time()’ function returns the number of seconds from an arbitrary epoch
• the ‘ctime()’ and ‘asctime()’ functions give current date and day of time as function value
• the ‘gmtime()’ converts time value into Coordinated Universal Time (UTC)
•‘asctime()’ and ‘ctime()’ functions return current date and time of day
•the ‘ctime()’ function may call ‘asctime()’ to perform its work
clock_t clock(void);
• returns the processor time (not the elapsed time) used by the program since some implementation-defined reference point
• often since execution began
• return value is of type clock_t, an integer type that is defined in time.h
• you typically call the clock() function at the start and end of some process in a program
• the difference is a measure of the processor time consumed by the process
• the processor time is the total time the processor has been executing on behalf of the process that called the clock() function
• the value that is returned by the clock() function is measured in clock ticks
• to convert this value to seconds
• you divide it by the value that is produced by the macro CLOCKS_PER_SEC (time.h)
• CLOCKS_PER_SEC is the number of clock ticks in 1 second
• returns –1 if an error occurs
• type time_t is defined in the header file and is often equivalent to type long
• to calculate the elapsed time in seconds between two successive time_t values returned by time()
• you use the function difftime()
• accepts a pointer to a time_t variable as an argument that contains a calendar time value returned by the time() function
• returns a pointer to a 26-character string containing the day, the date, the time, and the year, which is terminated by a newline
and '\0’
• the ctime() function has no knowledge of the length of the string you have allocated to store the result
• makes this an unsafe operation
•tm_sec
•seconds (0 to 60) after the minute on 24-hour clock
•value goes up to 60 for positive leap-second support
•tm_min
•minutes after the hour on 24-hour clock (0 to 59)
•tm_hour
•the hour on 24-hour clock (0 to 23)
•tm_mday
•day of the month (1 to 31)
• tm_year
• year (current year minus 1900)
• tm_wday
• weekday (Sunday is 0; Saturday is 6)
• tm_yday
• day of year (0 to 365)
• tm_isdst
• daylight saving flag
• positive for daylight saving time
• 0 for not daylight saving time
• negative for not known
time_t time_ptr;
time(&time_ptr);
printf("Current date and time = %s", asctime(localtime(&time_ptr)));
•returns the calendar time as a value of type time_t if the operation is successful
•returns –1 if the date cannot be represented as a time_t value
•causing the operation to fail
• when you declare a variable to be an int, you are saying that these and only these operations can affect it
• to define new data types, you want to follow a three-step process (from more abstract to more concrete)
• provide an abstract description of the type’s properties and of the operations you can perform on the type
• not tied to any particular implementation
• referred to as an abstract data type (ADT)
• the list type should support operations such as adding an item to the list
•to give you a sneak peak, lets quickly look at some of the operations
for each of these types of ADT’s
•operation names will be similar (add, insert)
•pop – remove and return the element at the top of the stack, if it is not
empty
•peek – return the element at the top of the stack without removing it, if
the stack is not empty
•dequeue – remove and return the first element of the queue, if the queue
is not empty
•peek – return the element of the queue without removing it, if the queue is
not empty
•preorder traversal
•postorder traversal
•inorder traversal
•a node/link can contain data of any type including other struct objects
•linked lists work like an array that can grow and shrink as needed, from any
point in the array
•linked lists are accessed via a pointer to the first node of the list
•the link pointer in the last node of a list is set to NULL to mark the end of the
list
•in order to traverse the list, you follow the pointer from each node/link to the
next
https://medium.com/journey-of-one-thousand-apps/data-structures-in-the-real-world-508f5968545a
• an array can be declared to contain more elements than the number of data items expected
• this can waste memory
• we are going to create an application that allows us to perform operations on a linked list of characters
• this application will allow you insert a character at the beginning of a list, at the end, or in alphabetical order or
to delete a character from the beginning of the list or a specific one
• all insertions and deletions are only made at the top of the stack
• the last item to be put in to the stack is always the first item to be removed
• referred to as a last-in, first-out (LIFO) data structure
https://www.tutorialspoint.com/data_structures_algorithms/stack_algorithm.htm
• stacks are used by compilers in the process of evaluating expressions and generating machine
language code
• balancing symbols (matching starting and ending brackets, parenthesis)
•used in many algorithms like Tower of Hanoi, tree traversals, stock span problem,
histogram problem
•the push operation is performed by inserting the element at the front of the list
•the pop operation is performed by deleting the element at the front of the list
•some drawbacks of this implementation include speed (all the operations take
constant time)
•calls to malloc and free are expensive especially in comparison to the pointer
manipulation routines
•queue elements are removed only from the head of the queue
• other operations
• IsEmpty - check if queue is empty
• IsFull - check if queue is full
• peek - get the value of the front of queue without removing it
• poll or offer (same as dequeue and enqueue)
https://en.wikipedia.org/wiki/Queue_(abstract_data_type)
• basically, when a resource is shared among multiple consumers, queues are often utilized
• data queues serve as the fastest form of asynchronous communication between two different tasks
• there is less overhead than with database files and data areas
• flexibility
• queues are flexible, requiring no communications programming
• the queue can remain active when there are no entries and ready to process data entries when necessary
•the enqueue operation is performed by inserting the element at the rear of the
list
•the dequeue operation is performed by deleting the element at the front of the
list
•some drawbacks of this implementation include speed (all the operations take
constant time)
•calls to malloc and free are expensive especially in comparison to the pointer
manipulation routines
• when initializing the queue, we set the value of FRONT and REAR to -1
• on enqueing an element, we increase the value of REAR index and place the new element in the position pointed to by
REAR
• on dequeueing an element, we return the value pointed to by FRONT and increase the FRONT index.
• when dequeing the last element, we reset the values of FRONT and REAR to -1.
Step 3 − If the queue is not full, increment rear pointer to point the next empty
space.
Step 4 − Add data element to the queue location, where the rear is pointing
Step 3 − If the queue is not empty, access the data where front is
pointing.
• trees whose nodes contain a maximum of two links are called binary trees
• none, one, or both of which may be NULL
•computer scientists normally draw trees from the root node down
•exactly the opposite of trees in nature
https://www.tutorialspoint.com/data_structures_algorithms/tree_data_structure.htm
https://www.interviewcake.com/concept/java/binary-search-tree
ADVANCED C PROGRAMMING COURSE DATA STRUCTURES
Binary Trees (Overview) ACP-2620-7
Binary search tree
• searching a binary tree for a value that matches a key value is fast
• if the tree is tightly packed, each level contains about twice as many elements as the previous level
• a binary search tree combines a linked structure with binary search efficiency
• search
• searches an element in a tree
• trees can be traversed in different ways because they are non-linear (inorder, preorder, postorder)
• inorder traversal
• gives nodes in non-decreasing order
• the steps are
• traverse the left subtree in-order
• process the value in the node
• traverse the right subtree in-order
• the value in a node is not processed until the values in its left subtree are processed
• prints the node values in ascending order
•postorder traversal
•used to delete the tree the steps are
•traverse the left subtree post-order
•traverse the right subtree post-order
•then process the value in the node
•the value in each node is not processed until the values of its children are
processed
• the shape of the binary search tree depends entirely on the order of insertions and deletions,
and can become degenerate
• there has been a lot of research to prevent degeneration of the tree resulting in worst case
time complexity of O(n)
• each process has its own address space and (usually) one thread of control
• the processes created by a given parent are called its child processes
• a child inherits many of its attributes from the parent process
• programmers do not normally need to be concerned with system calls because there are functions in the GNU C Library to do
virtually everything that system calls do
• these functions work by making system calls themselves
• each process is identified with a unique positive integer called a process ID (PID)
• the system call getpid() returns the process ID of the calling process
• this call is always successful and thus no return value to indicate an error
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
• the system call getppid() returns the Parent PID of the calling process
int main() {
int mypid, myppid;
printf("Program to know PID and PPID's information\n");
mypid = getpid();
myppid = getppid();
printf("My process ID is %d\n", mypid);
printf("My parent process ID is %d\n", myppid);
printf("Cross verification of pid's by executing process commands on shell\n");
system("ps -ef");
return 0;
}
•what if you want to send specific data to a process or read its output?
•if any process wants to communicate with some information from its own
address space to other processes
•it is only possible with IPC (inter process communication) techniques
• message queues
• processes will communicate with each other by posting a message and retrieving it out of a queue (full duplex)
• shared memory
• communication between two or more processes is achieved through a shared piece of memory among all processes
• sockets
• mostly used to communicate over a network between a client and a server
• signals
• communication between multiple processes by way of signaling
• a source process will send a signal (recognized by number) and the destination process will handle it accordingly
• communication can also be multi-level such as communication between the parent, the child and the grand-child, etc.
• can be thought of filling water with the pipe into some container, say a bucket, and someone retrieving it, say with a mug
• the filling process is nothing but writing into the pipe and the reading process is nothing but retrieving from the pipe
• implies that one output (water) is input for the other (bucket)
• the sending process places a message (via some (OS) message-passing module) onto a queue which can be read by another
process
• each message is given an identification or type so that processes can select the appropriate message
• process must share a common key in order to gain access to the queue in the first place.
• system calls are used for creating and using message queues
• communication is done via this shared memory where changes made by one process can be viewed by another
process
• communication between processes requires processes to share some variable and it completely depends on how
programmer will implement it
• system calls allow you to create a memory segment which can be accessed and modified by multiple processes
• setting it up is easy (and similar to Message Queues)
• managing the given data segment might be the only tricky part
• signals are a way to communicate information to a process about the state of other processes, the operating system and
hardware, so that the process can take appropriate action
• signals are generated by the system or the user can also generate the signal programmatically
• when a signal is sent, the operating system interrupts the target process' normal flow of execution to deliver the signal
• there are fix set of signals that can be sent to a process defined by the operating system
• signals are also used in the context of terminal signaling which is how programs stop, start and
terminate
• typing Ctrl-c that is the same as sending a SIGINT signal
• typing Ctrl-z that is the same as sending a SIGTSTP signal
• typing fg or bg that is the same as sending a SIGCONT signal
•Core - the process will terminate and produce a core dump file that traces the
process state at the time of termination
• the signal handling library (<signal.h>) provides the capability to raise specific signals
• the function raise() can only send a signal to the program that contains it
• cannot send a signal to other processes
• to send a signal to other processes, the system call kill() should be used
• after raising the signal, the execution of the current process is stopped
•the alarm() function will return a value if another alarm has been previously set
• sometimes you need to run your own code when receiving a signal (handle/catch)
• maybe your process has files or network connections open
• it might want to close things down and tidy up before exiting
• this function is used to tell the operating system which function it should call when a signal is sent to a process
• the function is used to deal with (or handle) a signal that is sent to it
• if you have a function called foo() that you want the operating system to call when someone sends an
interrupt signal to your process
• you need pass the function name foo to the signal function as a parameter
• the function foo() is called the handler
#include <signal.h>
• the system call signal() would call the registered handler (second parameter) upon generation of signal
• sigactions are used to tell the operating system which function it should call when a signal is sent to a
process
• the function is used to deal with (or handle) a signal that is sent to it
• if you have a function called foo() that you want the operating system to call when someone sends an
interrupt signal to your process
• you need to wrap the foo() function up as a sigaction
• the function foo() is called the handler
int sigaction(int signum, const struct sigaction *newaction, struct sigaction *oldaction)
• the sigaction() function will return –1 if it fails and will also set the errno variable
• you should always check for errors in your own code
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
• the handler for sa_handler specifies the action to be performed based on the signum
• SIG_DFL indicating default action
• SIG_IGN to ignore the signal or pointer to a signal handling function
• the structure siginfo_t contains signal information such as the signal number to be delivered, signal value, process id, real user id
of sending process, etc.
int sa_mask;
• this variable specifies the mask of signals that should be blocked during the execution of signal handler
int sa_flags;
• this field specifies a set of flags which modify the behavior of the signal
•the fork() system call is a function where a process creates a copy of itself
•creates a new process
•defined in <unistd.h>
•when a process calls fork, it is deemed the parent process and the newly created process is
its child
•the child process has an exact copy of all the memory segments of the parent process
•the fork operation creates a separate address space for the child
•updating a variable in one process will not affect the other
•a typical use case is that a process will first invoke the fork function to create a copy of
itself
•then, the copy (child process), calls the exec system call to overlay itself with the
other program
•it ceases execution of its former program in favor of the other
•after a new child process is created, both processes will execute the next instruction
following the fork() system call
•child and parent processes run in parallel
•we have to distinguish the parent from the child
•this can be done by testing the returned value of fork()
pid=fork();
• in the subsequent blocks of code you need to check the value of pid
• returns a negative value when the creation of a child process was unsuccessful
• returns a zero value to the newly created child process
• returns a positive value, the process ID of the child process, to the parent
• when we test the value of pid to find whether it is equal to zero or greater than it we are effectively
finding out whether we are in the child process or the parent process
•chat programs will need to read text from the network and
send data to the network at the same time
• in the real world, you cannot do all of the tasks at the same time (not by yourself)
• if someone comes into the shop, you may need to stop stocking the shelves to help a
customer
• if you work in a shop alone, you are the same as a simple process
• you do one thing after another, but always one thing at a time
• you can switch between tasks to keep everything going
• but what if there is a blocking operation?
• what if you are serving someone at the checkout and the phone rings?
• most of the programs we have written so far have had a single path of execution
• there has only been one task working inside the program’s process
• the main() function is the single path of execution (sequentially, line by line)
• a thread can also be scheduled independently of the larger program that it is inside of (done by the OS)
• means that a single program may actually use more than 100% of CPU resources on multi-processor machines
• a thread has its own unique id, program counter (PC), a register set, and a stack space just like a process
• threads share memory across each thread by having the same address space (unlike multi-processes)
• two threads have access to the same set of variables and can alter each other's variable values
• if one thread changes a global variable, all of the other threads will see the change immediately
• If one person is running the checkout, another is filling the shelves, and someone else is waxing the surfboards
• everybody can work without interruptions
• if one person answers the phone, it won’t stop the other people in the shop
• in the same way that several people can work in the same shop, you can have several threads living inside the
same process (program)
• each thread runs independently (a thread of execution)
• multi-threading means you can give each thread a separate task and they will all be performed at the same time
• in a browser, multiple tabs can be different threads
• MS word uses multiple threads, one thread to format the text, other thread to process inputs, etc.
• imagine a program with several threads that may access a variable containing salary data
• if one thread accesses the value to change it and the second thread does the same before the
first thread has stored the modified value
• you will have inconsistent data
• threading was traditionally provided via hardware and OS support in the past
• implementations differed substantially from each other making it difficult for programmers to develop portable threaded
applications
• in order to take full advantage of the capabilities provided by threads, a standardized programming interface was required
• In 1995, POSIX became the standard interface for many system calls in UNIX including the threading environment
• it is most effective on multi-processor or multi-core systems where the process flow can be
scheduled to run on another processor
• thus gaining speed through parallel or distributed processing
• a thread is spawned by defining a function and it's arguments which will be processed in the
thread
• the purpose of using the POSIX thread library in your software is to execute software faster
• pthread functions are defined in a pthread.h header/include file and implemented in a thread
library
•it is not possible to cover more than an introduction on pthreads given time
constraints
•an entire 15 hours course could be creating on posix threads
•thread management
•routines that work directly on threads - creating, detaching, joining, etc.
•also include functions to set/query thread attributes (joinable, scheduling
etc.)
•synchronization
•Routines that manage read/write locks and barriers and deal with
synchronization
•mutex functions provide for creating, destroying, locking and unlocking
mutexes (mutual exclusion)
•In this lecture, I will describe three functions that are involved in the
creation of a thread
•pthread_create, pthread_exit, and pthread_join
• the third argument is name of the function that the thread will execute once it is created
• the method for doing this is to join the thread, which is a lot like the wait() call for processes
• a call to pthread_join blocks the calling thread until the thread with identifier equal to the first
argument terminates
• makes the program stops in order to wait for the end of the selected thread
• typically, only the main thread calls join, but other threads can also join each other
• all threads are automatically joined when the main thread terminates
•the first argument is the thread id of the thread you want to wait
for
•if the second argument is not NULL, this value is passed to
pthread_exit() by the terminating thread
ADVANCED C PROGRAMMING COURSE THREADS
Creating a Thread ACP-2840-8
pthread_exit
• threads can be terminated in a number of ways
• by explicitly calling pthread_exit
• by letting the thread function return
• by a call to the function exit which will terminate the process including any threads
• typically, the pthread_exit() routine is called after a thread has completed its work and is no
longer required to exist
• if main() finishes before the threads it has created finish, and exits with pthread_exit(), the other
threads will continue to execute
• otherwise, they will be automatically terminated when main() finishes
• although not explicitly required to call pthread_exit() at the end of the thread function
• it is good practice to do so, as you may have the need to return some arbitrary data back to
the caller via pthread_join()
• If you know in advance that a thread will never need to join with another thread, consider creating it in a detached state
• some system resources may be able to be freed
• there are two common use cases of when you would want to detach
• in a cancellation handler for a pthread_join()
• nearly essential to have a pthread_detach() function in order to detach the thread on which pthread_join() was
waiting
• in order to detach the "initial thread" (as may be desirable in processes that set up server threads)
• the pthread_detach() routine can be used to explicitly detach a thread even though it was created as joinable
•safe and portable programs do not depend upon the default stack limit
•instead, explicitly allocate enough stack for each thread by using the
pthread_attr_setstacksize function
pthread_equal (thread1,thread2)
• thread synchronization is the concurrent execution of two or more threads that share critical resources
• it cannot be assumed that threads are executed in the order they are created
• they may also execute at different speeds
• when threads are executing (racing to complete) they may give unexpected results (race condition)
• a race condition often occurs when two or more threads need to perform operations on the same
memory area
• but the results of computations depends on the order in which these operations are performed
• may occur when commands to read and write a large amount of data are received at almost the same
instant
• the machine attempts to overwrite some or all of the old data while that old data is still being read
• a deadlock is a situation in which two threads are sharing the same resource and effectively
preventing each other from accessing this resource
• causes program execution to halt indefinitely
• each thread is “waiting” on the other thread
• deadlocks occur when one thread “locks” a resource and never “unlocks” that resource
• caused by poor application of “locks” or joins
• you have to be very careful when applying two or more “locks” to a section of code
• “thread safe” means that there are no static or global variables (shared resources) which other
threads may corrupt or clobber
• usually any function that does not use static data or other shared resources is thread-safe
• thread-unsafe functions may be used by only one thread at a time in a program and the
uniqueness of the thread must be ensured
• you do not want a thread to be modifying a variable that is already in the process of being
modified by another thread
• another scenario is where a value is in the process of being updated and another thread reads
an old value
• one of the primary means of implementing thread synchronization and for protecting shared data
• used to prevent data inconsistencies due to race conditions
• anytime a global resource is accessed by more than one thread the resource should have a mutex associated with it
• can apply a mutex to protect a segment of memory ("critical region") from other threads
• if a thread wishes to modify or read a value from a shared resource, the thread must first gain the lock
• once it has the lock it may do what it wants with the shared resource without concerns of other threads accessing
the shared resource because other threads will have to wait
• once the thread finishes using the shared resource, it unlocks the mutex, which allows other threads to access the
resource
• as an analogy, you can think of a mutex as a safe with only one key and the resource it is protecting lies within the safe
• only one person can have the key to the chest at any time, therefore, is the only person allowed to look or modify
the contents of the chest at the time it holds the key
• you could have a counter (flag) that once it reaches a certain count
• a thread will activate and then wait on a condition variable
• active threads will signal on this condition variable to notify other threads waiting/sleeping on this condition variable
• causing a waiting thread to wake up
• you can also use a broadcast mechanism if you want to signal all threads waiting on the condition variable to wakeup
•we will strive to write code that will handle race condition
situations
• when protecting shared data, it is the programmer's responsibility to make sure every thread that needs to use a mutex does so
• if four threads are updating the same data, but only one uses a mutex, the data can still be corrupted
• very often the action performed by a thread owning a mutex is the updating of global variables
• a safe way to ensure that when several threads update the same variable, the final value is the same as what it would be if only one thread performed the
update
• when several threads compete for a mutex, the losers block at that call
• please understand that a deadlock can occur when using a mutex lock
• making sure threads acquire locks in an agreed order (i.e. preservation of lock ordering)
•the below is a mutex variable named lock that is initialized to the default
pthread mutex values
• attributes for the mutex can be given through the second parameter
• to specify default attributes, pass NULL as the second parameter
• the pthread_mutex_destroy(mutex) function should be used to free a mutex object which is no longer needed
• anytime a global resource is accessed by more than one thread the resource should have a mutex associated with it
• the above functions should be used to control access to a "critical section” of code from other threads
• it is up to the programmer to ensure that the necessary threads all make the mutex lock and unlock calls correctly
void *function1()
{
...
pthread_mutex_lock(&lock1); - Execution step 1
pthread_mutex_lock(&lock2); - Execution step 3 DEADLOCK!!!
...
...
pthread_mutex_lock(&lock2);
pthread_mutex_lock(&lock1);
...
}
• while mutexes implement synchronization by controlling thread access to data, condition variables allow threads to synchronize based upon
the actual value of data
• without condition variables, the programmer would need to have threads continually polling (possibly in a critical section), to check if a
condition is met
• can be very resource consuming since the thread would be continuously busy in this activity
• the condition variable mechanism allows threads to suspend execution and relinquish the processor until some condition is true
• used with the appropriate functions for waiting and later, thread continuation
• one thing that was missed in the above analogy is we know that to access the shared information a thread needs to hold a lock
• since timmy needs to update the shared information while tommy is asleep and waiting
• we must have any thread that sleeps on a condition variable release the lock while it is asleep
• but since tommy had the lock when he fell asleep, he expects to still have the lock when he wakes up
• so the condition variable semantics guarantee that a thread sleeping on a condition variable will not fully wake up until
• (1) it receives a wake-up signal
• (2) it can re-acquire the lock that it had when it fell asleep
Creating/Destroying:
Waiting on condition:
pthread_cond_wait - will put the caller thread to sleep on the condition variable condition and release the mutex lock, guaranteeing that when the
subsequent line is executed after the caller has woken up, the caller will hold lock
pthread_cond_timedwait - place limit on how long it will block
• for all of the above functions, the caller must first hold the lock that is associated with that condition variable
• failure to do this can lead to several problems
• we have learned how processes on the same machine can communicate with each other
• luckily, C is used to write most of the low-level networking code on the Internet
• In this section, we are going to learn how to write C programs that can talk to other programs across the
network and across the world
• the goal of this section is to be able to create programs that act as servers and programs that act as clients
• TCP works with the Internet Protocol (IP), which defines how computers send packets of data to each
other
• together, TCP and IP are the basic rules defining the Internet
• all major Internet applications such as the World Wide Web, email, remote administration, and file
transfer rely on TCP
• TCP creates a connection between the source and destination node before transmitting the data and
keeps the connection alive until the communication is no longer active
• clients typically communicate with servers by using the TCP/IP protocol suite
• computer transactions in which the server fulfills a request made by a client are very common
• the client-server model has become one of the central ideas of network computing
• the client establishes a connection to the server over a local area network (LAN) or wide-area network (WAN), such as the Internet
• clients need to know the address of the server, but the server does not need to know the address or even the existence of the
client prior to the connection being established
• once the server has fulfilled the client's request, the connection is terminated
• because multiple client programs share the services of the same server program, a special server called a daemon may be
activated just to await client requests
https://en.wikipedia.org/wiki/Client%E2%80%93server_model
• after getting a request from the client, this process will perform the required processing, gather the requested
information, and send it to the requestor client
• server processes are always alert and ready to serve incoming requests
•Iterative Server
•the simplest form of a server where the server process serves one client at a time
•after completing the first request, it takes request from another client
•other client wait until it is their turn
•Concurrent Servers
•this type of server runs multiple concurrent processes to serve many requests at a
time
•one process may take longer and another client does not need to wait too long
•the simplest way to write a concurrent server under Unix is to fork a child process to
handle each client separately
<sys/types.h>
• contains definitions of a number of data types used in socket calls
<sys/socket.h>
• the main socket header file
• includes a number of definitions of structures needed for sockets (socket creation, accept, listen, bin, send, recv, etc)
<netinet/in.h>
• contains constants and structures needed for internet domain addresses
<netdb.h>
• defines the structure hostent
<arpa/inet.h>
defines in_addr structure
• to prevent the different conversations from getting confused, each server uses a different port
• the port assignments to network services can be found in the file /etc/services
• if you are writing your own server then care must be taken to assign a port to your server
• you should make sure that this port should not be assigned to any other server
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
• socket address structures are an integral part of every network program, most socket functions require a
pointer to a socket address structure as an argument
• we allocate them, fill them in, and pass pointers to them to various socket functions
• sometimes we pass a pointer to one of these structures to a socket function and it fills in the contents
• we always pass these structures by reference (i.e., we pass a pointer to the structure, not the structure
itself), and we always pass the size of the structure as another argument
• when a socket function fills in a structure, the length is also passed by reference, so that its value can be
updated by the function
• always set the structure variables to NULL, otherwise it may get unexpected junk values in your structure
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
• in addr is used only in the above structure as a structure field and holds the 32 bit netid/hostid
struct in_addr {
unsigned long s_addr;
};
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list
•the socket() and accept() functions both return handles (file descriptor)
•reads and writes to the sockets requires the use of these handles (file descriptors)
•in Linux, sockets and file descriptors also share the same file descriptor table
•you cannot randomly access a socket like you can a file with lseek()
• most of the functions are used by both the client and the server with the exception of
• bind() is used particularly by server programs
• connect() by client programs
• all of the above functions need to include <sys/types.h> and <sys/socket.h> except for
read/write/close which are defined in <unistd.h>
•family specifies the protocol family and is usually the constant AF_INET for
IPv4 protocols and AF_INETc for IPv6 protocols
•type specifies the kind of socket you want, and this is usually set to
SOCK_STREAM for a stream socket or SOCK_DGRAM for a datagram socket
•returns a socket descriptor that you can use in later system calls or -1 on error
•the setsockopt function helps in manipulating options for the socket referred to by
the file descriptor sockfd
•completely optional, but it helps in reuse of address and port
•prevents error such as: “address already in use”
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
• once a server program has created a socket and named it with bind( ) giving it an IP address and port number, should any program anywhere
on the network give that same name to the connect( ) function, that program will find the server program and they will link up
• my_addr is a pointer to struct sockaddr that contains the local IP address and port
• a 0 value for port number means that the system will choose a random port, and INADDR_ANY value for IP address means the server's IP
address will be assigned automatically
server.sin_port = 0;
server.sin_addr.s_addr = INADDR_ANY;
• the listen() system call tells the operating system how long you want the queue to be
• calling listen() with a queue length of 10 means that up to 10 clients can try to connect to the server at once
• they will not all be immediately answered, but they will be able to wait
• the 11th client will be told the server is too busy
• servers spend most of their lives waiting for clients to contact them
• the accept() system call waits until a client contacts the server, and then it returns a second socket descriptor that you
can use to hold a conversation on
• cliaddr is a pointer to struct sockaddr that contains client IP address and port
int recv(int sockfd, void *buf, int len, unsigned int flags);
• flags is set to 0
• returns the number of bytes read into the buffer, otherwise it will return -1 on error
int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);
• from is a pointer to struct sockaddr for the host where data has to be read
• fromlen should be set it to sizeof(struct sockaddr)
• returns the number of bytes read into the buffer, otherwise it returns -1 on error
• returns the number of bytes actually written to the file associated with fildes if successful otherwise, -1
is returned
int send(int sockfd, const void *msg, int len, int flags);
• len is the length of the data you want to send (in bytes)
• returns the number of bytes sent out, otherwise it will return -1 on error
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int
tolen);
• to is a pointer to struct sockaddr for the host where data has to be sent
• tolen should be set it to sizeof(struct sockaddr)
•returns the number of bytes actually written to the file associated with fildes, if successful
•otherwise, -1 is returned
• How
• 0 − indicates that receiving is not allowed
• 1 − indicates that sending is not allowed
• 2 − indicates that both sending and receiving are not allowed
• when how is set to 2, it's the same thing as close()
•send and receive data using the read()/recv and write()/send system calls
ADVANCED C PROGRAMMING COURSE NETWORKING (SOCKETS)
Creating a Server Socket ACP-2980-3
Illustration
https://www.thecrazyprogrammer.com/2017/06/socket-programming.html
https://aticleworld.com/socket-programming-in-c-using-tcpip/
listen( my_sd )
start loop
his_sd = accept( my_sd, <empty address to be filled in with his incoming info> )
recv( his_sd, <where to put what you receive> )
send( his_sd, <the stuff you want sent> )
close( my_sd )
end loop
https://www.thecrazyprogrammer.com/2017/06/socket-programming.html
https://aticleworld.com/socket-programming-in-c-using-tcpip/
close( my_sd )
• type qualifiers
• const, volatile, and restrict
• bit manipulation
• binary numbers and bits
• bitwise operators (logical and shifting)
• bitmasks and bitfields
•macros
•overview (vs. functions, when to use)
•predefined macros
•creating your own macros
•advanced pointers
•double pointers (pointers to pointers)
•function pointers
•more on void pointers
•data structures
•Linked lists, stacks, queues, and Trees
•hands on coding
Challenge Slides.
Example
Input elements: 10, 20, 30, 40, 50
Output
Sum of all elements = 150
•you must use a variable length array for the size of your array
•the obvious benefit of allowing variable-length arrays, is that you don't have to know in
advance of your program running, how much memory to allocate for your array variables
•create a structure named myArray that has a length member and a flexible array
member named array
•allocate memory for the structure based on this size read in from the user
•set the length member and fill the array with some dummy data
•Use the complex.h header file and the creal() and cimag() functions
•you can pass bin as an argument to the function and have the function
return an int value of 25
•you can pass decimal as an argument to the function and have the function
return a long long value of 1010
• Example run
Enter an integer: 10
Enter another integer: 11
• write a program that contains the following bit fields in a structure (onscreen box)
• the box can opaque or transparent
• a fill color is selected from the following palette of colors: black, red, green, yellow, blue,
magenta, cyan, or white
• a border can be shown or hidden
• a border color is selected from the same palette used for the fill color
• a border can use one of three line styles—solid, dotted, or dashed
• you need only a single bit to indicate whether the box is opaque or transparent
• you need only a single bit to indicate if the border is shown or hidden
• the eight possible color values can be represented by the eight possible values of a 3-bit unit
• a 2-bit unit is more than enough to represent the three possible border styles
• a total of 10 bits, then, is enough to represent the possible settings for all five properties
ADVANCED C PROGRAMMING COURSE BIT MANIPULATION
(Challenge) Using Bit Fields to Pack Data ACP-0725-2
Challenge (bit fields) (cont’d)
•use padding to place the fill-related information in
one byte
•lastly, you can choose to let 0, 1, and 2 represent the solid, dotted, and dashed styles
•can be used as values for a “border_style” member
•first, create a variable of the structure type and initialize the bitfields
with some default values
•display these default values
*
* *
* *
* *
*********
• the program should not utilize any looping constructs and instead only use the goto statement
• after writing the program, you should come to the conclusion that using a goto statement is terrible
and your code looks like spaghetti code
•create a function named error_recovery that prints out an error and then uses
longjmp to transfer control back to a main function loop
•your main function should include a forever loop that uses setjmp at the top of
the loop before processing
•you can add “dummy” code in your loop that simulates an error (by calling
error_recover) when setjmp returns 0
• write a program in C to count the number of words and characters in a file OR from standard input
• this program can take zero command-line arguments or one command-line argument
• If there is one argument, it is interpreted as the name of a file
• If there is no argument, the standard input (stdin) is to be used for input
what
nope
ok
•this program can take a command-line argument for the name of the file or you can ask
the user for the name of the file
•you will need to create a temporary file to store the result (converted character)
•can then rename this temporary file back to the original file
•you will need to use the isupper(ch) to check for upper or lower case characters (can also
use tolower and toupper)
Example output
HELLO, HOW ARE YOU
JASON
NOPE
OK
WHAT
LEST
AAAAA
•the requirements of the program are that it should print only those lines in the file
containing the given character
•lines in a file are identified by a terminating '\n'
•assume that no line is more than 256 characters long
•you are required to use fgets() or getline() (or try both) for reading of the file
•use puts to display the output
ADVANCED C PROGRAMMING COURSE INPUT AND OUTPUT
(Challenge) string functions ACP-1000-2
Challenge example input and output
hello, how are you
jason
nope
ok
what
lest
Aaaaa
Example run
./a.out j test.out
jason
ADVANCED C PROGRAMMING COURSE INPUT AND OUTPUT
(Challenge) string functions ACP-1000-3
Summary
•review the demonstration and solution provided for further
understanding
•write a program that takes as input, a set of numbers from a file and write
even, odd and prime numbers to standard output
•you can use either fscanf or (fgets and sscanf) to accomplish the above
•I have provided a sample input file (numbers.txt) that you can test with
•in order to know how many numbers are being passed to this
variadic function, you can use the first argument as the number
of arguments
•Output
•30
•60
•100
•you can also add to the program, functions that that calculate average value
and sum of unknown number of numbers, max or min of a list of numbers
•you can also create your own printf function (takes a variable number of
arguments)
•you would need a character to specify the format
•you can use ‘%’, as it is implemented in printf and scanf or some other
character
ADVANCED C PROGRAMMING COURSE ADVANCED FUNCTION CONCEPTS
(Challenge) Variadic Functions ACP-1140-4
Summary
•review the demonstration and solution provided for further
understanding
•write a program which will calculate the sum of numbers from 1 to n using
recursion
Sample Input/Output:
•write a program which will find GCD (greatest common denominator) of two
numbers using recursion
Sample Input/Output:
Sample Input/Output:
• write a program which will define a union and then use this union to assign and access its members
• you must define a union named student that consists of the following three elements
• char letterGrade
• int roundedGrade
• float exactGrade
• your program should declare two union variables inside the main method (variable 1 and variable 2)
•your program should then assign a value (using variable 2) to its to member
rounded grade and then print it out
•your program should then assign a value (using variable 2) to its to member
exact grade and then print it out
•you should notice the difference in output with variable 1 and variable 2
•why is there a difference?
• Write a program to print the values of the following predefined symbolic constants
• __LINE__
• __FILE__
• __DATE__
• __TIME__
• __STDC__
• Example output
__LINE__ = 34
__FILE__ = fedin.c
__DATE__ = Jul 16 2019
__TIME__ = 11:12:23
__STDC__ = 1
•Your program should have the user enter the two numbers
•Your program should then display the sum as output by invoking the above
macro
ADVANCED C PROGRAMMING COURSE MACROS
(Challenge) Macros ACP-1580-3
Challenge #3
• Write a C program to find the square and cube of a number using macros
• Your program should then display the square and cube of the number as output by invoking the above macros
Example output
Enter any number to find square and cube: 10
SQUARE(10) = 100
CUBE(10) = 1000
• You should create two macros that accept a single argument (character)
• IS_UPPER
• IS_LOWER
• both of the macros should return a boolean
• true (1) or false (0) based on whether they are upper or lower case
• Your macro will need to include a conditional and can use logical operators
• Your program should have the user enter any character ( getchar() )
• Your program should then display whether the character is upper or lower case as output by invoking the above macros
Example output
Enter any character: C
'C' is uppercase
•also, go into code::blocks or whatever IDE you are using and also change the build
options in there
• this should help to explain why the last fprintf() failed to print
• try running the program with only one parameter, and see how it fails
• add some more debugging statements to your main() to find exactly where the line is that is causing
the problem
• in the work above, we never actually fixed the problems with main.c
• this is intentional, so that we could continue to use for your challenge
• copy the source file in the resources section that has the added debug fprintf statements (main.c)
• modify main.c to use preprocessor directives to easily turn on or off the debugging output on the
basis of whether a DEBUG symbol is defined
•for our third challenge, we will use gdb to debug our program (main.c)
•will need to compile with debugging information (-g)
•use the source file from your previous challenge (main.c) and compile
with the –g option and turn debugging on
• to see how large a core file you are permitted to generate, type ulimit -a
• a limit of 0 will prevent the generation of core files at all
• I suggest that you set it (typically in a file called .profile) to a unlimited (e.g., by commenting out the
limit that exists there)
• If your core file size is unlimited, then you can limit it if you want from the command line
•use the source file from your previous challenge and compile with the –g option and
turn debugging on
•write a program that creates, assigns, and accesses some double pointers
1. create a normal integer variable (non pointer) and assign it a random value
2. create a single integer pointer variable
3. create a double integer pointer variable
4. assign the address of the normal integer variable (step 1) to the single pointer (step 2)
5. assign the address of the single pointer (step 2) to the double pointer variable (step 3)
• modify the function named allocateMemory to take a double pointer of type int as a function
parameter
• void allocateMemory(int **ptr);
• this function should allocate memory for this pointer (use the correct syntax)
•write a program that will perform some arithmetic operation (user entry) on an
array using function pointers
•demo solution file to students showing how the program works (show the
output, similar to next slides)
• Task 1:
• create an array of function pointers that point to each arithmetic function (add, sub, mult, div)
• Task 2:
• add a fourth parameter to the performOp function so that it takes a function pointer with the
signature of the arithmetic functions
• Task 4:
• include the code for the function performOp
• your code should invoke the function pointed to from the parameter passed in
• will perform the operation on elements of the two arrays passed in and store the result in a
third array
• function should return the new array containing the results
• Task 5:
• include the code for the function display
• your code should display as output all the elements of the given array in a readable format
/*
str – string to search
searchCharacter – character to look for
return type - int : count for the number of times that character was
found
*/
int numberOfCharactersInString(char *str, char searchCharacter);
/*
source - source string
return type - int : length of string
*/
int lengthOfString(char *source);
ADVANCED C PROGRAMMING COURSE STATIC LIBRARIES AND SHARED OBJECTS
(Challenge) Static Library ACP-1820-4
strConcat, strCopy
/*
str1 – string to concatenate to (resulting string)
str2 – second string to concatenate from
return type - int : 0 on success
*/
int strConcat(char *str1, char *str2);
/*
source – string to copy from
destination – second string to copy to
return type - int : 0 on success
*/
int strCopy(char *source, char *destination);
/*
str – string to search
searchCharacter – character to look for
return type - int : count for the number of times that character was
found
*/
int numberOfCharactersInString(char *str, char searchCharacter);
/*
source - source string
return type - int : length of string
*/
int lengthOfString(char *source);
/*
source – string to copy from
destination – second string to copy to
return type - int : 0 on success
*/
int strCopy(char *source, char *destination);
•create a shared object library (.so) containing your string manipulation functions
• lib_stringfunctions.so
• you need to dynamically load the symbols from the shared object library (lib_stringfunctions.so)
that you created in the last challenge
• create another program that uses the dlopen and dlsym functions
• basically, convert your driver program (main.c) from the last challenge to dynamically load all
the string functions in the shared object
• use dlopen to open the shared object file from the last challenge (lib_stringfunctions.so)
• make sure you use an absolute path when specifying the shared object filename (current
directory??)
• otherwise, you will need to setup your LD_LIBRARY_PATH environment variable or place
the shared object in a certain directory
•invoke all of the string manipulation functions using the function pointers
•display output to the screen to verify that the string functions work
•make sure you call dlclose for the shared object handle that you used in dlopen
•the output of this program will be the same as in the last challenge
•the difference is that the symbols/functions are dynamically loaded and not
loaded at program start up time
•you are required to use the srand function, passing in the time function
•as a seed to using the rand() function
• write a program that will sort an array of doubles from lowest to highest using the qsort function
• create a function that takes a double array and a size parameter which generates some random double values
• void fillarray(double ar[], int n);
• create a function that displays an array (takes a double array and size)
• void showarray(const double ar[], int n);
Sorted list:
0.0671 0.0735 0.1706 0.2333 0.2455 0.2898
0.3792 0.3849 0.3908 0.4364 0.4489 0.4697
0.4935 0.4940 0.5014 0.5074 0.6113 0.6842
0.7105 0.7591 0.7676 0.8038 0.8789 0.8863
0.9808 1.0393 1.0603 1.1928 1.2160 1.2911
1.8076 2.0695 2.1304 2.5516 3.2049 4.5768
4.6147 6.1123 7.2541 13.5337
• you need to create a linked list that stores integers and uses pointers
•your program can utilize global variables for previous, head, tail, temp,
new nodes, etc or you can pass around data to functions
•your program will need to create a menu that allows the user to enter
their choices for what operation to perform on the list
• when the program ends, it will display the final score (number of answers correct)
•the program needs to raise a signal if the user does not answer a question
within 5 seconds
•use the alarm function and catch the SIGALRM signal
•the first time, you should answer a few questions and then hit Ctrl-C
•ctrl-c sends the process an interrupt signal that makes the program display
the final score and then exit()
•the second time, wait for at least five seconds on one of the answers and see
what happens
•the alarm signal ( SIGALRM) should occur
•the program was waiting for the user to enter an answer, but because he
took so long, the timer signal was sent
•program should output “TIME’S UP!” and then raise the SIGINT signal which
causes the program to display the final score
•write a program to create one parent with three children processes (four processes)
•must use the fork() function
•your program should contain output that identifies each parent and each child
•will need to write if statements to check process id’s returned from the fork() call, so that
the output information is correct
•“parent”, “first child”, “second child”, “third child”
•utilize the getpid() and getppid() functions to display each processes id
•at some instance of time, it is not necessary that child process will execute first or parent
process will be first allotted CPU
•any process may get CPU assigned, at some quantum time
•also, the process id may differ during different executions
second child
13207 0
my id is 13208
my parentid is 13194
First child
0 13209
my id is 13207
my parentid is 13194
third child
00
my id is 13209
my parentid is 13207
•lastly, the main thread should call join and exit on all of its child threads
•to make the original thread wait for its child threads to complete
•run the program many times, did you notice anything strange about the global
counter values for the two print statements in the same thread?
•you should have noticed in challenge 1 that the global variable count value was a
different value when displayed on two consecutive statements in the same thread
•multiple threads would update the counter between the two print statements
• the counter variable is shared across multiple threads without any synchronization constructs to protect it
• for this challenge, you will need to edit the code from challenge 1 to create a mutex variable to ensure that
multiple threads do not access the variable at the same time and introduce race conditions
• each thread should try to lock and unlock the mutex when they are executing any critical section (shared
resources)
• incrementing and printing the global counter variable
• after doing this, the counter should display the same value in each print statement for that same thread
• we will modify the program from challenge #2 so that certain threads execute critical sections of code
before other threads do
• this program should print messages from threads that pass in an even number first (parameter) and
those that pass in an odd number after all the evens have printed
• since we are not guaranteed that the threads will start in any given order, we must have the odd
threads wait until all the even threads have printed
• we do not care about the order that they print their message
• this program will require you to use condition variables to accomplish the ordering
• all of the odd threads will sleep until a certain condition is met (all the even threads have finished)
• condition variables are always associated with locks because the shared information that they depend
on must be synchronized across threads
•you will also need a global variable that indicates when all even threads have finished
•int number_evens_finished = 0; (shared data amongst threads)
•you can increment the global variable for each even thread and signal the other
threads when this count is equal to NUM_THREADS / 2
•you can check if numbers are even by using the modulus operator
•number % 2 == 0
•you can use the sleep(1) call after all threads have been created and are running
before checking if the even threads have finished
•the server should be listening for numbers on a port known to the clients
•should handle the client connections sequentially and accept connections
from multiple clients
•after servicing one client to completion (Client 1), it should proceed to the next