C Programming Fundamentals — Unix/Linux Cheatsheet
1. Basic Program
#include <stdio.h>
int main(void)
{
printf("Hello, world.\n");
return 0;
}
Compile:
clang -Wall -Wextra -Wpedantic -std=c17 hello.c -o hello
Run:
./hello
Check exit code:
echo $?
2. Compilation Model
Think:
source code
↓
preprocessor
↓
compiler
↓
assembly
↓
assembler
↓
object file
↓
linker
↓
executable
Typical one-step compile:
clang main.c -o main
Preprocess only:
clang -E main.c
Compile to assembly:
clang -S main.c
Compile to object file:
clang -c main.c -o main.o
Link object file:
clang main.o -o main
Useful learning flags:
-Wall
-Wextra
-Wpedantic
-Werror
-g
-O0
-std=c17
Example:
clang \
-Wall \
-Wextra \
-Wpedantic \
-g \
-O0 \
-std=c17 \
main.c \
-o main
-g includes debugging information.
-O0 disables optimization, making debugging easier.
3. Header Files
Include standard library declarations:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
Local header:
#include "myheader.h"
Headers generally contain:
- function declarations
- type definitions
- constants
- macros
Example declaration:
int printf(const char *format, ...);
4. main()
No command-line arguments:
int main(void)
{
return 0;
}
With command-line arguments:
int main(int argc, char *argv[])
{
return 0;
}
Example:
./program one two
Produces conceptually:
argc = 3
argv[0] = "./program"
argv[1] = "one"
argv[2] = "two"
5. Exit Status
Success:
return 0;
Failure:
return 1;
Or:
#include <stdlib.h>
return EXIT_SUCCESS;
return EXIT_FAILURE;
Shell:
./program
echo $?
Bash directly uses exit status:
if ./program; then
echo success
else
echo failure
fi
6. Fundamental Types
Common integer types:
char
short
int
long
long long
Unsigned:
unsigned int
unsigned long
Floating point:
float
double
Examples:
int count = 10;
long total = 100000L;
double load = 3.14;
char grade = 'A';
Find size:
printf("%zu\n", sizeof(int));
Important:
sizeof(char) == 1
but one C byte is not guaranteed by the language to mean exactly 8 bits.
On modern Linux/macOS systems it normally does.
7. Fixed-Width Integers
For system-level programming, useful types come from:
#include <stdint.h>
Examples:
int8_t
uint8_t
int16_t
uint16_t
int32_t
uint32_t
int64_t
uint64_t
Example:
uint32_t flags;
Useful when exact integer widths matter.
8. Variables and Initialization
Good:
int count = 0;
Dangerous:
int count;
printf("%d\n", count);
Local variables are not automatically initialized.
Initialize variables deliberately.
9. Pointers
Variable:
int value = 42;
Address:
&value
Pointer:
int *ptr = &value;
Dereference:
printf("%d\n", *ptr);
Conceptually:
value
+----------+
| 42 |
+----------+
0x1000
ptr
+----------+
| 0x1000 |
+----------+
Print address:
printf("%p\n", (void *)&value);
10. Arrays
int values[3] = {10, 20, 30};
Access:
values[0]
values[1]
values[2]
Arrays and pointers are closely related, but they are not literally identical.
11. Strings
C strings are arrays of characters terminated with:
'\0'
Example:
char name[] = "Jeremy";
Memory conceptually:
J e r e m y \0
Common string functions:
strlen()
strcmp()
strcpy()
strncpy()
memcpy()
Header:
#include <string.h>
Be careful with buffer sizes.
12. Structs
struct process {
int pid;
char name[64];
};
Use:
struct process p;
p.pid = 1234;
Pointer:
struct process *ptr = &p;
ptr->pid = 1234;
Equivalent:
(*ptr).pid
13. Functions
Definition:
int add(int a, int b)
{
return a + b;
}
Use:
int result = add(2, 3);
Prototype:
int add(int a, int b);
14. Standard I/O vs Unix I/O
C standard library:
printf()
fprintf()
fopen()
fread()
fwrite()
fclose()
Unix/POSIX-style interface:
open()
read()
write()
close()
Example:
int fd = open(...);
read(fd, ...);
write(fd, ...);
close(fd);
This distinction is important.
Think:
printf()
↓
libc buffering
↓
write()
↓
kernel
15. File Descriptors
Unix processes commonly begin with:
0 = stdin
1 = stdout
2 = stderr
Example:
write(1, "hello\n", 6);
writes directly to standard output.
Shell equivalents:
command >file
command 2>errors
command 2>&1
These are manipulating file descriptors.
16. write()
#include <unistd.h>
int main(void)
{
write(STDOUT_FILENO, "Hello\n", 6);
return 0;
}
Compile:
clang write.c -o write_demo
This avoids printf() and gets closer to the operating system interface.
17. Error Handling
Many Unix functions return:
-1
on failure and set:
errno
Example:
#include <errno.h>
#include <stdio.h>
if (something_failed) {
perror("operation");
}
Typical output:
operation: Permission denied
Never rely on errno unless a function has indicated failure.
18. Dynamic Memory
Allocate:
#include <stdlib.h>
int *values = malloc(100 * sizeof(*values));
Check:
if (values == NULL) {
return 1;
}
Release:
free(values);
Typical problems:
memory leak
use-after-free
double free
buffer overflow
out-of-bounds access
NULL dereference
19. Stack vs Heap
Very simplified model:
Process Address Space
high addresses
+--------------------+
| stack |
| ↓ |
+--------------------+
| |
| free |
| |
+--------------------+
| ↑ |
| heap |
+--------------------+
| global/static data |
+--------------------+
| machine code |
+--------------------+
low addresses
Stack:
- automatic local variables
- function calls
- usually automatically managed
Heap:
- dynamically allocated memory
malloccallocreallocfree
20. Processes
Useful functions:
getpid()
fork()
exec()
wait()
exit()
Header:
#include <unistd.h>
Conceptually:
shell
|
fork()
|
child process
|
exec()
|
your program
This is foundational Unix behavior.
21. fork()
pid_t pid = fork();
Return values:
-1 failure
0 child
>0 parent receives child's PID
Example:
if (pid == 0) {
printf("child\n");
} else {
printf("parent\n");
}
22. exec()
exec() replaces the current process image with another program.
Think:
process PID 123
running program A
exec()
process PID 123
running program B
The process does not simply create another child.
Its program is replaced.
23. Signals
Common signals:
SIGTERM
SIGINT
SIGKILL
SIGHUP
SIGSEGV
Shell:
kill PID
kill -TERM PID
kill -KILL PID
Important:
SIGTERM can be handled.
SIGKILL cannot.
24. Pipes
Shell:
ps aux | grep java
Conceptually:
ps stdout
|
| pipe
v
grep stdin
Unix C APIs:
pipe()
dup2()
fork()
exec()
Eventually you should understand how a shell constructs pipelines.
25. Sockets
Typical server lifecycle:
socket()
bind()
listen()
accept()
read/write
close()
Typical client:
socket()
connect()
read/write
close()
Sockets are also file descriptors.
That is a major Unix concept:
Files, terminals, pipes and sockets can often be manipulated through the same file-descriptor abstraction.
26. Important macOS/Linux Difference
macOS native executable format:
Mach-O
Linux native executable format:
ELF
macOS tool:
otool -L executable
Linux equivalent:
ldd executable
macOS and Linux are both Unix-like, but Linux-specific facilities include things like:
/proc
/sys
cgroups
Linux namespaces
epoll
many Linux-specific syscalls
Use a Linux VM/container when studying these.
27. Essential Inspection Tools
file
Identify a file:
file ./program
nm
Inspect symbols:
nm ./program
Find main:
nm ./program | grep main
otool
macOS dynamic dependencies:
otool -L ./program
Linux equivalent:
ldd ./program
objdump
Inspect compiled code:
objdump -d ./program
On macOS:
otool -tvV ./program
strings
Look for printable strings:
strings ./program
28. Debugging with lldb
macOS default debugger:
lldb ./program
Inside LLDB:
breakpoint set --name main
run
next
step
continue
frame variable
bt
quit
Short forms often work:
b main
r
n
s
c
bt
Compile with:
clang -g -O0 program.c -o program
On Linux you will commonly use:
gdb
29. System Call Tracing
On Linux:
strace ./program
Very useful examples:
strace -f ./program
strace -e openat ./program
strace -e read,write ./program
strace -o trace.txt ./program
macOS does not have normal Linux strace.
For serious syscall-learning exercises, use a Linux environment.
30. Process Inspection
Useful Unix/Linux commands:
ps
top
pgrep
pkill
lsof
Linux-specific tools worth learning:
pidstat
vmstat
iostat
sar
ss
Useful process questions:
What PID is it?
How much CPU?
How much memory?
What files are open?
What network sockets exist?
What child processes exist?
What signals has it received?
What syscalls is it making?
31. /proc on Linux
Examples:
/proc/PID/status
/proc/PID/maps
/proc/PID/fd/
/proc/PID/cmdline
/proc/PID/environ
Examples:
cat /proc/1234/status
Open file descriptors:
ls -l /proc/1234/fd
Memory mappings:
cat /proc/1234/maps
This will become extremely useful for your Linux-oriented C study.
32. Static and Dynamic Linking
Very simplified:
dynamic linking
program → shared library at runtime
versus:
static linking
library code → included in executable
Linux shared libraries commonly look like:
libsomething.so
macOS:
libsomething.dylib
33. Useful make
Simple Makefile:
CC = clang
CFLAGS = -Wall -Wextra -Wpedantic -g -O0
hello: hello.c
$(CC) $(CFLAGS) hello.c -o hello
clean:
rm -f hello
Run:
make
Clean:
make clean
Important:
Commands in traditional Makefiles begin with a tab.
34. Useful Shell Commands While Learning C
Compile:
clang ...
See status:
echo $?
Inspect:
file
nm
strings
otool
objdump
Processes:
ps
top
pgrep
lsof
Files:
ls
find
stat
Libraries:
otool -L
Linux:
ldd
strace
/proc
35. Compiler Warning Philosophy
During learning, assume:
Every compiler warning deserves investigation.
Recommended:
clang \
-Wall \
-Wextra \
-Wpedantic \
-g \
-O0 \
-std=c17 \
program.c \
-o program
Occasionally:
-Werror
to force warning-free builds.
36. Core Unix Mental Model
Keep this model in mind:
+----------------+
| shell |
+--------+-------+
|
fork/exec
|
v
+----------------+
| process |
+----------------+
| | |
| | |
fd memory signals
|
+-----+-----+
| | |
files pipes sockets
|
syscalls
|
v
+--------+
| kernel |
+--------+
|
hardware
C is valuable because it lets you interact with these abstractions with relatively little machinery hiding them.
37. C → Linux Concepts to Master
Your target progression:
C syntax
↓
memory/pointers
↓
files
↓
file descriptors
↓
syscalls
↓
processes
↓
signals
↓
pipes
↓
sockets
↓
threads
↓
memory mapping
↓
ELF/linking
↓
Linux process internals
38. Things Worth Memorizing
Know these without needing documentation:
int main(void)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
printf(...)
malloc(...)
free(...)
&variable
*pointer
return 0;
And these concepts:
stdin = fd 0
stdout = fd 1
stderr = fd 2
39. Things NOT Worth Memorizing
Do not waste time memorizing:
- every libc function
- every compiler option
- every syscall argument
- every LLDB/GDB command
- every Make feature
- obscure C syntax
Instead know:
What kind of tool/function should answer this question?
Then use:
man
Example:
man printf
On Linux:
man 2 write
man 2 fork
man 3 printf
Traditionally:
section 2 = system calls
section 3 = library functions
That distinction is especially useful for your learning.
40. Your Main Question While Studying C
Whenever you encounter something new, ask:
What does this look like from the operating system's point of view?
Examples:
printf()
→ eventually writes bytes to a file descriptor
malloc()
→ manages process memory
return from main()
→ becomes process exit status
fopen()
→ eventually opens a file
fork()
→ creates another process
socket()
→ creates a file descriptor connected to networking
exec()
→ replaces the running program inside a process
That question is the reason you're learning C.