C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
43: Understanding Memory Addresses
Up until now, we've treated variables like magic boxes. You give a variable a name, you put a value in it, and C remembers it for you. But as a software engineer, "magic" is a dangerous way to think. In reality, your variables aren't boxes—they are specific locations in your computer's RAM. Every single byte of memory has a unique numeric address, like a house number on a very long street.
Defining our game state variables
To see this in action, let's build a tiny snippet of a game save system. I want to track two things: a player's health and their current gold count. I'll keep it simple with two integers.
int health = 100;
int gold = 50;
At this point, the compiler has set aside two chunks of memory for these values. You know them as health and gold, but the CPU sees them as hex codes like 0x7ffeefbff5c8. This is where the "address-of" operator, the ampersand (&), comes in. It tells C: "Don't give me the value inside the box; give me the address of the box itself."
Peeking under the hood with the & operator
I want to print these addresses to the console so we can actually see where they live. I'll use the %p format specifier, which is specifically designed for pointers (addresses).
printf("Health value: %d\n", health);
printf("Health address: %p\n", (void*)&health);
printf("Gold value: %d\n", gold);
printf("Gold address: %p\n", (void*)&gold);
I added the (void*) cast there because %p expects a generic void pointer. It's a habit that prevents compiler warnings on stricter systems. When you run this, you'll see two long hexadecimal strings. These are the exact coordinates in your RAM where your game data is sitting.
A quick detour into a common mistake
When I first started doing this, I thought, "Wait, an address is just a number. Why can't I just print it as a decimal integer to make it easier to read?" I tried something like this:
// My mistake: trying to treat a memory address as a standard integer
printf("Health address as decimal: %d\n", &health);
I quickly realized why that's a bad idea. First, the compiler started screaming warnings at me because a memory address (usually 64-bit on modern machines) is way too large to fit into a standard 32-bit int. The value got truncated, and the number I saw was completely useless. Memory addresses aren't "numbers" in the way we think of counts or measurements; they are identifiers. Always use %p for addresses. Don't try to force them into int or long variables unless you're doing very specific low-level memory manipulation.
Visualizing the memory gap
One thing that always fascinated me when I first learned this was seeing how the compiler arranges these variables. If you look at the output of our game state addresses, you'll notice they are very close to each other, but not identical. Since an int usually takes up 4 bytes, you'll likely see a difference of 4 (or sometimes 8, depending on padding) between the two addresses.
This layout is the foundation for everything we'll do with pointers. Once you stop seeing health as a name and start seeing it as "the value located at address 0x7ffe...", the rest of C's memory management starts to make a lot more sense.
📋 Practical Task
The Variable Distance Mapper
Create a program that declares three different variables of different types: a char, an int, and a double. Print the memory address of each variable using %p. Once you've done that, observe the hexadecimal addresses and determine which variables are stored closest to each other and how the size of the data type (1 byte for char, 4 for int, 8 for double) reflects the "gap" between their addresses in memory.
There are no comments for now.