-
C — Debugging with Valgrind
Valgrind is a dynamic analysis tool that runs your program in a controlled environment and reports every memory error — leaks, invalid accesses, and use of uninitialised values — with the exact line where they happened.
Step 1 — Compile with Debug Symbols
Like GDB, Valgrind needs the
-gflag to show source lines instead of raw addresses:gcc -Wall -Wextra -Werror -g main.c -o my_programStep 2 — Run Under Valgrind
valgrind ./my_programThis runs Memcheck (Valgrind’s default tool) and prints a summary at the end. For thorough leak detection use the full set of flags:
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./my_programFlag Effect --leak-check=fullreport every individual leaked block, not just totals --show-leak-kinds=allinclude definitely, indirectly, possibly lost, and still reachable --track-origins=yesshow where an uninitialised value was created -sprint a short error summary at the end Reading the Output
A clean run ends with:
LEAK SUMMARY: definitely lost: 0 bytes in 0 blocks indirectly lost: 0 bytes in 0 blocks possibly lost: 0 bytes in 0 blocks still reachable: 0 bytes in 0 blocks suppressed: 0 bytes in 0 blocks ERROR SUMMARY: 0 errors from 0 contextsAny non-zero number is a problem to fix.
Types of Errors
Memory Leak —
definitely lostMemory was allocated but never freed, and no pointer to it remains:
int *arr = malloc(10 * sizeof(int)); // forgot to free(arr)LEAK SUMMARY: definitely lost: 40 bytes in 1 blocksFix: add
free(arr)before the program exits.Invalid Read / Write
Accessing memory outside an allocated block — classic buffer overflow or off-by-one:
int *arr = malloc(3 * sizeof(int)); arr[3] = 99; // index 3 is out of bounds (valid: 0, 1, 2)Invalid write of size 4 at 0x... main (main.c:6) Address 0x... is 0 bytes after a block of size 12 alloc'dFix: check array sizes and loop bounds.
Use of Uninitialised Value
Reading a variable that was declared but never assigned:
int x; if (x > 0) // x has no defined value printf("positive\n");Conditional jump or move depends on uninitialised value(s) at 0x... main (main.c:5)With
--track-origins=yesValgrind also reports wherexwas allocated, making it much easier to trace the root cause.Invalid Free
Freeing a pointer that was already freed, or one that was never malloc’d:
free(ptr); free(ptr); // double freeInvalid free() / delete / delete[] / realloc() at 0x... free (in valgrind) by 0x... main (main.c:8) Address 0x... is 0 bytes inside a block of size 4 free'dFix: set
ptr = NULLimmediately afterfree(ptr).Still Reachable
Memory that was never freed but a valid pointer to it still exists at exit. Technically not a leak (the pointer is accessible), but still sloppy:
char *buf = malloc(64); // buf is never freed, but main() returns with buf still in scopeThese show up under
still reachablein the summary. Free everything before returning frommain.
A Typical Workflow
# Compile gcc -Wall -Wextra -Werror -g main.c utils.c -o my_program # Run with full checks valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./my_program # If your program takes arguments valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./my_program arg1 arg2Read the output top to bottom:
- Each error block shows the type, the line, and the call stack
- The LEAK SUMMARY groups leaks by category
- The ERROR SUMMARY gives the total count
Fix errors from the innermost call stack frame up — that is usually the root cause.
Quick Reference
Command / Flag Effect valgrind ./progrun with default Memcheck settings --leak-check=fullreport each leaked block individually --show-leak-kinds=allinclude all leak categories --track-origins=yesshow where uninitialised values come from -sprint error summary at the end definitely lostmalloc’d memory with no surviving pointer — must fix still reachablepointer exists but memory never freed — fix before exit Invalid read/writeout-of-bounds access Uninitialised valuevariable used before being assigned Invalid freedouble free or freeing a non-heap pointer Created on July 2026 -
C — Debugging with GDB
GDB (GNU Debugger) lets you run a C program step by step, inspect memory and variables at any point, and find exactly where and why something goes wrong.
Step 1 — Compile with Debug Symbols
By default GCC strips debug information from the binary. The
-gflag includes it:gcc -Wall -Wextra -Werror -g main.c -o my_programWithout
-g, GDB can still run the program but cannot show source lines, variable names, or meaningful stack frames — just raw addresses.Step 2 — Launch GDB
gdb ./my_programThis opens the GDB prompt
(gdb). The program is loaded but not yet running.If your program takes command-line arguments, use
--argsso GDB does not consume them:gdb --args ./my_program arg1 arg2Everything after the binary name is forwarded to your program when you run
r. Without--args, passing arguments directly togdbwould be interpreted as GDB options.Step 3 — Use GDB TUI for a Visual Layout
gdbtuilaunches GDB with a split-screen terminal UI — source code on top, command prompt below. It is far easier to follow than the plain prompt:gdbtui ./my_program gdbtui --args ./my_program arg1 arg2 # with argumentsThe top pane shows the source file with an arrow pointing at the current line. The bottom pane is the normal GDB command prompt.
Core Commands
r— RunStarts (or restarts) the program. Pass arguments after
rif your program expects them:(gdb) r (gdb) r arg1 arg2The program runs until it hits a breakpoint, crashes, or finishes.
b— BreakpointPauses execution before a specific line or function:
(gdb) b 42 # break at line 42 of the current file (gdb) b main.c:15 # break at line 15 of main.c (gdb) b my_function # break at the entry of my_functionList all breakpoints:
(gdb) info bDelete a breakpoint by its number:
(gdb) delete 1n— Next (step over)Executes the current line and moves to the next one. If the current line is a function call, the entire function runs without stepping into it:
(gdb) ns— Step (step into)Like
n, but if the current line calls a function, GDB steps inside that function:(gdb) sUse
swhen you want to follow execution into a function you wrote. Usenwhen you want to skip over library calls or functions you trust.c— ContinueResumes execution at full speed until the next breakpoint (or the end of the program):
(gdb) cprint— Inspect a ValueEvaluates and prints any expression — variable, pointer, arithmetic:
(gdb) print x (gdb) print *ptr (gdb) print arr[2] (gdb) print a + bYou can also modify a variable on the fly:
(gdb) print x = 10disp— Display (auto-print on every step)displayworks likeprintbut re-evaluates and shows the value automatically after everyn,s, orcthat pauses execution:(gdb) disp x (gdb) disp *ptr (gdb) disp arr[i]List active displays:
(gdb) info displayRemove a display by its number:
(gdb) undisplay 1
Useful Extra Commands
bt— BacktraceShows the full call stack at the current point — essential when diagnosing a crash:
(gdb) bt#0 ft_strlen (s=0x0) at ft_strlen.c:6 #1 ft_putstr (str=0x0) at ft_putstr.c:12 #2 main () at main.c:20Read from bottom to top:
maincalledft_putstrwhich calledft_strlenwhere the crash occurred.p/x— Print in HexadecimalUseful for inspecting raw memory values, addresses, and bitfields:
(gdb) p/x flags (gdb) p/x *ptrwatch— WatchpointPauses execution whenever a variable’s value changes — useful for tracking down unexpected mutations:
(gdb) watch xrefresh— Redraw the TUIThe TUI pane can glitch after terminal output or resizing.
refresh(or its shorthandref) redraws it cleanly:(gdb) refresh (gdb) refRun it any time the source window looks garbled.
q— Quit(gdb) q
A Typical Session
# Compile with debug symbols gcc -Wall -Wextra -Werror -g main.c utils.c -o my_program # Launch with TUI (with optional arguments) gdbtui --args ./my_program arg1 arg2(gdb) b main # break at entry (gdb) r # run until breakpoint (gdb) disp i # watch variable i on every step (gdb) disp *ptr # watch what ptr points to (gdb) n # step over next line (gdb) s # step into the function call (gdb) print result # inspect a value (gdb) c # continue to next breakpoint (gdb) bt # check the call stack after a crash (gdb) q # quit
Quick Reference
Command Effect gcc -ginclude debug symbols in the binary gdbtui ./proglaunch GDB with split source/command UI gdb --args ./prog a bpass arguments to the program, not to GDB rrun (or restart) the program b <line>set a breakpoint at a line or function nnext line — step over function calls sstep — enter function calls ccontinue until next breakpoint print <expr>evaluate and print an expression once disp <expr>auto-print an expression after every step btshow the full call stack p/x <expr>print value in hexadecimal watch <var>pause when a variable changes refresh/refredraw the TUI when it glitches qquit GDB Created on July 2026 -
C — Makefile
A
Makefileautomates compilation. Instead of retypinggcccommands every time, you runmakeand the build system figures out what needs to be rebuilt.
A Minimal Makefile
NAME = my_program CC = gcc CFLAGS = -Wall -Wextra -Werror SRCS = main.c utils.c OBJS = $(SRCS:.c=.o) all: $(NAME) $(NAME): $(OBJS) $(CC) $(CFLAGS) $(OBJS) -o $(NAME) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(OBJS) fclean: clean rm -f $(NAME) re: fclean all .PHONY: all clean fclean reHow It Works
Variables
NAME = my_program # output binary name CC = gcc # compiler CFLAGS = -Wall -Wextra -Werror # compiler flags SRCS = main.c utils.c # source files OBJS = $(SRCS:.c=.o) # replace .c with .o automatically$(VAR)expands a variable.$(SRCS:.c=.o)is a substitution reference — it producesmain.o utils.ofrommain.c utils.c.Rules
A rule has the form:
target: prerequisites recipe- target — the file to produce (or a phony label like
clean) - prerequisites — files the target depends on; if any are newer, the recipe runs
- recipe — shell commands to run (must be indented with a real tab, not spaces)
Pattern Rule
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@%is a wildcard.$<is the first prerequisite (the.cfile),$@is the target (the.ofile). This single rule compiles every.cinto its matching.o..PHONY
.PHONY: all clean fclean reTells
makethese targets are not real files. Without this, if a file namedcleanexisted,make cleanwould do nothing.Mandatory Rules
Rule Effect make/make allcompile the program make cleanremove .oobject filesmake fcleanremove .ofiles and the binarymake refull rebuild from scratch ( fcleanthenall)Every C project should have all four.
Incremental Compilation
makeonly recompiles files that changed. If you editutils.c, onlyutils.ois rebuilt —main.ois untouched. This saves time on large projects.Common Mistakes
Spaces instead of tabs — the recipe line must start with a tab character. Most editors show them the same but
makewill refuse:Makefile:10: *** missing separator. Stop.Forgetting
fcleanremoves the binary —cleanonly removes.ofiles.fcleanalso removes$(NAME). Both are required.Not listing all source files — if
SRCSis incomplete, some files won’t compile and you’ll get linker errors about undefined symbols.
Quick Reference
Syntax Meaning $(VAR)expand variable $(SRCS:.c=.o)substitution: replace .cwith.o$@the target of the current rule $<the first prerequisite %.o: %.cpattern rule matching any .c→.o.PHONYdeclare targets that are not files -cflagcompile to .owithout linkingCreated on July 2026 - target — the file to produce (or a phony label like
-
Shell Tips — Commands & Bash Scripting
The terminal is one of the most powerful tools in a developer’s toolkit. Whether you’re on Linux, macOS, or WSL on Windows, mastering a handful of Shell commands and understanding how to write Bash scripts can dramatically speed up your workflow.
Essential Commands
ls — List Directory Contents
lsshows what’s in the current directory. The flags make it genuinely useful:ls # basic list ls -l # long format: permissions, owner, size, date ls -a # show hidden files (starting with .) ls -la # combine both: long format + hidden filesThe
-lacombo is the one you’ll use 95% of the time. It gives you the full picture of a directory including dotfiles like.zshrc,.gitignore, etc.cd — Change Directory
cd /path/to/dir # absolute path cd Documents # relative path cd .. # go up one level cd ~ # go to home directory cd - # go back to previous directorycd -is underrated — it toggles between the last two directories you visited, great when you’re jumping between two project folders.cp — Copy Files or Directories
cp file.txt backup.txt # copy a file cp -r folder/ backup_folder/ # copy a directory recursively cp *.png ~/Pictures/ # copy all .png files to PicturesAlways use
-r(recursive) when copying directories, otherwisecpwill refuse.mv — Move or Rename
mv old_name.txt new_name.txt # rename a file mv file.txt ~/Documents/ # move a file mv folder/ ../other_folder/ # move a directorymvdoes double duty: moving and renaming are the same operation. There’s no dedicated rename command in Shell.chmod — Change File Permissions
Permissions control who can read, write, or execute a file. They apply to three groups: owner, group, and others.
chmod +x script.sh # make a file executable chmod 755 script.sh # rwxr-xr-x (owner full, others read+execute) chmod 644 file.txt # rw-r--r-- (owner read+write, others read only)There is also a symbolic mode using
+,-, and=to add, remove, or set permissions, andu(user/owner),g(group),o(others),a(all) to target who:chmod +x script.sh # give execute to everyone chmod -x script.sh # remove execute from everyone chmod +r file.txt # give read to everyone chmod -w file.txt # remove write from everyone chmod u+x script.sh # give execute to owner only chmod g-w file.txt # remove write from group chmod o-r file.txt # remove read from others chmod a+r file.txt # give read to all (same as +r) chmod u+rwx,go+rx file # owner full, group and others read+execute chmod o= file.txt # remove all permissions from othersSymbol Meaning +add permission -remove permission =set exact permissions (removes others) uowner (user) ggroup oothers aall (u+g+o) The numeric mode (
755,644) is the most common in practice. Each digit is a sum of three base values:Value Permission 4 read (r) 2 write (w) 1 execute (x) Add them together to combine permissions:
Value Permissions 7 rwx (4+2+1) 6 rw- (4+2) 5 r-x (4+1) 4 r— (4) 0 --- alias — Create Command Shortcuts
aliaslets you define a shortcut for any command or chain of commands:alias ll="ls -la" alias gs="git status" alias ..="cd .." alias ...="cd ../.."The problem: aliases defined in the terminal only last for the current session.
Making Aliases Permanent with .zshrc
To persist aliases across sessions, add them to your shell config file. For Zsh (default on macOS and most modern Linux distros), that’s
~/.zshrc:# Open .zshrc with your editor nano ~/.zshrc # Add your aliases at the bottom alias ll="ls -la" alias gs="git status" alias gp="git push" alias ..="cd .." # Reload the config without restarting the terminal source ~/.zshrcFor Bash users the file is
~/.bashrcinstead.
Writing a Bash Script
A Bash script is a plain text file containing a sequence of Shell commands. The first line — the shebang — tells the OS which interpreter to use.
The Shebang Line
#!/bin/bashThis must be the very first line of the file. It points to the Bash binary, so the script runs with Bash regardless of what shell the user has active.
A Practical Example
Here’s a script that creates a timestamped backup of a directory:
#!/bin/bash SOURCE="$HOME/Projects" DEST="$HOME/Backups" TIMESTAMP=$(date +"%Y-%m-%d_%H-%M") BACKUP_NAME="backup_$TIMESTAMP" mkdir -p "$DEST/$BACKUP_NAME" cp -r "$SOURCE/" "$DEST/$BACKUP_NAME/" echo "Backup created: $DEST/$BACKUP_NAME"Save it as
backup.sh, then make it executable and run it:chmod +x backup.sh ./backup.shKey Script Concepts
Variables — no spaces around
=:NAME="Sam" echo "Hello, $NAME"Conditionals:
if [ -f "file.txt" ]; then echo "File exists" else echo "File not found" fiLoops:
for file in *.txt; do echo "Processing $file" doneFunctions:
greet() { echo "Hello, $1" } greet "Sam"
Quick Reference
Command Purpose lsList directory contents cdChange directory cpCopy files or directories mvMove or rename files chmodChange file permissions aliasCreate a command shortcut source ~/.zshrcReload shell config #!/bin/bashShebang — declare Bash interpreter Created on July 2026