Basic C Notes
Linux System:
Introduction to Linux Environment:
Linux Basics:
Linuxis a powerful class of operating systems.Linuxis a multiuser, unified file system:- The
Linuxsystem hasdirectoryas folders. ~denotes the home directory,.denotes the current directory, and..denotes the previous directory.
- The
Linuxis system with command-line interface.- There is no
GUI. - Referred as “long-term lazy”: slower to learn, faster to use.
- allow for easy automation of series of commands.
- There is no
Basic Linux Commands:
Basic Commands:
- Print working directory:
pwd
- List directory contents:
- Listing files:
ls - Listing in long format:
ls -l - Listing hidden files:
ls -a
- Listing files:
- Change directory:
cd <folder_name>
- Make new directory:
- Within current directory:
mkdir <folder_to_create> - At another directory:
mkdir <pathname_to_folder> - Filling intermediate directories:
mkdir -p <pathname_to_folder>
- Within current directory:
- View text file:
- Concatenate:
cat <path_to_file> - Full screen terminable:
more <path_to_title> - Screenful at a time:
less <path_to_file>
- Concatenate:
- Checking manual:
- Checking the manual for commands:
man <cmd> - Checking manual also works for
Ccommands.
- Checking the manual for commands:
- Search keywords:
- Searching for keyword:
grep <keyword>
- Searching for keyword:
- Meta characters:
*(asterisk): wildcard character, expands to everything.!(bang): repeating the previous command.- up and down arrows: cycle through your command history.
history: present the history
File Redirections:
- Manipulate location of file or folder:
- Change location / rename:
mv <source> <destination> - Make a copy:
cp <source> <destination> - Copying a non-empty directory:
cp -r <source> <destination> - Remove a file:
rm <file_to_remove> - Remove empty directory:
rmdir <directory_to_remove> - Remove non-empty directory:
rm -r <directory_to_remove>
- Change location / rename:
- Virtual connections:
- Connecting to
Linuxaccount:ssh <your-username>@ugradx.cs.jhu.edu - Secure transfer files:
scp <username>@ugradx.cs.jhu.edu:<file_name> .
- Connecting to
- Redirections and pipelines:
- Redirecting output (overwrite):
<cmd> > <outfile> - Redirecting output (append):
<cmd> >> <outfile> - Redirecting standard error: `
2> - Redirecting output and standard error:
<cmd> &> <outfile> - Redirecting input from outside:
<cmd> < <infile> - Piping standard output previous directly into input for another:
<cmd> | <cmd>
- Redirecting output (overwrite):
- Zipping and unzipping files:
- Zipping files:
zip <zip_filename> <filename> <filename> ... - Checking
zipfiles without unzipping:unzip -l <zip_filename> - Unzipping a file:
unzip <zip_filename>
- Zipping files:
Git Workflow:
Introduction to Git:
Git Basics:
Gitis a way of sharing files:gitis powerful in sharing code.- It has distributed version control.
- It facilitates collaboration, snapshots, sharing.
Gitis compatible for any programming language:- It fits for any
textfiles.
- It fits for any
.gitstores information about changes in a copy of the repo:.gitis a subdirectory..gitgets created for you when you clone a repo.
.gitignoretellsgitto ignore a file:- Anything generated by the compiler (executables,
.ofiles) should be in.gitignore.
- Anything generated by the compiler (executables,
Git File Types:
- Files that are not part of project are untracked:
- A new created file is untracked until it is added.
Gitwill notice it so it will be shown asuntrackedingit status.
- Files as a part of project are tracked:
- The files that are the same as local repo are unmodified.
- The files that are different but not staged are modified.
- The files that will be updated later are staged.
Git Commands:
Git Tasks:
- Repo-related tasks:
- Local repo asks for most updated copy from remote repo:
git pull - Cloning a local copy of the repo:
git clone <url_address> - Add to project (“stage a file”):
git add <file> - Update local repo to include changes since last commit:
git commit -m "message" - Send changes up to remote repo:
git push - Check what’s been modified or staged:
git status - Move/Rename file:
git mv <file> <file> - Remove file:
git rm <file> - Get log:
git log
- Local repo asks for most updated copy from remote repo:
Git Command Orders:
- Step 1. Before start working:
git pull
- Step 2. After finished the edit:
git add <files you edited>
- Step 3. Commit changes with comments:
git commit -m <comments>
- Step 4. Pull one more time to sync with new updates if any:
git pull
- Step 5. Solve conflicts if it happens (between edit and new updates):
- Then repeat step 2-5.
- Step 6. Push back to the repo:
git push
C Compiler:
C Structure:
C programming files:
- Source files
<filename>.c:- The program code.
- Contains definitions for functions declared in a
.hfile. - Use
#includeinto include corresponded.hfiles.
- Header files
<filename>.h:- Group together declarations.
- Included using
#includein appropriate files.
- Intermediate object files
<filename>.o:- Translated from source files (
.cfiles). - Needed to be linked to executable file.
- Translated from source files (
- Executable file
<filename>.out:- Executable file.
- Execute using
./<executable>.
- Makefile
Makefile:- Producing linking and compiling with less repetition.
.h file in C:
.hheader file group together declarations, and are then#includeinto appropriate files.- A separate
.csource file will contain definitions for those functions declared in a.hheader file:- Typically, functions defined in
file.care declared in a function namedfile.h.
- Typically, functions defined in
- To include the header files:
- For imported libraries, use
#include <stdio.h>. - For user defined headers, use
#include "myHeader.h". - Header files typically contains
functiondeclaration andstruct.
- For imported libraries, use
- Header files often has header guards when it contains definitions.
- Header guards prevents definition duplications (giving compiler errors) when multiple
.cfiles include the same.hfile.
- Header guards prevents definition duplications (giving compiler errors) when multiple
- Sample header guard in
.hfile:
// rectangle.h:
// include lines like this at the top; change the all-caps
// name to match the file name, rectangle.h in this case
#ifndef RECTANGLE_H
#define RECTANGLE_H
struct rectangle { // structure
int height;
int width;
};
// include this line at the bottom
#endif
Makefile in C:
Makefileis a tool that helps keeping track of which files need to be recompiled.- Save time by not re-compiling unnecessarily.
- Replacing the complicated recompile code for different files.
- With the name of
Makefileormakefile, the compiling uses onlymake. Makefileuses thebashsyntax:- Lines in a makefile that begin with
#are comments. $expands the variables being predefined.- First (topmost) target listed is default target to run.
target_nameis a list of files on which target depends.- Disambiguate
Tabwith spaces in the file. - Multiple targets can be triggered by making a single target.
- Lines in a makefile that begin with
- Sample
Makefilefile:
# Makefile:
CC=gcc
CFLAGS=-std=c99 -pedantic -Wall -Wextra
main: mainFile.o functions.o
$(CC) -o main mainFile.o functions.o
mainFile.o: mainFile.c functions.h
$(CC) $(CFLAGS) -c mainFile.c
functions.o: functions.c functions.h
$(CC) $(CFLAGS) -c functions.c
clean:
rm -f *.o main
C Compiling Process:
Steps of Compiler in C:
- Step 1. Preprocessor:
- Bring together all the code that belongs together.
- Process the directives that start with
#, such as
#includeand#define.
- Step 2. Compiler:
- Turn human-readable source code into object code.
- Yield warnings and errors if code has compiler mistakes.
- Step 3. Linker:
- Bring together all the relevant object code into a single executable file.
- Yield warnings and errors if relevant code missing, naming conflict, …
C Compiling Command:
- Compile using GNU C compiler (
gcc) to compile, link, and create executable. - The standard compiling command is
gcc -std=c99 -Wall -Wextra -pedantic <c_file>:-std=c99flag implies that the program uses standardC99complier to compile.-Wallflag enables all the warnings about constructions that some users consider questionable, and that are easy to avoid (or modify to prevent the warning), even in conjunction with macros.-Wextraflag enables some extra warning flags that are not enabled by-Wall.-pedanticflag issues all the warnings demanded by strict ISO C. It rejects all programs that use forbidden extensions, and some other programs that do not follow ISO C.
- There are also other optional commands:
-oflag places output in designatefilename, regardless the output type.-gflag produce debugging information in the operating system’s native format.
C Debugging Process:
GNU debugger, GDB:
gdbhelps running program in a way that allowing:- Flexibly
pauseandresume. - Print out the values of
variablesduring the program. - Check where errors (e.g. Segmentation Faults) happen.
- Flexibly
- When using
gdb, the executable should be compiled with-g, which packages up the source code (“debug symbols”) along with the executable, that is:gcc -std=c99 -Wall -Wextra -pedantic <c_file> -o <executable_file> -g.
- When running
gdb, it runs the executable file:- For normal execution, use:
gdb --args <executable> <input> ... - For execution that takes command-line input, use:
gdb --args random_crash input.txt
- For normal execution, use:
breakcommand makes the code pause ingdb, and it set before the code starts running:- Debugger to pause as the program starts:
break main - Debugger to pause as the program enters a function:
break <function_name>
- Debugger to pause as the program starts:
runcommand start the program, which immediately pauses after the first break.nextorncommand executes the statement on the current line and moves onto the next:- If the statement contains a function call,
gdbexecutes withnextwithout pausing.
- If the statement contains a function call,
stepcommand begins to execute the statement on the current line:- If the statement contains a function call, it steps into the function and pauses there.
printorpcommand prints out the value of a variable:- Print a variable in the scope:
print <var_name>
- Print a variable in the scope:
listcommand shows the current code block.backtracecommand tracks the call stack, especially in recursions:- Going up a stack:
up <optional_number_of_stacks> - Going down a stack:
down <optional_number_of_stacks>
- Going up a stack:
helpcommands provide prompt for help:- Help advancing through the program:
help running - Help printing commands:
help show
- Help advancing through the program:
Memory Track, valgrind:
valgrindfinds memory usage mistakes:- Invalid memory accesses (e.g. array index out of bounds).
- This is attempts to dereference pointers to memory that is not owned.
- Memory leaks (i.e., failure to free dynamically-allocated memory).
- This is failing to deallocate a block of memory that is allocated.
- Invalid memory accesses (e.g. array index out of bounds).
- When using
valgrind, the executable should be compiled with-g, which packages up the source code (“debug symbols”) along with the executable, that is:gcc -std=c99 -Wall -Wextra -pedantic <c_file> -o <executable_file> -g.
- When running
valgrind, it runs the executable file:valgrind --leak-check=full --show-leak-kinds=all ./<executable> <args> ...- The program’s output is interleaved with
valgrindmessages.
C Libraries:
stdio Library:
stdio.hheader mainly contains functions that involves opening files, reading, and writing:- Should be included by
#include stdio.h.
- Should be included by
- Variables in
stdio.h:size_t: Unsigned integral type and is the result of thesizeofkeyword.FILE: Object type suitable for storing information for a file stream.fpos_t: Object type suitable for storing any position in a file.
- Functions in
stdio.h:FILE *fopen(const char *filename, const char *mode): Opens the filename pointed to by filename using the given mode.`int fclose(FILE *stream): closes the stream and clears all buffers.int feof(FILE *stream):Tests the end-of-file indicator for the given stream.int ferror(FILE *stream): Tests the error indicator for the given stream.void rewind(FILE *stream): Sets the file position to the beginning of the file of the given stream.int fprintf(FILE *stream, const char *format, ...): Sends formatted output to astream.int printf(const char *format, ...): Sends formatted output tostdout.int sprintf(char *str, const char *format, ...): Sends formatted output to astring.int fscanf(FILE *stream, const char *format, ...): Reads formatted input from a stream.int scanf(const char *format, ...): Reads formatted input from stdin.int sscanf(const char *str, const char *format, ...): Reads formatted input from a string.size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream): Reads data from the givenstreaminto the array pointed to, byptr.size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream): writes data from the array pointed to, byptrto the givenstream.
math Library:
math.hheader mainly contains functions in mathematics:- Should be included by
#include math.h. - When compiling, it requires the flag
-lmfor link math.
- Should be included by
- Functions in
math.h:sqrt(x): \(\sqrt{x}\).pow(x, y): \(x^y\).exp(x): \(e^x\).log(x): \(\log_{e}(x)\).log10(x): \(\log_{10}(x)\).ceil(x)/floor(x): \(\lceil x\rceil\) / \(\lfloor x\rfloor\).sin(x)/sinh(x)/asin(x): sine, hyperbolic sine, and arc sine (also other trigonometric function).
xandyarguments have typedouble, and the return type is alsodouble:- It’s allowed to pass another numeric type, like
int, but with variable promotion.
- It’s allowed to pass another numeric type, like
assert Library:
assert.hheader contains theassertfunction:- Should be included by
#include <assert.h>.
- Should be included by
- Assertion statements help catch bugs as close to the source as possible:
- The structure is:
assert(boolean expression);booleanexpression is an expression that should betrueif everything is fine.- If it’s
false, program immediately exits with an error message, indicating the assertion failed.
- The structure is:
- Assert is not for typical error checking, but for debugging:
- If checking for bad user input, or another strange but not impossible situation, use if and
printa meaningful error message tostderr.- If you must exit, return non-zero to indicate failure.
- If checking for something that implies the program being incorrect, use
assert.
- If checking for bad user input, or another strange but not impossible situation, use if and
string Library:
string.hheader contains thestringfunctions:- Should be included by
#include <string.h>.
- Should be included by
- Variables in
stdio.h:size_t: Unsigned integral type and is the result of thesizeofkeyword.
- Functions in
stdio.h:int strcmp(const char *str1, const char *str2): Compares the string pointed to, bystr1to the string pointed to bystr2.- it compares two strings according to character ASCII values:
- negative:
str1beforestr2. - zero:
str1andstr2equal. - positive:
str1afterstr2.
- negative:
- it compares two strings according to character ASCII values:
int strncmp(const char *str1, const char *str2, size_t n): Compares at most the firstnbytes ofstr1andstr2.char *strcpy(char *dest, const char *src): Copies the string pointed to, bysrctodest.destmust be defined with a sufficient size.
char *strncpy(char *dest, const char *src, size_t n): Copies up toncharacters from the string pointed to, bysrctodest.size_t strlen(const char *str): Computes the length of the stringstrup to but not including the terminating null character.- Note that
sizeoffunction for a string contains an additional byte for\0.
- Note that
char *strcat(char *dest, const char *src): Appends the string pointed to, bysrcto the end of the string pointed to bydest.char *strncat(char *dest, const char *src, size_t n): Appends the string pointed to, bysrcto the end of the string pointed to, bydestup toncharacters long.char *strtok(char *str, const char *delimiters): Split str into tokens, which are sequences of contiguous characters separated by any of the characters that are part of delimiters.
ctype Library:
ctype.hcontains a bunch of useful functions we can apply to character:- Should be included by
#include <ctype.h>.
- Should be included by
- Functions in
ctype.h:int isalpha(int c): Checks whether the passed character is alphabetic.int isdigit(int c): Checks whether the passed character is decimal digit.int islower(int c): Checks whether the passed character is lowercase letter.int isupper(int c): Checks whether the passed character is an uppercase letter.int isspace(int c): Checks whether the passed character is white-space.int ispunct(int c): Checks whether the passed character is a punctuation character.int tolower(int c): Converts uppercase letters to lowercase.int toupper(int c): Converts lowercase letters to uppercase.
stdlib Library:
stdlib.hdefines variables and functions for performing general functions.- Functions in
stdlb.h:int atoi(const char *str): Converts the string pointed to, by the argumentstrto an integer (typeint).int abs(int x): Returns the absolute value of x.
C Variables and Functions:
Variables in C:
Data Types in C:
- Integer types:
int: signed integer, usually stored in 32 bits.inthas 32 bits, where 1 bit stores the sign and 31 bits store the number.- The range of integer is \([-2147483648,2147483647]\).
- When overflow, the variables starts from \(-2147483648\).
unsigned: unsigned integer.long: signed integer with significantly greater capacity than a plainint.
- Floating-point (decimal) types:
float: single-precision floating point number.floathas 32 bits, where 1 bit stores the sign, 8 bits stores the exponent, and 23 bits storing the mantissa.
double: double-precision floating point number. -doublehas 64 bits, where 1 bit stores the sign, 11 bits stores the exponent, and 52 bits storing the mantissa.
- Character type:
char: holds a 1-byte character.- Characters a basically integers.
- For ASCII Table,
0(or numbers) from48,Aor (capitalized letters) from65, andaor (lowercase letters) from97.
- Boolean type:
- Integer types are
bools, where0meansfalse, non-0or1meanstrue. - Otherwise, have
#include <stdbool.h>to includetrueandfalse.
- Integer types are
Variable Type Conversion:
Ccan automatically convert between types “behind the scenes”- This is automatic conversion and can be a promotion (smaller to larger), or narrowing (larger to smaller).
- When operand types don’t match, “smaller” type is promoted to “larger” before applying operator, with the order as:
char < int < unsigned < long < float < double.
- Narrowing conversions usually chop off the extra bits without rounding values.
Declaring and Initializing Variables:
- A variable
varis declared by:<data_type> var;
- It is good practice to initialize variables as it is declared, as:
<data_type> var = <value>;- Generally, a variable declared, but not yet assigned has an “undefined” value.
- A variable can be
constprotected:- Put
constbefore the type to say a variable cannot be modified. - Compiler will catch accidental modifications.
- Put
Operators and Precedence in C:
- Operators in
Cfollows the precedence rule. - The most prior operations, conducted from left to right is:
(): parentheses (function call operator).[]: array subscript..: member selection via object.->: member selection via pointer.++: unary post-increment.- Unary post-increment increment the variable after the variable is used.
--: unary post-decrement.- Unary post-decrement increment the variable after the variable is used.
- Then, are the following operators, conducted from right to left:
++: unary pre-increment.- Unary pre-increment increment the variable before the variable is used.
--: unary pre-decrement.- Unary pre-decrement increment the variable before the variable is used.
+: unary plus (absolute value).- Unary plus makes a variable to be positive.
-: unary minus (additive opposite value).- Unary minus makes the number to negative/positive when it’s positive/negative.
!: unary logical negation.~: unary bitwise complement.- Unary bitwise complement converts each digit between
0and1.
- Unary bitwise complement converts each digit between
(type):C-style unary cast.C-style unary cast does not check for validity of data type.
*: dereference.&: address.sizeof: determine size in bytes.
- Afterwards, are the binary, second order, operators, conducted from left to right:
*: multiplication./: division.%: modulus.- Modulus
%generates a negative residue with negative numerator.
- Modulus
- Hereby, are the binary, first order, operators, conducted from left to right:
+: addition.-: subtraction.
- Later, are the bitwise shifts, conducted from left to right:
<<: bitwise left shift.- Bitwise left shift is the equivalent with
*2. - The left most bit will be abandoned.
- Bitwise left shift is the equivalent with
>>: bitwise right shift.- Bitwise left shift is the equivalent with
/2. - The right most bit will be abandoned.
- Bitwise left shift is the equivalent with
- After that, are relations, conducted from left to right:
<: relational less than.<=: relational less than or equal to.>: relational greater than.>=: relational greater than or equal to.
- Following that, are equaling relations, conducted from left to right:
==: equal to.!=: not equals to.
- With the following, precedence are from up to down, conducted from left to right:
&: bitwise AND.- Bitwise AND is
1when both bits are1.
- Bitwise AND is
^: bitwise XOR (exclusive or).- Bitwise XOR is
1when only one bit is1.
- Bitwise XOR is
|: bitwise OR (inclusive or).- Bitwise OR is
1when at least one bit is1.
- Bitwise OR is
&&: logical AND.||: logical OR.
- Continually, there are the ternary conditionals, conducted from right to left:
?:: ternary conditionals.- Ternary conditionals is a condensed form of
if-elsestatement. - Ternary conditionals has the value after
:for value before?being0, and has value before:otherwise.
- Ternary conditionals is a condensed form of
- After this, the assignments are the next, conducted from right to left:
=: simple assignment.+=: assignment by sum.-=: assignment by difference.*=: assignment by product./=: assignment by quotient.%=: assignment by remainder.<<=: assignment by bitwise left shift.>>=: assignment by bitwise right shift.&=: assignment by bitwise AND.^=: assignment by bitwise XOR.|=: assignment by bitwise OR.
- Eventually, there goes the comma, conducted from left to right:
,: comma.
Functions in C:
Function definitions:
- A helper function include information about input and return type:
<retur_type> <function_name>(<input_type> <input_name>, ...) {
// Code in between.
return <value>;
}
- Helper functions need to be defined before the function is use.
- This could either be declared prior to the function call with definition in the end.
- The declare needs to contain the data type but not variable names:
<retur_type> <function_name>(<input_type>, ...);- Names of parameters are optional, but can be illuminating, as meaningful parameter names illustrate order of arguments.
The main function:
- The
mainfunction could take in some parameter and returns an integer for status:
int main() {
// Code in between.
if (condition) return 1; // return non-zero value to indicate error code.
return 0; // return zero for success.
}
- The
mainfunction could take in parameters from the command line:
int main(int argc, char* argv[]) {
// Code in between.
return 0; // return zero for success.
}
int argcis a parameter storing the number of command-line arguments.- The program name (e.g.
./a.out) counts as the first.
- The program name (e.g.
char* argv[]is an array of strings. The strings are the command-line arguments:- This could also be represented by
char argv[][]orchar **argv.
- This could also be represented by
Recursive Functions:
- Recursion is repeatedly making incremental progress until problem is solved.
- Recursion is another way to express repetition (as of
fororwhileloop):- Can be more succinct than loops.
- Leads to elegant solutions for some problems.
- Has some (potential) downsides.
- Every function call requires an stack frame, an area of memory storing variables, temporary values for a function call, and the return address indicating the code location where the function call will return to.
- Every recursive function call creates a new stack frame. The call stack, where stack frames are allocated, is limited in size.
- “Deep” recursion may fail because the call stack size is exceeded.
- Recursion works by:
- Dividing a large problem into one or more smaller problems.
- Then, extending the solution to the smaller problem(s) to be a solution for the large problem.
- Recursion is implemented by having a function make a call to itself using different arguments.
- A function calling itself is called a recursive call.
- Steps of recursive functions:
- The smaller problem(s) (“subproblems”) must have the same form as the larger problem.
- Subproblems are solved recursively, by making another call to the same method using different parameter values.
- There must be one or more instances of the problem that can be solved without recursion, known as the base cases.
- A recursive function must check to see whether a base case was reached before attempting to solve a subproblem recursively.
- Failing to do this could lead to an infinite recursion.
- All recursive calls must make progress towards a base case, so that the computation will eventually complete.
- The smaller problem(s) (“subproblems”) must have the same form as the larger problem.
Pseudo-random in C:
rand()generates (pseudo) random integers between0andRAND_MAX:- Distribution is uniform, so each value in range is equally likely to be generated.
- The pseudo random sequence of integers is based on a seed:
- Different seed implies different sequence of pseudo-random values.
srand(unsigned int)sets the seed value:- If
srand()is not called, by default, it uses seed1(as ifsrand(1)is called). - May use
srand(time(0))to generate time dependent random integers (time.his required).
- If
- The modulus (
%) operator is useful for constraining the range of values generated byrand():
| Code | Range of values (inclusive) |
|---|---|
| rand() | 0 to RAND_MAX |
| rand() % x | 0 to x-1 |
| (rand() % x) + y | y to y+x-1 |
| (rand() % x) / (double) x | 0.0 to \(1.0-\frac{1}x\) |
| rand() / (double)(RAND_MAX) | 0.0 to 1.0 |
- To generate a Normally Distributed Pseudo-random, use sum of many generations:
#include <math.h>
int randomNormal() {
int max_limit = 100;
double num = 0.0;
for (int i = 0; i < max_limit; i++) {
num += rand() - (RAND_MAX / 2.0f); // Increment by sum.
}
return (int) (num / max_limit); // Returns normally distributed integer.
}
Types of Variables:
Scope and Lifetime:
- Lifetime of a variable is the period of time during which the value exists in memory.
- Scope of a variable is the region of code in which the variable name is usable.
- Lifetime and Scope are inter-related, but are not the same.
Local (or Stack) Variable:
- Alive from point of declaration until end of block in which declared.
- Automatically created when enter that function, and destroyed when that function ends:
- The function ends precisely at the end of the function at
}.
- The function ends precisely at the end of the function at
- A variable in scope may be temporarily “hidden” when an inner scope declares a variable with the same name.
- E.g. a
forloop with the same variable creates a new scope such the outer scope is temporarily “hidden”.
- E.g. a
- Local variables go out of scope during calls to other functions.
- Local variables live in a region of memory known as the stack.
- Stack frames are added/removed as functions get called and then return.
Static Variable:
- Static variables are defined with a extra
statickeyword:static <data_type> <variable_name>;
- Static variables have similar scope to local variables, but longer lifetime.
- Static variables are automatically created and initialized to zero.
- Static variables are not destroyed at end of block of code.
- In the next time same block is executed, static variables come back into scope with the same value they had before.
- Static variables live in a region of memory known as the data segment.
- The data segment is allocated when program begins, freed when program exits.
Global Variable:
- Global variables are declared outside any function.
- Global variables have scope from point of declaration until end of program.
- Global variables can be accessed by any function.
- Global variables are automatically created and initialized to zero.
- Global variables can even be accessed by functions in other files.
- Need to bring into scope using
extern <type> <name>in targeted file.
- Need to bring into scope using
- Usage of global variables is generally discouraged:
- Debugging is harder, it is difficult to track which function might have changed a global variable’s value.
- Global variables cross boundaries between program modules, undoing benefits of modular code with readability, testability, and reusability.
- Values should, instead, be conveyed via parameter passing and return values.
- Global variables live in a region of memory known as the data segment.
- The data segment is allocated when program begins, freed when program exits.
Control and Flow:
Conditional:
ifconditional evaluates a represents some boolean expression for conducting code:
if (condition_1) {
// Code for condition_1.
}
else if (condition_2) {
// Code for condition_2.
}
else {
// Code for else.
}
- An
if-elseconditional can be represented by ternary conditions:
condition ? /* code for true */ : /* code for false */ ;
Calso has switch statements on decimals (intorchar):
switch (integer_expr) {
case c1: stmt1; // execution starting point for c1
case c2: stmt2;
break; // exits switch block
case c3:
case c4: stmt3;
stmt4; // executes stmt3, stmt4 and stmtlast for matches of c3 or c4
default: stmtlast; // if no case matches
}
Loops:
whileloop iterates \(\geq 0\) times, as long as the expression istrue:
while (boolean_expression) { /* statements */ }
do-whileloop iterates at least 1 time, then more times as long as expression istrue:
do { /* statements */ } while (boolean_expression);
forloop has initialize and iterates \(\geq 0\) times, as long as the expression istrue:forloop initialize happens first; usually declares & assigns “index variable”.- Right after statements, update is run; often it increments the index variable (
i++).
for (initialize; boolean_expression; update) { /* statements */ }
breakimmediately exits loop.continueimmediately proceeds to next iteration of loop.
C Input and Output:
File* Pointer:
fopen Function:
FILE*is a pointer to aFILE struct, andfopen()function returns aFILE*pointer.fopenfunction has different modes:r: reading.rwill not overwrite the file, it reads the lines.
w: open file for writing.- Note that
woverwrites the file immediately if it already exists. wcannot read.
- Note that
r+: open for reading & writing.r+will not overwrite the file, it appends the lines in the end.r+allows reading lines in the files as well.
w+: open file for reading & writing.- Note that
w+overwrites the file immediately if it already exists. w+allows reading.
- Note that
rb: open file for binary reading mode.- Particularly useful for reading large amounts of data in one operation.
- Literally copy bits from disk to memory (
fread). - Binary files are less portable than text, due to some types being different sizes on some architectures.
wb: open file for binary write mode.- Particularly useful for writing large amounts of data in one operation.
- Literally copy bits from memory to disk (
fwrite). - Binary files are less portable than text, due to some types being different sizes on some architectures.
fopenreturnsNULLiffopenfailed.- Always need to check, since reading or writing
NULLcauses a crash NULLis a special pointer value, usually equal to 0- This is common way to indicate failure for functions with pointer return type.
- Always need to check, since reading or writing
Other Open Functions:
feof(fileptr)returns non-zero if the pointer read past the end of the file.ferror(fileptr)returns non-zero if file is in an error state.- E.g. if we’ve opened file for writing but then attempt a read.
rewind(fileptr)returnsfileptrto beginning of file.fclose(fileptr)closes thefopenfunction to prevent a memory leak.
Standard Structure for fopen:
- The complete code for read file is:
// file_io_loop_eg.c:
#include <stdio.h>
int main() {
FILE* input = fopen("numbers.txt", "r"); if (input == NULL) {
fprintf(stderr, "Error: could not open input file\n");
return 1; // indicate error
}
int a = 0, b = 0;
int numCollected = fscanf(input, "%d%d", &a, &b);
while (numCollected == 2) {
printf("%d\n", a+b);
numCollected = fscanf(input, "%d%d", &a, &b);
}
if (ferror(input)) {
fprintf(stderr, "Error: error indicator was set for input file\n");
return 2; // indicate error
} else if (numCollected != EOF) {
fprintf(stderr, "Error: could not parse line\n");
return 3; // indicate error
}
fclose(input); // Close input file return 0; // no error
return 0;
}
Program Output:
printf for output:
printfis limited to directing output tostdout:printfdepends on thestdioheader, by#include <stdio.h>.
printfallows for formatted printing of values, using placeholders in the format string:printfsyntax is:printf("...%<specifier>...", <var>);printfdoes not have a new line, so a newline character\nshould be added.
printfhas the placeholders for most common data type:
| Placeholder Letter | Placeholder Type |
|---|---|
d |
decimal (integer type, ld for long int) |
u |
unsigned (integer type that disallows negatives, lu for long unsigned) |
f |
floating point (float, lf for double) |
c |
character |
s |
string |
printfalso allows formatted print, for floating type,printfallows precision of the data:- For rounding down digits, we use:
printf("%0.<decimal_places><data-type>");
- For rounding down digits, we use:
- If successful, the total number of characters written is returned.
- On failure, a negative number is returned.
fprintf for output:
fprintfallows printing to a location not limited tostdout:fprintfdepends on thestdioheader, by#include <stdio.h>.- Directing to standard output:
fprintf(stdout, "message"); - Directing to standard error:
fprintf(stderr, "message"); - Directing to file pointer:
fprintf(fileptr, "message");(must allow writing)
- If successful, the total number of characters written is returned.
- On failure, a negative number is returned.
sprintf for output:
sprintfallows sending the print output to a string:sprintfdepends on thestdioheader, by#include <stdio.h>.- Directing output:
fprintf(<char-array>, "message");
- If successful, the total number of characters written is returned.
- On failure, a negative number is returned.
fwrite for output:
fwritetake a pointer to a block of memory, an element size, a number of elements, and a file-handle.fwritecopies data from memory to the specified file:int items_written = fwrite(where_from, size_of_el, num_els, fp);fwritereturns the number of items successfully written (should be same asnum_els).
Program Input:
scanf for input:
scanfis limited to taking input fromstdin:scanfdepends on thestdioheader, by#include <stdio.h>.
scanfreading formatted input:scanfuses a format string followed by the memory location(s) to read into.scanfsyntax is:scanf("...%<specifier>...", <var-address>);printfdoes not have a new line, so a newline character\nshould be added.
scanfhas specifier for capturing the following data type:
| Placeholder Letter | Placeholder Type |
|---|---|
d |
decimal (integer type, l for long) |
u |
unsigned (integer type that disallows negatives, lu for long unsigned) |
c |
character |
f |
floating point (float, lf for double) |
s |
string (default reads to a space) |
- When using
scanf, make sure that the memory location to fill accommodate the type. scanfreturns a value that indicates how many successful read is made:- Specifically, for
EOF(end of file) orctrl-D(break),scanfreturns-1.
- Specifically, for
scanfmight need accommodation in skipping spaces / newlines in operations.- To skip whitespaces, use:
scanf(" %<specifier>", <var-address>)". - To move until a new line, use:
- To skip whitespaces, use:
char dummy = '\0';
while (dummy != '\n') {
if (scanf("%c", &dummy) == -1) {
break;
}
}
fscanf for input:
fscanfallows reading from a location not limited tostdin(command line input):fscanfdepends on thestdioheader, by#include <stdio.h>.- Reading from standard input:
fscanf(stdin, "input-formatting", <var-address>); - Reading from file pointer:
fscanf(fileptr, "input-formatting", <var-address>);(must also reading).
fscanfalso returns a value that indicates how many successful read is made:- Specifically, for
EOF(end of file) orctrl-D(break),scanfreturns-1.
- Specifically, for
sscanf for input:
sscanfallows reading from a string (or array of characters):sscanfdepends on thestdioheader, by#include <stdio.h>.- Reading:
sscanf(<input_string>, "input-formatting", <var-address>);
sscanfalso returns a value that indicates how many successful read is made:- Specifically, for
EOF(end of file) orctrl-D(break),scanfreturns-1.
- Specifically, for
fread for input:
freadtake a pointer to a block of memory, an element size, a number of elements, and a file-handle.freadreadssize_of_el * num_elsbytes of memory from the file beginning at the file cursor locationfp, and stores them starting at pointer locationwhere_to:int items_read = fwrite(where_to, size_of_el, num_els, fp);freadreturns the number of items successfully read (should be same asnum_els).
Arrays and Pointers in C:
Arrays:
1D Arrays:
- An array variable is a collection of elements laid out consecutively in memory.
- All elements have the same declared type.
- Individual elements accessed with
[]notation:- The actual value of an array variable is a memory address in
C.
- The actual value of an array variable is a memory address in
- To declare a 1D array:
<data-type> <var-name>[<size>];- Square brackets go after the variable name, not after the type.
- Array values are undefined until explicitly initialized:
- For initializing all elements, it is:
int array[3] = {1, 2, 3};- When initializing with
{...}, array size can be omitted, that is:int array[] = {1, 2, 3};
- When initializing with
- For initializing with all zeros, it is:
int array[3] = { 0 }; - For initializing a single element, it is:
int array[3]; array[0] = 1;
- For initializing all elements, it is:
- Array are different from other structure:
- Can’t assign one array to another using
=:- Need loop to copy elements from one array to another.
- There is no “slicing” in
C. - An entire array can’t be printed or scanned (Except strings).
- Can’t assign one array to another using
- When accessing elements out of bound, the compiler will not have error, as arrays are memory address.
- Accessing elements out of bound (that are not owned) will cause memory leak.
- An array does not need to be freed after use.
Strings:
- String is a sequence of characters handled as a unit.
- In
C, a string is an array of characters with final character equal to the “null character”,\0, also called the “null terminator”.- Null termination is used to indicate where the string ends.
sizeofof a string is the total bits it contains, including\0.strlenfunction returns number of chars before\0.- Both functions return unsigned long,
%luformat.
- A string is declared as an array of
char:- Declare a string as an array:
char day[] = "text"; - Declare a string as a pointer:
char *day_ptr = "text"; - The string can also be defined expanded:
char day[] = {'t', 'e', 'x', 't', '\0'}; charin a string can be accessed by index.
- Declare a string as an array:
- In copying an array, it needs to be done by loops:
const char str[] = "text";
char str_copy[sizeof(str)];
for(int i = 0; i < sizeof(str); i++) {
str_copy[i] = str[i];
}
- There are no concatenation operator
+or assignment=between strings declared as arrays.
Multi-dimensional Arrays:
- In declaring multi-dimension arrays, use:
<base_type> <name>[dim1_sz][dim2_sz]...; - In defining 2D arrays, use
int table[2][4] = { {1,2,3,4},{5,6,7,8} };:- Physically in memory, the array is laid out as one contiguous block of \(2\times4=8\)
int-sized locations. - Array elements are stored in row-major order: all of
row1comes first, then all ofrow2, …
- Physically in memory, the array is laid out as one contiguous block of \(2\times4=8\)
- In 2D arrays, the variable holds address of first element.
var[i][j]- refers tojth element inith row.- In 2D arrays,
var[i][j]is considered asi * row_size + j-th element, so it could also get out of bound, causing memory leak.
Arrays in functions:
- When arrays are passed into functions, they are passed a pointers, so modifications on them will be reflected.
- When passing arrays, the dimension for the first dimension may not be passed, but the others should be passes.
- The dimension for the first dimension needs to be passes by a separated variable.
- E.g.
void sum_matrix(int list[][4], int numRows);butvoid sum_matrix(int list[], int numRows);
constprotection for arrays in functions:- Arrays could be
constprotected, so it cannot be modified. - When
constprotect is in the function parameter, non-protected variables can be passed in. However, when the function parameter does not haveconstprotect, aconstprotected variable cannot be passed in.
- Arrays could be
- An initialized array cannot be returned as a
returnof a function.- In terms returning arrays, it could either be a parameter or returned by dynamic allocation
Pointers:
Pointers in C:
- A pointer is a variable that contains the address of a variable.
- Every pointer points to a specific data type (except a pointer to
void).void *pointer can be casted so it prints the address of pointer.
- Every pointer points to a specific data type (except a pointer to
- Declare a pointer using type of variable it will point to, and a
*:<data-type> *<var-name>; - Operations related to pointers:
- Address-of operator
&: returns address of whatever follows it. - Dereferencing operator
*: returns value being pointed to.
- Address-of operator
- Pointers can be passed into functions so the variables can be modified.
Pointer Arithmetics:
- Pointer assignment assigns pointers of the same type:
- In
ptr1 = ptr2, only address inptr2is copied, and they reference the same memory location.
- In
- Pointer comparisons compares the address of the pointer:
ptr1 == ptr2andptr1 != ptr2compare the addresses inside the pointer variables for equality.ptr == NULLchecks ifptris0.ptr1 < ptr2compares addresses, ifptr1has a smaller address.
- Operators
+,-,+=,-=can be used with other pointers or integers for the 2nd operand:- Most often used on pointers into arrays.
- Doesn’t add the actual number, it adds that number times how many bytes each element takes up based on the pointer base type.
- E.g.
a[3]is a synonym for*(a + 3)(offset three from pointer to start of array) - E.g.
&a[3]is a synonym fora + 3.
- Pointer subtraction is the only defined semantics for two pointers:
ptrdiff_tis a predefined type in librarystddef.h, as the resulting type when subtracting pointers (memory addresses).- It is essentially equivalent to the
longinteger type.
Dynamically Allocation:
- Dynamically-allocated memory is located in a part of memory separate from the stack, it lives on “the heap”:
- User is responsible for allocating and freeing memory in the heap.
- If not freed, it will cause memory leak.
- Dynamically-allocated memory lives long until entire program ends.
- Dynamically-allocated memory is not subject to size limitations based on stack frame size, since it’s not part of the stack:
- The size of a dynamically-allocated block of memory can be decided at run time.
- To use dynamic allocation, it is needed to include
#include <stdlib.h>. mallocdoes not initialize the variables:
// Allocating space for size data_type on heap
<data_type> *ptr = malloc(sizeof(<data_type>) * size);
// Check if allocation succeeded
if (ptr == NULL) { /*output error message*/ }
// Free the pointer after use
free(ptr);
callocinitialize the variables with bits into0:
// Allocating space for size data_type on heap
<data_type> *ptr = calloc(size, sizeof(<data_type>));
// Check if allocation succeeded
if (ptr == NULL) { /*output error message*/ }
// Free the pointer after use
free(ptr);
reallocreallocates the given area of memory, and can be used for both expanding and contracting. (The area must have been previously dynamically allocated.)- This is achieved by expanding or contracting the existing area, if possible or allocating a new memory block of new size bytes.
- When success, it returns the pointer to the beginning of newly allocated memory, else, it returns a null pointer.
// reallocate to expand or contract
ptr = realloc(ptr, sizeof(<data_type>) * new_size);
Multi-dimensional Arrays by Dynamic Allocation:
- A 2D array con be dynamically allocated using one dimension:
<data_type> *array = malloc(sizeof(<data_type>) * num_rows * num_cols);- In this definition, it converts
[row][col]indexing to[row * num_cols + col].
- A double (
**) memory allocation allows non-rectangular shapes.
// Declaring and initializing the double pointer.
<data_type> **ptr = malloc(sizeof(<data_type>*) * num_rows);
// Fill in each pointer with pointer.
for (int i = 0; i < num_rows; i++) {
a[i] = malloc(sizeof(<data_type>) * num_cols);
}
- When freeing multi-dimensional arrays, it is important to free the memory for each dimension and each multi-pointers themselves.
const Protection for Pointers:
- In terms of
const, read declarations from right to left. - To make a (mutable) pointer to const (non-modifiable) data:
const int * iptr;- This is a pointer to a constant integer.
- Prevents changing contents of the pointed to memory
- Allows changing pointer address.
- Similar to const
int iray[];as a function parameter.
- To make a const (non-modifiable) pointer:
int * const iptr;- This is a constant pointer to an integer.
- Prevents assignments to change the address.
- Allows pointer variable itself.
- Similar to
int iray[10];as a local variable.
- To make a const ptr to const data:
const int * const iptr;- This is a constant pointer to a constant integer.
- Prevents changes to pointer variable itself or the memory it points to.
- Similar to
const int iray[] = { 1, 2, 3 };as local variable.
struct in C:
Introduction to struct:
Defining struct:
structis a collection of related variables, bundled together into one variable:- Variables in a struct are fields. Fields can be variable type, pointers, or other structures.
struct card {
// Fields
int rank;
char suit;
};
- After definition, a variable of type
structcan be declared as initialize by:
struct <struct_name> <var_name>; // Declaring a struct
<var_name> = { ... }; // Initialize struct as arrays.
- In
struct, there are ways of accessing data:- Fields in a
structcan be accessed viavariable.fieldorptr->field. - When defining
structinstruct, there is a simplified declare for nested define:
- Fields in a
typedef struct {
struct { // Unnamed structure.
int r; // Fields in this struct.
int b;
int g;
} color;
struct { // Second Unnamed structure.
int x;
int y;
} position;
} pixel;
Struct and Functions:
sizeoffunction forstruct:sizeofastructreturns total size of all fields.- Size of struct is at least the sum of the sizes of its fields It can be bigger if the compiler decides to add “padding”.
- In “padding”, the system adds the length of different data type to be uniform.
- A struct can be a function parameter and/or return type.
- When a
structis passed to a function, everything inside is copied, so it is passed by value:- This includes
arrays, meaning that anarraywrapped in astructis pass-by-value.
- This includes
Linked Lists:
Linked List as a Data Structure:
- Linked list is a linear data structure in which the elements, called nodes, are not stored at contiguous memory locations (in contrast with arrays).
- Each node comprises two items: the data it stores and a pointer to the next node.
- The head of a linked list (entry point) is a pointer points to the first node:
- The head pointer is not itself a node; it just holds the address.
- In an empty linked list, the head pointer points to
NULL.
- The last node’s next pointer points to
NULL.
- The linked list is a
struct:
typedef struct _node {
char data;
struct _node *next;
} Node;
- Comparing arrays with linked lists:
| Array | Linked List |
|---|---|
| size of the array is fixed | sized of linked list is not fixed |
| occupies less memory for the same number of elements | requires more space because of “next” |
| accessing \(i\)-th value is fast using indices (simple arithmetic) | has to traverse the list from start |
| inserting new elements is expensive | after deciding where to add, is straightforward (no shifting) |
| no deleting without shifting items | deleting is easy (kind of) |
Functions in Linked List:
printprints the data in the linked list:
// Print all the elements in the linked list
void print(const Node * cur) {
while (cur != NULL) {
printf("%c ", cur->data);
cur = cur->next; // advance to next node
}
}
lengthcounts the number of nodes in a linked list:
// Get the length of nodes in a linked list
long length(const Node * head) {
if (head == NULL) return 0; // Base case, return 0
return 1 + length(head->next);
}
add_afterinserts new node with a given data value immediately after a given existing node:
// Add a new node to linked list
void add_after(Node * node, char val) {
if (node == NULL) return;
Node * n = create_node(val);
n->next = node->next;
node->next = n;
}
add_frontinserts a new node in the beginning of the list:
// add node to beginning of list
void add_front(Node ** list_ptr, char val) {
Node * n = create_node(val);
n->next = *list_ptr;
*list_ptr = n;
}
remove_afterremoves node after current and returns the removedchar:
// remove node after current, return removed char, or '?' if no node
char remove_after(Node * node) {
char rmd_value = '?';
if (node->next == NULL) return rmd_value;
Node * ptr = node->next->next;
rmd_value = node->next->data;
free(node->next);
node->next = ptr;
return rmd_value;
}
remove_frontremoves first node, if any, return removedchar:
// remove first node, if any, return removed char or '?' if no node
char remove_front(Node ** list_ptr) {
char rmd_value = '?';
if ((*list_ptr) == NULL) return rmd_value;
Node * ptr = (*list_ptr)->next;
rmd_value = (*list_ptr)->data;
free(*list_ptr);
*list_ptr = ptr;
return rmd_value;
}
remove_allremoves all occurrences of a particular character:
// remove all occurrences of a particular character
void remove_all(Node ** list_ptr, char val) {
if (*list_ptr == NULL) return;
if ((*list_ptr)->data == val) {
remove_front(list_ptr);
remove_all(list_ptr, val);
} else {
remove_all(&((*list_ptr)->next), val);
}
}
clear_listfrees all the nodes:
// free all the nodes
void clear_list(Node * list_ptr) {
if (list_ptr->next == NULL) {
free(list_ptr);
} else {
clear_list(list_ptr->next);
}
}