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
  • malloc
  • calloc
  • realloc
  • free

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.