COMP 206 Review Notes for Midterm 2 & Cheat Sheet
For a first post to my website, I thought it might be nice to post my study sheet for my second midterm in COMP 206! Enjoy :D
Why C?
“C is the latin of programming languages.” Lots of modern languages today were based and developed in C. This is partly due to its speed and memory efficiency. It forces you to understand how memory on a computer is organized/stored, as well as how much storage is required for different types of data. For this reason, C also gives stronger insight into how computers work.
C Syntax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 1. Essential Includes
#include <stdio.h> // printf, scanf, file I/O
#include <stdlib.h> // malloc, free, exit, atoi
#include <string.h> // strcpy, strlen, strcmp
#include <stdbool.h> // bool, true (1), false (0) (C doesn't have these built-in!)
// 2. Constants and Typedef
const float PI = 3.14159;
typedef int scalefactor:
scalefactor a;
a = 10;
// 3. The Main Function & Command Line Args
// argc: Argument count (always at least 1, because argv[0] is the program name)
// argv: Array of argument strings (e.g., argv[1] is the first user input)
int main(int argc, char *argv[]) {
printf("%s\n", argv[1]);
// 4. Format Specifiers
int age = 21;
char initial = 'H';
char name[] = "Hugo"; // strings are arrays of characters in C!
double gpa = 4.0;
// %d or %i : integers
// %f : floats/doubles (use %.2f to restrict to 2 decimal places)
// %c : single character
// %s : string (char array or char pointer)
// %p : memory address (pointer)
// %zu : size_t (the type returned by the sizeof operator)
printf("%s %c is %d years old.\n", name, initial, age);
// 5. Escape Characters
// \n : new line
// \t : tab
// \\ : backslash
// \b : backspace (non-destructive)
// Return 0 indicates the program executed successfully to the OS
return 0;
}
// 6. Standard I/O
// printf: Prints formatted output to stdout
printf("Hello %s\n", "World");
// puts: Prints a string to stdout, automatically appends a newline
puts("Hello World");
// fgets: The safest way to read strings (prevents buffer overflow)
// format: fgets(buffer, max_size, input_stream)
char buffer[50];
fgets(buffer, 50, stdin);
// scanf: Reads formatted input from stdin (requires memory addresses!)
int age;
scanf("%d", &age); // Notice the '&' - scanf needs to know WHERE to put the data
// sizeof: returns the number of bytes taken up by a variable
sizeof(age);
Data Types in C
In C, it’s important to know how many bytes different data structures use (e.g reading elements in array). You should know the following:
| Data Type | Size (Bytes) | Format Specifier | Description |
|---|---|---|---|
char | 1 | %c | Single character (ASCII) |
short | 2 | %hi | Short integer |
int | 4 | %d or %i | Standard integer |
long | 8 | %ld | Long integer |
float | 4 | %f | Single-precision floating point |
double | 8 | %lf | Double-precision floating point |
Any Pointer (*) | 8 | %p | Memory address (e.g., int *, char *) |
void | 0 | none | No type |
Note: Pointer sizes are always 8 bytes on a 64-bit system, regardless of what type of data they point to, because memory addresses themselves are 64 bits long.
The following can be added in front of char and int to save memory:
unsignedcan only represent positive numberssignedcan represent either positive or negative
Arrays and Pointers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. Array Declaration and Initialization
int numbers[5] = {10, 20, 30, 40, 50}; // Fixed size, allocated on the stack
int size = sizeof(numbers) / sizeof(numbers[0]); // How to safely get array length
// 2. The Golden Rule: Array names decay into pointers
// 'numbers' is effectively an int* pointing to the address of numbers[0]
int *ptr = numbers;
// 3. Accessing Elements
// These two lines are completely identical in C:
printf("First element: %d\n", numbers[0]);
printf("First element: %d\n", *ptr); // Dereferencing the base address
// 4. Pointer Arithmetic (The Magic)
// Adding 1 to a pointer moves it forward by the size of its data type!
// Since this is an int* (4 bytes), ptr + 1 moves exactly 4 bytes forward.
printf("Second element: %d\n", numbers[1]);
printf("Second element: %d\n", *(ptr + 1));
// 5. Array Decay in Functions
// Arrays LOSE their size information when passed to functions.
// They become standard pointers, so sizeof() won't work inside the function.
// You MUST pass the length as a separate argument.
void print_array(int *arr, int length) {
for (int i = 0; i < length; i++) {
// You can still use bracket notation on pointers!
printf("%d ", arr[i]);
}
printf("\n");
}
Handling Strings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 1. String Declaration
// Strings are just char arrays ending with a Null Terminator ('\0')
char str1[] = "Hello"; // Compiler automatically adds '\0' (Size is 6 bytes!)
char str2[20] = "World"; // Fixed size of 20, initialized with "World\0"
char *str3 = "Immutable"; // Read-only string literal! Don't try to change this.
// 2. Essential <string.h> Functions
// strlen: Gets the length of the string EXCLUDING the null terminator
// e.g., strlen("Hello") returns 5.
int len = strlen(str1);
// strcpy: Copies str2 into str1 (DESTINATION first, SOURCE second)
// DANGER: Make sure destination is big enough, or you'll get a buffer overflow!
char dest[50];
strcpy(dest, str1);
// strcat: Concatenates (appends) str2 onto the end of str1
// str1 must have enough extra space to hold str2
strcat(dest, " ");
strcat(dest, str2); // dest is now "Hello World"
// strcmp: Compares two strings alphabetically
// Returns 0 if they are exactly identical
if (strcmp(str1, str2) == 0) {
puts("Strings match!");
}
// 3. String Input Trap
// When using scanf for strings, DO NOT use the '&' operator!
// The array name is already a memory address.
char name[50];
scanf("%49s", name); // "49" prevents buffer overflow (leaving 1 byte for '\0')
Struct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. Defining a Struct with Typedef (Best Practice)
// Typedef lets you use 'Student' instead of typing 'struct Student' every time.
struct Student {
char name[50];
int age;
double gpa;
};
int main() {
// 2. Initialization & The Dot Operator (.)
// Use the dot operator to access members of a standard struct variable.
struct Student s1 = {"Hugo", 21, 4.0};
printf("%s is %d years old.\n", s1.name, s1.age);
// Modifying after initialization:
s1.gpa = 3.9;
strcpy(s1.name, "Hugo P."); // Remember: You can't use '=' for C arrays/strings!
// 3. Pointers to Structs & The Arrow Operator (->) (Midterm Essential!)
// When you have a POINTER to a struct, you MUST use '->' instead of '.'
struct Student *ptr = &s1;
// These two lines do the exact same thing:
printf("GPA: %f\n", (*ptr).gpa); // Dereference first, then dot (clunky)
printf("GPA: %f\n", ptr->gpa); // Arrow operator (clean, standard)
return 0;
}
Dynamic Memory Allocation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 1. Essential Include
#include <stdlib.h> // Required for malloc, calloc, realloc, and free
// 2. malloc: Memory Allocation (Uninitialized)
// Allocates a block of memory on the HEAP.
// You MUST use sizeof() to calculate the exact bytes needed!
int *arr = malloc(5 * sizeof(int));
// ALWAYS check if malloc failed (returns NULL if the system is out of memory)
if (arr == NULL) {
puts("Memory allocation failed!");
exit(1);
}
// 3. calloc: Contiguous Allocation (Initialized to Zero)
// Takes two arguments: number of elements, and size of each element.
// Slightly slower than malloc, but safer because it zeroes out the memory.
int *clean_arr = calloc(5, sizeof(int));
// 4. realloc: Re-Allocation
// Resizes an existing heap allocation. It preserves your existing data.
// If it has to move the data to find a bigger contiguous block, it handles the copy.
arr = realloc(arr, 10 * sizeof(int)); // Doubling the array size from 5 to 10
// 5. free: Preventing Memory Leaks
// For every single malloc/calloc, there MUST be exactly one free().
// When a function ends, stack variables die, but HEAP memory lives forever until freed.
free(arr);
free(clean_arr);
// 6. Defeating Dangling Pointers
// After freeing, the pointer itself still holds the old, now-invalid memory address.
// Using it causes a segfault. Set it to NULL immediately to be safe.
arr = NULL;
clean_arr = NULL;
Opening Files for Reading, Writing, and Appending
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// fopen: Opens a file ("r" = read, "w" = write, "a" = append)
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
puts("Error opening file!");
exit(1);
}
// fprintf: Just like printf, but writes to a specific file
fprintf(file, "Saving age: %d\n", age);
fprintf(stderr, "error: incorrect number of arguments");
// fclose: ALWAYS close files to prevent memory leaks!
fclose(file);
Compilation and Makefiles
The source code must be compiled before it can be run. In this class, we’ve used the gcc compiler:
1
2
# compiles the file hello_world.c and names it hello
gcc hello_world.c -o hello
The compiler checks for errors and translates the code into machine code. This is one of C’s key strengths. Once compiled, you have a highly efficient standalone executable. In contrast, Bash interprets commands one by one, and Python/Java compile to bytecode (an intermediate language read by an interpreter).
Common File Extensions:
- filename.c : Source Code (the actual program logic).
- filename.h : Header File (function prototypes and struct definitions shared across multiple .c files).
- filename.o : Object File (compiled machine code, but not yet linked into an executable).
- filename (no extension) : Executable File (the final, linked program).
Basic Makefile Template: Makefiles automate the compilation process so you don’t have to type long gcc commands every time.
Rule syntax: Target: Dependencies [TAB] Command
# Variables
CC = gcc
CFLAGS = -Wall -g # -Wall shows all warnings, -g adds debugging info
# The final executable depends on the object files
my_program: main.o utils.o
gcc -Wall -g main.o utils.o -o my_program
# How to build the object files
main.o: main.c utils.h
gcc -Wall -g -c main.c
utils.o: utils.c utils.h
gcc -Wall -g -c utils.c
# 'make clean' removes all compiled files to start fresh
clean:
rm -f *.o my_program