CS50 Fall 2025 - Lecture 4 - Memory (live, unedited)
Watch on YouTube →
Overview
CS50's Lecture 4 on Memory introduces hexadecimal notation (base 16) for representing memory addresses and colors, explaining its convenience over binary for programmers. The lecture details pointers, demonstrating how to declare and dereference them using the '*' and '&' operators to manipulate data indirectly. It also covers file I/O (Fopen, Fclose, Fprintf, Fread, Fwrite) for persistent data storage and introduces memory allocation (Malloc, Free) and debugging tools like Valgrind to manage memory effectively and prevent common errors like buffer overflows and memory leaks.
Key takeaways
- Hexadecimal (base 16) is a convenient notation for representing memory addresses and colors, mapping directly to groups of 4 bits.
- Pointers store memory addresses, enabling indirect access and modification of data, crucial for functions that need to alter original variables (pass-by-pointer).
- File I/O (Fopen, Fprintf, Fread, Fwrite) allows programs to persistently store and retrieve data on disk, essential for applications beyond simple in-memory calculations.
- Memory allocation (Malloc) and deallocation (Free) are vital for managing dynamic memory, preventing leaks and ensuring efficient resource usage.
- Tools like Valgrind are indispensable for detecting memory errors (leaks, invalid reads/writes) that can cause crashes or security vulnerabilities.
- Understanding memory layout (stack vs. heap) and potential overflows is key to writing robust C programs.
Chapters
- Images are represented as grids of pixels, each with a color.
- Low-resolution images can be represented using binary (0s and 1s) for black and white.
- Modern images use multiple bits per pixel (e.g., 16, 24, or more) for a wide color spectrum.
- Colors can be represented using RGB (Red, Green, Blue) values.
- Photoshop's color picker uses hexadecimal notation (e.g., 000000 for black, FFFFFF for white).
- Hexadecimal (base 16) uses digits 0-9 and A-F.
- Hexadecimal uses 16 digits: 0-9 and A-F.
- Each hexadecimal digit represents 4 bits (a nibble).
- Two hexadecimal digits represent 8 bits (1 byte), ranging from 00 to FF (0 to 255 in decimal).
- Computer memory locations are addressed using hexadecimal notation.
- The '0x' prefix denotes a hexadecimal number (e.g., 0x10 is decimal 16).
- Memory addresses are typically represented in hexadecimal for brevity and convenience.
- A simple C program declares an integer variable 'n' and initializes it to 50.
- The program uses printf with '%i' to print the integer value.
- The variable 'n' occupies 4 bytes (32 bits) in memory.
- The '&' operator retrieves the memory address of a variable.
- The '%p' format specifier in printf is used to print memory addresses.
- Running the program shows the hexadecimal memory address where 'n' is stored.
- A pointer is a variable that stores a memory address.
- Pointers are declared using an asterisk '*' after the data type (e.g., 'int *p').
- The '*' operator is used for dereferencing (accessing the value at the address).
- Declaring a pointer requires '*' before the variable name (e.g., 'int *p').
- Assigning an address to a pointer requires the '&' operator (e.g., 'p = &n;').
- Attempting to assign an address to a non-pointer variable causes a type incompatibility error.
- Dereferencing a pointer (using '*') accesses the value stored at the memory address.
- Printing '*p' displays the value of the variable the pointer points to (e.g., 50).
- Pointers can store addresses of various data types (e.g., 'char *' for strings).
- Strings in C are null-terminated arrays of characters.
- A string variable (e.g., 'char s[] = "Hi!";') is essentially a pointer to its first character.
- The null terminator ('\0') marks the end of the string.
- Array indexing (e.g., 's[0]') and pointer arithmetic (e.g., '*s') can access string characters.
- Pointer arithmetic (e.g., 's + 1') moves the pointer to the next character's address.
- Printf with '%s' handles null-terminated strings automatically.
- Comparing string variables directly (e.g., 's == t') compares their memory addresses, not their content.
- The 'strcmp()' function from <string.h> compares string content character by character.
- strcmp() returns 0 if strings are equal, negative if the first is less, positive if the first is greater.
- String variables (char*) store the memory address of the first character.
- Integer variables store their actual value directly.
- String literals are stored elsewhere in memory, and the variable holds their address.
- Direct assignment (e.g., 't = s;') creates a shallow copy, making both pointers point to the same memory.
- Modifying the copied string (e.g., 't[0] = toupper(t[0]);') affects the original string.
- A deep copy requires allocating new memory and copying characters individually.
- Malloc (from <stdlib.h>) allocates a specified number of bytes from the heap.
- Free returns allocated memory back to the system.
- Failure to free allocated memory leads to memory leaks.
- Calculate required memory size using strlen(s) + 1 (for null terminator).
- Allocate memory using malloc(size).
- Copy characters from source to destination using a loop or strcpy().
- strcpy(destination, source) copies the source string to the destination.
- Requires destination buffer to be large enough to hold the source string.
- Potential for buffer overflows if destination is too small.
- NULL is a special memory address (0x0) indicating an invalid pointer.
- Functions like GetString, Malloc, and Fopen can return NULL on error.
- Always check return values for NULL to prevent crashes and handle errors gracefully.
- An empty string has a length of 0 and consists only of the null terminator.
- Attempting to modify characters of an empty string can lead to undefined behavior.
- Check string length before performing operations like toupper() on the first character.
- Machine code and global variables are loaded at the top of memory.
- The heap grows downwards, used for dynamic memory allocation (Malloc).
- The stack grows upwards, used for local variables and function calls.
- Stack overflow occurs from excessive function calls or deep recursion.
- Heap overflow occurs from allocating too much memory or memory leaks.
- Buffer overflows happen when writing past the allocated bounds of an array.
- GetString (CS50 library) handles dynamic memory allocation for user input.
- Scanf (standard C library) requires manual memory management and address passing.
- Scanf uses format specifiers like '%i' for integers and '%s' for strings.
- Scanf requires the address of the variable to store input.
- For integers, use '&n' to pass the address.
- For strings (char*), the variable itself is already an address, so '&s' is not used.
- Scanf with '%s' and a fixed-size buffer is vulnerable to buffer overflows.
- The program doesn't know the user's input length, leading to potential overwrites.
- CS50's GetString dynamically allocates memory to avoid this issue.
- File I/O allows programs to read from and write to persistent storage (disks).
- Fopen(filename, mode) opens a file; 'w' for write, 'r' for read, 'a' for append.
- Fclose(file_pointer) closes the file, ensuring data is saved.
- Fprintf(file_pointer, format, ...) writes formatted data to a file.
- Similar to printf, but directs output to a specified file.
- Used for creating structured data files like CSV (Comma Separated Values).
- Using 'a' mode in Fopen appends data to the end of an existing file.
- Using 'w' mode overwrites the file or creates a new one if it doesn't exist.
- This allows for persistent storage of multiple entries (e.g., phone book).
- File I/O functions (Fopen, Fprintf, etc.) operate on file pointers.
- Fopen returns a pointer to a FILE struct representing the open file.
- Pointers are essential for C to manage file access and data manipulation.
- Takes source and destination filenames as command-line arguments (argv[1], argv[2]).
- Opens source file in read mode ('r') and destination in write mode ('w').
- Uses Fread and Fwrite to copy file content byte by byte.
- Fread(buffer, size, count, file_pointer) reads data into a buffer.
- Fwrite(buffer, size, count, file_pointer) writes data from a buffer to a file.
- Used iteratively to copy files chunk by chunk, similar to buffering.
- Typedef 'byte' is defined as 'unsigned char' for clarity.
- Unsigned char ensures byte values are treated as raw data (0-255), not signed integers.
- Essential for binary file operations like image manipulation.
- The CP program demonstrates byte-by-byte file copying.
- This technique is fundamental for processing binary files like BMP images.
- Future problems involve applying filters (grayscale, sepia, reflection, blur) to images by manipulating pixel data.
- Passing integers by value to a function creates copies.
- Swapping the copies within the function does not affect the original variables.
- This illustrates the limitation of pass-by-value for modifying original data.
- Modify the swap function to accept pointers (addresses) to integers.
- Use the dereference operator '*' to access and modify the original variables' values.
- Pass addresses using the '&' operator (e.g., swap(&x, &y)).
- Scanf (standard C library) reads formatted input from stdin.
- Uses format specifiers like '%i' for integers.
- Requires the address of the variable ('&n') to store the input value.
- Strings in C are char pointers (addresses of characters).
- Scanf with '%s' reads a string, storing it at the provided address.
- Do not use '&' with string variables (char*) as they are already addresses.
- Declaring a pointer without initializing it (e.g., 'char *s;') results in a garbage address.
- Using Scanf or dereferencing an uninitialized pointer leads to undefined behavior and crashes.
- Valgrind can detect 'use of uninitialized value' errors.
- Using fixed-size buffers with functions like Scanf('%s') can cause buffer overflows.
- Input exceeding buffer capacity overwrites adjacent memory.
- CS50's GetString handles dynamic allocation to prevent overflows.
- Valgrind is a tool for detecting memory errors (leaks, invalid reads/writes).
- Run Valgrind on your executable (e.g., 'valgrind ./program').
- Analyze output for 'invalid write', 'invalid read', and 'definitely lost' memory blocks.
- Memory leaks occur when allocated memory (Malloc) is not freed.
- Dangling pointers point to memory that has been freed or is invalid.
- Valgrind helps identify these issues by tracking memory usage.
- Memory not explicitly initialized contains residual data ('garbage values').
- Accessing uninitialized memory leads to unpredictable program behavior.
- Always initialize variables and pointers before use.
- The stack stores local variables and function call information, growing upwards.
- The heap stores dynamically allocated memory (Malloc), growing downwards.
- Machine code and global variables reside at the top of memory.
- Stack grows upwards, heap grows downwards.
- Excessive function calls (stack overflow) or memory allocation (heap overflow) can cause them to collide.
- This leads to program crashes or unpredictable behavior.
- Stack overflow: Infinite recursion or too many nested function calls exhaust stack space.
- Heap overflow: Excessive Malloc calls without corresponding Free exhaust heap space.
- Stack Overflow website is named after the common stack overflow error.
- Buffer overflow: Writing data beyond the allocated size of an array or buffer.
- Can corrupt adjacent memory, leading to crashes or security vulnerabilities.
- Requires careful bounds checking and using safe string functions.
- File I/O functions require pointers to manage file access.
- Fopen returns a pointer to a FILE struct, representing the file stream.
- Pointers are essential for C to interact with files on disk.
- Takes source and destination filenames as command-line arguments (argv).
- Uses Fopen to open both files in appropriate modes ('r' for source, 'w' for destination).
- Employs Fread and Fwrite in a loop to copy data byte by byte.
- Reads data into a buffer (byte variable) using Fread.
- Writes the read data to the destination file using Fwrite.
- Continues until Fread returns 0, indicating end-of-file.
- Fopen can open files in text mode (default) or binary mode ('rb', 'wb', 'ab').
- Binary mode prevents interpretation/translation of bytes, crucial for non-text files (images, executables).
- Using binary mode ensures accurate data copying.
- A buffer is a temporary storage area (often an array) for data.
- Video players use buffers to store upcoming video segments for smooth playback.
- Copying files byte-by-byte mimics this buffering concept.
- Bitmap (BMP) files store images as grids of pixels.
- Each pixel has associated Red, Green, and Blue (RGB) values.
- File I/O allows reading and manipulating these pixel values to create image filters.
- Grayscale: Adjusting RGB values to shades of gray.
- Sepia: Applying a warm, brownish tone.
- Reflection: Mirroring the image horizontally or vertically.
- Blurring: Averaging pixel values to soften the image.
- The corrected swap function takes pointers to integers.
- It dereferences pointers ('*') to modify the original variables' values.
- This demonstrates effective modification of data outside the function's scope.
- Memory is segmented: code, globals, heap (dynamic), stack (local vars/functions).
- Stack grows up, heap grows down; potential for collision.
- Understanding these regions is key to avoiding memory errors.
- C offers low-level memory control for high performance.
- This power comes with responsibility; errors can crash programs or cause security issues.
- Modern languages (Python, Java) offer more safety but less direct control and potentially lower performance.
- When calling a function with arguments like 'swap(x, y)', copies of x and y are passed.
- The function operates on these copies (A and B), not the original variables.
- Changes inside the function do not affect variables outside its scope.
- Modify function signature to accept pointers: 'void swap(int *a, int *b)'.
- Use dereference operator '*' to access values at addresses: 'temp = *a;'.
- Pass addresses using '&': 'swap(&x, &y)'.
- Pointers (like Binky's wands) store memory addresses.
- Pointees are the actual data stored at those addresses.
- Dereferencing follows the pointer (wand) to access the pointee (data).
- Pointer assignment (e.g., 'y = x;') makes 'y' point to the same address as 'x'.
- Dereferencing (e.g., '*y = 13;') modifies the data at the address 'y' points to.
- Sharing a pointee means changes via one pointer affect the other.
- Dereferencing a pointer that hasn't been assigned a valid memory address (e.g., uninitialized or NULL) causes errors.
- This can lead to crashes or data corruption.
- Always ensure pointers point to valid memory locations before dereferencing.
- Malloc allocates memory dynamically from the heap.
- Free deallocates memory, returning it to the system.
- Crucial for preventing memory leaks in long-running programs.
- To create an independent copy of a string, allocate new memory using Malloc.
- Copy the source string's content (including null terminator) to the new memory using Strcpy.
- This ensures modifications to the copy do not affect the original.
- Direct assignment 't = s;' creates a shallow copy (t points to the same memory as s).
- Modifying t[0] (e.g., to uppercase) also changes s[0] because they share memory.
- Requires explicit memory allocation and copying for a true deep copy.
- Scanf('%s', buffer) reads input into a fixed-size buffer.
- If input exceeds buffer size, it causes a buffer overflow.
- This overwrites adjacent memory, leading to crashes or security vulnerabilities.
- CS50's GetString dynamically allocates memory, preventing buffer overflows.
- Manual input handling with Scanf requires careful buffer size management.
- Using libraries like CS50's simplifies safe input acquisition.
- Strings in C must be null-terminated ('\0').
- Functions like strlen and strcpy rely on the null terminator.
- Forgetting the null terminator leads to reading beyond allocated memory.
- Calculating strlen() repeatedly inside a loop is inefficient.
- Store the length in a variable before the loop for performance.
- This avoids redundant function calls.
- NULL represents an invalid or unassigned memory address (typically 0x0).
- Used to indicate errors or the absence of a valid pointer.
- Checking for NULL return values from functions like Malloc and Fopen is crucial.
- After calling Malloc or Fopen, check if the returned pointer is NULL.
- If NULL, handle the error (e.g., print an error message, return 1) and exit.
- This prevents dereferencing invalid pointers.
- Allocate memory for strings using Malloc based on calculated size (strlen + 1).
- Store the returned pointer in a char* variable.
- This provides a safe buffer for user input or string manipulation.
- Reads source and destination filenames from command-line arguments.
- Opens source file for reading ('r') and destination for writing ('w').
- Copies data byte-by-byte using Fread and Fwrite in a loop.
- Strcpy(dest, src) copies the source string to the destination buffer.
- Requires destination buffer to be sufficiently large.
- Simpler than manual copying but still requires careful memory management.
- Scanf('%s', buffer) is unsafe if input exceeds buffer size.
- Leads to buffer overflows, corrupting memory.
- CS50's GetString is a safer alternative for reading strings.
- NUL ('\0') is the null terminator character, marking the end of a C string.
- NULL is a special pointer value representing an invalid address.
- Both are important for string handling and error checking.
- Always check if Malloc returns NULL (indicating allocation failure).
- Always check if Fopen returns NULL (indicating file opening failure).
- Implement error handling to prevent crashes and ensure program stability.
- Check string length before processing to avoid errors.
- If expecting a non-empty string, validate that strlen(s) > 0.
- Prevents issues like trying to uppercase characters in an empty string.
- Memory allocated with Malloc must be freed when no longer needed.
- Failure to free leads to memory leaks, consuming system resources.
- Long-running programs with leaks can become slow or crash.
- Declaring variables (especially pointers) without initializing them results in garbage values.
- Accessing or using these garbage values leads to unpredictable behavior.
- Always initialize variables to known values (e.g., 0, NULL, empty string).
- Using an uninitialized pointer (e.g., 'char *s;') with Scanf or Fwrite leads to writing to arbitrary memory locations.
- This can corrupt unrelated data or crash the program.
- Always initialize pointers to valid addresses (e.g., using Malloc or '&').
- Functions like CS50's GetString handle memory allocation and input validation safely.
- Using standard C functions like Scanf requires careful manual management.
- For complex input, libraries abstract away difficult details.
- Fopen opens a file, returning a FILE pointer; 'w' for write, 'a' for append.
- Fprintf writes formatted data to a file.
- Fclose closes the file, saving changes.
- RAM is volatile (data lost on power off); disks (HDD/SSD) are non-volatile (persistent).
- File I/O allows saving data long-term.
- CSV (Comma Separated Values) is a common text file format for structured data.
- 'w' mode overwrites existing file content or creates a new file.
- 'a' mode appends new data to the end of the file.
- Choosing the correct mode is crucial for desired file behavior.
- Fopen returns a pointer to a FILE struct.
- This struct contains information about the file stream (e.g., current position, buffer).
- File I/O functions operate on this file pointer.
- Uses command-line arguments (argv) for source and destination filenames.
- Opens files using Fopen in 'r' (read) and 'w' (write) modes.
- Copies file content byte-by-byte using Fread and Fwrite.
- Reads data into a buffer (byte variable) using Fread.
- Writes the read data to the destination file using Fwrite.
- Loop continues until Fread returns 0 (end of file).
- Use 'rb' (read binary) and 'wb' (write binary) for non-text files (images, executables).
- Ensures data is read/written as raw bytes without interpretation.
- Crucial for preserving file integrity.
- A buffer is a temporary memory area (e.g., an array) for data.
- Video players use buffers to ensure smooth playback.
- Copying files byte-by-byte simulates reading and writing data in chunks.
- BMP files represent images as grids of pixels.
- Each pixel has RGB color values.
- File I/O enables reading and manipulating pixel data for image filters.
- Grayscale: Adjusting RGB values.
- Sepia: Applying a warm tone.
- Reflection: Mirroring pixels.
- Blurring: Averaging neighboring pixel values.
- Passing arguments by value creates copies within the function.
- Modifications to copies do not affect original variables outside the function.
- This prevents functions from directly altering caller variables.
- Function parameters are changed to pointers (int *).
- Dereference pointers ('*') to access and modify original variable values.
- Pass addresses using '&' operator.
- Stack: Local variables, function calls; grows upwards.
- Heap: Dynamic memory (Malloc); grows downwards.
- Code and globals are loaded at the top.
- Stack overflow: Excessive function calls/recursion.
- Heap overflow: Excessive memory allocation without freeing.
- Can lead to crashes; Stack Overflow website named after this error.
- Writing data beyond allocated array bounds.
- Corrupts adjacent memory, causing crashes or security issues.
- Requires careful bounds checking and safe input functions.
- GetString (CS50) handles dynamic memory allocation safely.
- Scanf requires manual memory management and address passing.
- Scanf('%s') is unsafe with fixed buffers.
- Strings (char*) are already pointers; pass them directly to Scanf('%s').
- Using '&' with a string variable creates a pointer-to-pointer, which is incorrect for Scanf.
- Scanf writes to the memory location pointed to by the string variable.
- Uninitialized pointers hold garbage addresses.
- Using Scanf or dereferencing them leads to writing/reading invalid memory.
- Causes crashes and unpredictable behavior; Valgrind detects these errors.
- Prefer library functions like GetString that handle memory safely.
- If using Scanf, ensure sufficient buffer size and validate input.
- Avoid manual memory management for basic input unless absolutely necessary.
- Fopen opens files, returning a FILE pointer; 'w' for write, 'a' for append.
- Fprintf writes formatted data to a file.
- Fclose saves changes and releases resources.
- RAM is volatile; disks (HDD/SSD) provide persistent storage.
- File I/O enables saving data long-term.
- CSV format uses commas to separate values in rows.
- 'w' overwrites or creates a file.
- 'a' appends data to the end of the file.
- Essential for managing file content.
- Fopen returns a pointer to a FILE struct.
- This struct manages file stream information.
- Pointers are fundamental to C's file handling.
- Uses command-line arguments (argv) for source/destination filenames.
- Opens files in 'r' (read) and 'w' (write) modes.
- Copies data byte-by-byte using Fread/Fwrite.
- Fread reads data into a buffer.
- Fwrite writes data from the buffer to the destination.
- Loop continues until end-of-file is reached.
- Use 'rb'/'wb' for non-text files (images, executables).
- Ensures raw byte data is read/written without interpretation.
- Critical for preserving file integrity.
- A buffer is temporary storage (e.g., an array).
- Video players use buffers for smooth playback.
- Byte-by-byte copying simulates buffering.
- BMP files store images as pixel grids.
- Each pixel has RGB color values.
- File I/O enables image filter implementation.
- Grayscale, Sepia, Reflection, Blurring.
- Achieved by manipulating pixel RGB values.
- Requires understanding file I/O and byte manipulation.
- Function arguments are passed by value (copies).
- Changes to copies inside the function don't affect originals.
- Illustrates scope limitations.
- Function accepts pointers (addresses) to variables.
- Dereference pointers ('*') to modify original values.
- Pass addresses using '&'.
- Stack: Local variables, function calls; grows upwards.
- Heap: Dynamic allocation (Malloc); grows downwards.
- Code/globals at top; potential for collision.
- Stack overflow: Excessive function calls/recursion.
- Heap overflow: Excessive Malloc without Free.
- Can lead to crashes; Stack Overflow website named after this.
- Writing past allocated array bounds.
- Corrupts adjacent memory, causing crashes/security issues.
- Requires bounds checking and safe functions.
- GetString (CS50) handles memory safely.
- Scanf requires manual memory management.
- Scanf('%s') is unsafe with fixed buffers.
- Strings (char*) are pointers; pass directly to Scanf('%s').
- Using '&' creates a pointer-to-pointer, incorrect for Scanf.
- Scanf writes to the memory location pointed to by the string variable.
Summary, takeaways, and chapters were generated by AI from the video's transcript and may contain errors. The video belongs to its creator, CS50.