CNotes
CNotes
○
● What is perrror?
○ Function define din <stdio.h>
○ Prints an error message to standard error, describing the last error that occurred
based on errno
○ Void perror(const char *s)
■ S is a user defined string that’s prefixed to the error message
● Malloc interacts with the operating system through system calls
● Sbrk and brk are traditional UNIX system calls for managing program break
● Brk
○ Sets The break pointer which marks the end of the heap
○ Int brk(void* addr)
○ Parameters:
■ Addr: A pointer to the new end of the heap
○ Returns
■ 0 on success
■ -1 on failure (Sets errno)
○ Heap is a contiguous block of memory that starts just after the program’s data
segment
○ Brk moves the program break directly to the specific addr, expanding or shrinking
the heap accordingly
○ If the request address exceeds system limits or it overlaps with other segments, it
fails
■ void* current_break = sbrk(0); // Get current program break
● Sbrk
○ Adjusts the program’s break pointer by a specified increment (amount to increase
or decrease the program break)
○ Moves program break up or down by the increment value
○ Internally calls brk to adjust the pointer
○ Returns:
■ Previous break pointer on success
■ Failure: (void*) -1 on failure (sets errno).
○ sbrk(0) - used to get the current program break
● Malloc
○ Allocates a block of memory on the heap
○ void* malloc(size_t size);
○ Parameters:
■ Size: number of bytes to allocate
○ Returns:
■ Pointer to the allocated memory block
■ NULL if the allocation fails
○ Malloc uses brk and sbrk to allocate memory on teh heap
○ It manages a memory pool and returns a block from this pool
○ The memory is uninitialized so the contents of the allocated block are undefined
○ Pointers:
■ Always check if malloc returned null
■ Memory allocated with malloc should be freed using free to avoid memory
leaks
○ Free
■ Deallocates memory rpevously allocated by malloc, calloc, or realloc
■ void free(void* ptr);
■ Parameter = pointer to memory block to be freed
■ Behavior:
● Memory becomes available
● Memory allocator strategies
○ First Fit
■ Search for first block >= requested size and put block in there but this can
lead to fragmentation
○ Best Fit
■ Search for the smallest block >= requested size
● Less fragmentation but slower
○ Worst Fit
■ Search for largest block
● Good for splitting, poor memory utilization
● Strcpy, strcat, strncpy, strncat, strtok
● int compare(const void* a, const void* b) {
● return (*(int*)a - *(int*)b);
● }
○ Result determines the relative order of the two elements (negative value: a < b;
positive value: a > b; zero a= b)
○ Used with qsort which sorts arrays:
■ void qsort(void* base, size_t num, size_t size, int (*compare)(const void*,
const void*));
● Error handling
○ // Return value error checking
○ FILE* file = fopen("data.txt", "r");
○ if (file == NULL)
■ { perror("Error opening file");
■ return 1; }
● Introduction to GCC
○ GCC: A robust, open source compiler system that support multiple programming
languages
○ Used primarily on linux/unix
○ Compiles code into machine-readable executables
○ Supports optimizations, warnings, debugging, and linking with libraries
● Basic Compilation with GCC
○ gcc -o output_file source_file.c
■ -o output_file: specifies the name of the output file
■ Source_file.c: the C source code to compile
■ Ex.
● Gcc -o hello hello.c
■ If you don’t specify -o, GCC outputs the executable as a.out
● Compilation without linking
○ Use -c to generate an object file (.o) from a source file without linking (useful for
separating compilation and linking stages, especially for larger projects)
● Include Paths for header files
○ Use -I to specify directories containing header files
○ Ex. for a project structure like this:
■ project/
■ ├── src/
■ │ ├── main.c
■ │ ├── functions.c
■ ├── include/
■ ├── functions.h
■
○ Compile as shown:
■ Gcc -I include -o program src/main.c src/functions.c
● LInking with libraries
○ Use -L to specify the directory containing libraries and -l to link a specific library
○ Example project structure:
■ project/
■ ├── src/
■ │ ├── main.c
■ ├── libs/
■ │ ├── libmylib.a
■ ├── include/
■ ├── mylib.h
■
○ Compile with:
■ gcc -Iinclude -Llibs -o program src/main.c -lmylib
■ -lmylib: links libmylib.a
● Use -Wall to enable all common warnings, which help detect potential issues
○ gcc -Wall -o program program.c
● Use -g to include debugging symbols for tools like gdb
○ gcc -g -o program program.c
● Optimizations -0 followed by a number to control optimization levels
○ -O0: No optimization (useful during debugging).
○ -O1: Minimal optimization.
○ -O2: Default optimization for speed.
○ -O3: Maximum optimization for speed.
● Compile and link multiple files together
○ gcc -o program file1.c file2.c
● Or you can separate compilation and linking:
○ Compile each file:
■ gcc -c file1.c
■ gcc -c file2.c
○ Link the object files:
■ gcc -o program file1.o file2.o
● Use the linker (ld)
○ Ld is the linker GCC invokes internally. Use it directly for:
■ Fine control over linking
■ Skipping redundant calls to gcc
■ gcc -c main.c # Creates main.o
■ gcc -c helper.c # Creates helper.o
■ ld -o program main.o helper.o
Advanced example:
Given the project structure:
project/
├── src/
│ ├── main.c
│ ├── functions.c
├── libs/
│ ├── libmylib.a
├── include/
│ ├── functions.h
Understanding Linking
● What is linking
○ Linking is the step after compilation where
■ The object field generated during compilation are combined
■ External symbols are resolved
■ Libraries are included to provide additional functionality
○ Static linking
■ Libraries are included directly in executable (results in larger executables
w/o dependency on external library files)
○ Dynamic linking
■ Libraries are not included in the executable but are linked at runtime
■ Result in smaller executables and allows sharing of libraries among
multiple programs
○ Ld
■ Command that’s a low level linker used to combine object field and
libraries into an executable
■ While gcc autoamtes linking internally, ld provides control over the
process
○ Key concepts:
■ Undefined symbols may be resolved during linking (external function
calls)
■ Object files: contain machine code and symbol information
■ Produced using the -c flag in gcc
■ Librairies; .a (archive of object field)
■ .so (shared libareis linked at runtime)
Header files, implementation files, and main files in c
Header files
- #ifndef FUNCTIONS_H
- #define FUNCTIONS_H
-
- // Function prototypes
- void greet();
- int add(int a, int b);
-
- #endif // FUNCTIONS_H
-
- #include “functions.h” in both the corresponding functions.c file and main.c
Makefiles
Explicit rules says
- Makefiles are a build automation tool that help to compile and manage projects
- When adn how to remake one or more files, called the rule’s targets
- Lists the other files that the targets depend on called prereqs of the target
- May also give recipe to use to create or update teh targets
Implicit rules says
● When adn how to remake a class of files based on their names
● Describes how a target may depend on a file with a name similar to the target
● A recipe to create or update such a target
Variable definition
● A line that specifies a text string value for a variable
● Be substituted into the text later
Directive
● Instruction for make to do something special while reading the makefile
○ Reading another makefile
○ Deciding (based on value of vars) wether to use or ignore a part of the makefile
○ Defining a variable from a verbatim string containing multiple lines
Comments
● # in a line of a makefile is used to start a comment
● It and rest of the line are ignored
● Except that a trailing backslash not escaped by another backslash will continue the
comment across multiple lines
Anu’s notes
● Two main types of rules to define how targets are built
○ Implicit rules
○ Explicit rules
■ Explicitly defines hwo to build a target from its prerequisites
■ These results are written for specific files or targets and explicitly list all
the prerequisites and the recipe commands to build the target
○ Implicit rule
■ Is a predefined rule that tell snake how to build certain kinds of files
depending on their names, without needing to explicitly define each one
■ Rules are built into make and applied automatically when certain patterns
are matched
■ Ex.
● %.o: %.c
○ Gcc -c $< -o $@
■ In the example above the pattern is: %.o:%.c which tells make that for any
.o file (target) that corresponds to a .c file, use the gcc command to
compile it
■ $< represents the first prerequisite, which in this case is the .c file
■ $@ represent the target file (which in this case is the .o file)
● If main.c is updated, make will automatically use this rule to create main.o without
needing to explicitly define it for each file
● Directives in makefiles
○ Directives are special instructions that modify how make processes the makefile
○ Directives are not rules for building targets; they are commands that change the
behavior of make
○ They provide additional functionality like including other files, setting variables, or
defining conditional logic
○ Command directives in makefiles:
■ Include: include another makefile inside this one
● Purpose: the directive is used to include another makefile. This
allows you to break a large makefile into smaller, more
manageable pieces
● Syntax:
○ Include filename.mk
■ Includes the contents of filename.mk into the
current makefile. If filename.mk contains rules or
variables, they will be available in the current
makefile
■ Define
● Purpose: defines a multiline variable (useful when you need a
variable to span more than one line)
● Ex.
○ define VARIABLE_NAME
○ line1
○ line2
○ Endef
■ ifeq/ifneq (conditional directives)
● Purpose: conditional statements that allow make to execute part of
the makefile depending on wether certain conditions are true
● Ex.
○ ifeq($(CC), gcc)
■ CFLAGS += -02
○ Else
■ CFLAGS += -00
○ Endif
● This conditional checks wether the CC variable is equal to gcc. If it
is, -02 optimization flags are added ot the CFLAGS variable.
Otherwise -00 (no optimization) is used
■ Export
● Purpose: makes a variable available to sub-make processes
○ Syntax:
■ Export VAR_NAME = value
■ Ex. export CC = gcc
● Makes teh CC var available to any child
make processes
■ Override
● Used to ensure that a variable is set to a particular value even if it
was defined earlier in teh Makefile or from the environment
○ Override VARNAME = value
○ Override CFLAGS = -g -Wall
● Basic Structure
○ Cosnisits of rules of the following format:
■ Target: the file to be created or action to be executed
■ Dependencies: files need to build the target
■ Commands: shell commands to build the target (MUST be indented with
a TAB)
● Command text itself will print out
● Ex. if you said echo “This WILL be printed to the console”
(everything including echo will be outputeed?)
● To suppress use the @ in front to nto see verbose
● Ex. @echo “This will be printed to the console”
● Managing dependencies and file locations
○ Full paths
■ You can specify full paths to your source adn header files directl yin
makefile but it can become cumbersome as project grows
■ Make has built-in rules that allow it to search for prerequisites in certain
locations automatically. These built-in tools will handle common file types
and their default locations without the need to manually specify paths
each time
● Ex. main.o: main.c
● gcc -c main.c -o main.o
■ Make automatically knows to look for main.c in the current directory
because .c files are implicitly searched for in the current working directory
■ However, if the file is in another directory, you may need to use one of the
other methods to specify the path
○ VPATH variable
■ VPATH variable in Makefiles specifies the directories where make should
search for field that are used as prerequisites (depdnencies)
■ Useful wehn your source files and headers are in diff directories adn you
want to keep makefile clean
■ VPATH = src:../headers
● Tells make to search teh src and ../headers directories for the
prerequisites
● This way make will look for main.c in src/ and main.h in ../headers
without needing to specify the full paths
■ Vapth directive
● Vpath pattern directory
○ Ex. vpath %.h ../headers
● Makefile variables
○ Make makefiles more maintainable with text replacement
○ Ex.
■ Objects = program.o foo.o utils.o
■ Program: $(objects)
● cc -o program $(objects)
○ $(objects) : defs.h
● Variable expansion in makefiles (simple vs recursive)
○ Recursive variable expansion - the value of the variable is evaluated at hte time
of use; can lead to diff results
■ Ex.
■ foo = abc
■ bar = $(foo)
■ foo = xyz
■
■ all:
■ echo $(bar)
■ Return xyz
○ Simple variable expansion
■ foo := abc
■ bar := $(foo)
■ foo := xyz
■ all: echo $(bar) # Prints abc
○ Undefined conditional
■ FOO ?= bar
■ FOO ?= bar checks if the variable FOO is undefined or empty.
■ If FOO is not already defined (i.e., its value is empty or it doesn't exist), it
will be assigned the value bar.
■ If FOO already has a value, this assignment will not change it.
○ SRCS = $(wildcard $(SRC_DIR)/*.c)
■ Wildcard function in make - selects all files which match the pattern
● $(SRC_DIR)/*.c
■ Pattern substitution example:
● OBJS = $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
● % acts as a placeholder for the filename without the extension
● Trasnofms the name of .c files to object field
○ $@: Target name
○ $<: First dependency
○ $^: All dependencies
○ $*: Stem (part that matched the %)
● Phony Targets
○ Declare targets that don;t create files
■ Avoid a conflict with a file of the same name
■ Improve performance
● -DDEBUG: Defines the DEBUG macro for the preprocessor. This is useful for conditionally
including debug-specific code (e.g., using #ifdef DEBUG in C source files).
● Multilayer make files
○ $(MAKE) -C lib: Recursively calls the Makefile in the lib directory
● -I../include
○ Tells compiler to search for header files in a certain directory (in this case
../include) for the preprocessing step
Low level C programming and GCC internals
● Stage 1: preprocessing (gcc -E)
○ The preprocessor handles directives like #include, #define, and #ifdef
■ Expands macros (all macros you define like #define are replaced with
their values or instructions)
● Ex. #define SQUARE(x) (x*x)
● After preprocessing SQUARE(5) becomes (5*5)
■ Includes header files (copies and pastes everything from teh header files
into this code wherever you have the #include)
■ Removes comments
○ Gcc - E example.c -o example.i
■ Example of producing a preprocessed file
○ GCC automatically adds built in macros like
■ __STDC__: says your compiler is following the C standard
■ __GNUC__: Gives the GCC version (ex. 11)
■ __linux__: says you’re on linux
● These macros can help make your code portable across diff
systems
○ The preprocessor keeps track of where code comes from using lines like
■ #1 “example.c”
● We’re now working on line 1 of the file example.c - this is the
output from the preprocessor indicating which files nd line
numbers are being processed and show show preprocessor
handles file inclusions, built in defs, and command line options
● Why does this matter?
○ If an error happens, these line directives help the compiler
and you know where the problem is in your og file
○ Preprocessed output shows you exactly what the compiler
sees after macros and includes are processed
● Stage 2: Compilation to Assembly (gcc -S)
○ Gcc -S example.i -o example.s
○ Generate assembly with detailed comments explaining each assembly
instruction:
■ gcc -fverbose-asm -S example.c
○ Check stack usage
😭
■ gcc -fstack-usage example.c
○ CS 33 flashbacks
● Stage 3: Assembly to object code (gcc - c)
○ Converts assembly code to machine code in object file format
● Stage 4: Linking (gcc example.o -o example)
○ Combines object fiels into single executable file and resolves external references
(these are functions/varaibles declared in one file but defined in another)
○ Ex.
■ When you compile files separately with -c they become object files
● Gcc -c file1.c file2.c
■ The linker combines them into one executable file
● Gcc file1.o file2.o -o program
○ What happens with external library calls?
■ When your program uses a function from an external library (like printf
from libc) the actual location of that function in memory isn’t known until
that program runs. Instead of hardcoding the address, your program uses
a mechanism called PLT (Procedure Linkage Table) and GOT (Global
Offset Table) to figure it out during runtime
○ PLT (procedure linkage table)
■ Think of PLT as a “to do list” or a shortcut table for calling external
functions
■ Doesn’t store the real address of a function at first (it know how to ask
dynamic linker to find the address when needed)
○ GOT (Global Offset table)
■ The GOT is like a notebook where the real address of functions are
written once they are known
■ Initially the entries in the GOT don’t have the actual address but they get
filled during program execution
○ Lazy Resolution
■ Functions are only resolved when you call them for the first time (lazy
resolution)
● PLT tells dynamic linker “hey find the real address of this function”
when it first encounters an external function
● The dynamic linker looks up the function in external library, finds
the actual address in memory and updates the GOT with that
address
● Now every future call to that function uses the address from the
GOT (no need to ask linker again)
○ Why lazy resolution
■ Prgoram doesn’t need to know exact address ofhte library functions
beforehand (maeks it easier to replace/update libraries without chiangng
your program)
○ What is inside the executable?
■ Once linked, the executable contains different parts or segments
■ Text segment(code)
● Contains actual instructions for program
● Read-only and shared
■ Data segment (initialized data)
● Holds global and static varaibels that are initialized
● Read-write property during program execution (values can be
updated)
● Take space in the executable file
■ BSS Segment (Uniinitalized Data)
● Holds globala nd static varaibels that are NOT initialized
● These variables are automatically set to 0 at runtime
● They take no space in the executable file (only metadata is stored)
● Name: stands for block started by symbol
■ Dynamic Linking information
● Information that helps the program find and use external libraries
(like libc)
● Symbol tables and reallocation information tell the dynamic linker
(ld.so) where to find external functions like printf
● These are resolved at funtime using PLT and GOT
local_var:
● Explanation: You mentioned that local_var is not visible in the symbol table. This is
because it’s a local variable created on the stack and doesn't have a global or static
scope. It won't appear in the symbol table because it is managed at runtime and only
exists during the execution of the function in which it is declared.
9.
10.For debugger optimizatons:
a. Performs instruction scheduling: Rearranges instructions to improve CPU
efficiency.
b. Inlines small functions: Replaces function calls with their actual code.
c. Adds vectorization: Converts scalar operations into SIMD (Single Instruction,
Multiple Data) for parallel execution.
d. What is SIMD
i. Single instruction, multiple data - technique used in modern processors to
perform the same operation on multiple pieces of data at the same time
ii. Makes programs run faster, especially when dealing with repretitive tasks
lik eprocessing arrays
iii. Ex. instead of mulitplying numbers one by one (multiply them all in one
swing)
11.Loop Optimization in GCC
a. Loop vectorization
i. gcc -ftree-vectorize -fopt-info-vec example.c
1. Transform sloops to use SIMD instructions allowing multiple
iterations to be executed simultaneously
2. Makes loops process multipel data points at once, speeding up
tasks like mathematical calculations on arrays
b. Enabling SMD with native instructions
i. gcc -march=native -ftree-vectorize example.c
1. Generates code that uses the best SIMD instructions available on
you CPU
c. Cache optimization reports
i. gcc -fopt-info-vec-optimized example.c
ii. Tells you if the compiler optimized your loops for cache performance
iii. Loops that work well with the CPU’s cache run faster because accessing
memory from cache is quicker than RAM
d. Cache prefetching
i. gcc -fprefetch-loop-arrays example.c
ii. Tells the CPU to load data into the cache ahead of time
iii. Reduces waiting times when the loop needs the data
Can we tell the compiler what to do/ what I am doing gin advance?
- Compiler attributes are hints we give to the compiler to optimize, enforce, or modify how
our code behaves
- Can apply to functions, variables, or types and let us fine tune performance
- __attribute__((attribute_name))
- Attributes appear before or after the function/variable declaration
- __attribute__((noreturn))
- Indicates function will enver return
- __attribute__((const))
- Specifies that the return value depends only on input parameters (no global
memory or side effects)
- __attribute__((deprecated("Use new_function() instead")))
- Marks a function as outdated and issues warning when used
- __attribute__((always_inline))
- Forces a compiler to inline a function (avoiding function call overhead)
- An inline function is one for which the compiler copies the code from the
function definition directly into the code of the calling function rather than
creating a separate set of instructions in memory.
- variable/type attributes
-int matrix[4][4] __attribute__((aligned(16)));
- Specifies the memory alignment for variables or structures
- Ensures compatibility with SIMD instructions (minimizes padding between
struct members)
Memory Alignment Basics
- Memory alignments ensures data is stored at memory addresses that are MULTIPLES of
the data size
- Aligned vs unaligned data
- Aligned data
- Stored at addresses that are multiples of the data size
- Accessing aligned data required single memory access which is faster
- Unaligned data
- Stored at arbitrary addresses (not a multiple of the data size)
- Accessing unaligned data requires two memory access (slower))
- Struct Padding and Memory Optimizaton
- When defining structs, padding bytes may be added to maintain proper alignment
for faster memory access but this can increase memory footprint
Cache Memory
Cache memory is a small, fast memory located close to the CPU. It stores copies of frequently
accessed data to speed up subsequent access to that data. When the CPU accesses memory,
it first checks the cache. If the data is found there (a cache hit), it can be accessed much faster
than if it has to be fetched from the main memory (a cache miss).
● Spatial Locality: Refers to the use of data elements within relatively close storage
locations. If your program accesses memory locations that are close to each other in a
short period, it benefits from spatial locality.
● Temporal Locality: Refers to the reuse of specific data within relatively short time
periods. If your program accesses the same memory locations multiple times in a short
period, it benefits from temporal locality.
Cache Misses
A cache miss occurs when the CPU tries to read or write data that is not in the cache, causing
it to fetch the data from the slower main memory. Cache misses can significantly slow down a
program.
In our matrix multiplication example, accessing matrix b in a column-major order causes
frequent cache misses because of its poor spatial locality. Every access to b[k*n + j] may
require fetching a new cache line, leading to inefficient cache usage.
Optimization Techniques
One common optimization technique is blocking or tiling. This technique involves dividing the
matrices into smaller blocks that fit into the cache, improving both spatial and temporal locality.
Explanation of Optimization
● Blocking/Tiling: By breaking the matrices into smaller blocks, the data within each block
can fit into the cache. This reduces the number of cache misses because the data is
accessed in a more cache-friendly manner.
- System calls are the interface between user applications and the operating system in
Unix-like system → allow programs to request services from the kernel, such as file
operations, process control, and communication
- Common system calls in C:
- Open: opens a file and returns file descriptor
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
-
- int main() {
- int fd = open("example.txt", O_RDONLY);
- if (fd == -1) {
- perror("open");
- return 1;
- }
- // Use the file descriptor...
- close(fd);
- return 0;
- }
- Read:
- ssize_t read(int fd, void *buf, size_t count);
-
- int main() { int fd = open("example.txt", O_RDONLY); if (fd == -1) { perror("open");
return 1; } char buffer[128]; ssize_t bytesRead = read(fd, buffer, sizeof(buffer) -
1); if (bytesRead == -1) { perror("read"); close(fd); return 1; } buffer[bytesRead] =
'\0'; printf("Read: %s\n", buffer); close(fd); return 0; }
- ssize_t write(int fd, const void *buf, size_t count);
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <string.h>
- int main() {
- int fd = open("example.txt", O_WRONLY | O_CREAT, 0644);
- if (fd == -1) {
- perror("open");
- return 1;
- }
- const char *message = "Hello, world!\n";
- ssize_t bytesWritten = write(fd, message, strlen(message));
- if (bytesWritten == -1) {
- perror("write");
- close(fd);
- return 1;
- }
- close(fd);
- return 0;
- }
Access Modes:
● STDOUT_FILENO
● O_RDONLY: Open the file for read-only access.
● O_WRONLY: Open the file for write-only access.
● O_RDWR: Open the file for both reading and writing
- O_CREAT: Create the file if it does not exist. This flag requires a third argument
specifying the file permissions.
- O_EXCL: Ensure that this call creates the file. If this flag is specified and the file already
exists, the open call will fail.
- O_TRUNC: If the file exists and is opened for write access, its length will be truncated to
0.
- O_APPEND: Open the file in append mode. All writes will be added to the end of the file.
- O_NONBLOCK: Open the file in non-blocking mode. This is useful for special files like
FIFOs and device files.
- O_SYNC: Open the file for synchronous I/O. Writes to the file will block until the data has
been physically written.
Parameters