Why are three words "senior, same, and nuns" important to programmers?
Can you guess?
No, it's not a mental acuity test for unwell presidents.
It's a kind of pointer. Don’t worry if it doesn’t make sense yet. All will be revealed.
The Joy and Frustration of Pointers in C
Pointers are one of the most fundamental yet often misunderstood concepts that frustrate people new to C programming. I was there myself for the longest time - I did everything I could to avoid them.
The problem is they are vital in some cases, and in others, such as retro computer programming, really really useful.
Getting your head around what pointers are and how they work can significantly improve your understanding of memory management and how programs operate under the hood.
Let me show you why pointers matter and how they can be used.
So What Is a Pointer?
A pointer is a special kind of variable that stores a memory address rather than a direct value. That means, instead of holding data like a number or a string, a pointer holds the location of where that data is stored in memory.
Think of a pointer as being a box. On the outside it says "Screen RAM," but instead of being full of screen characters, inside there’s a slip of paper with GPS coordinates typed neatly on it.
Do the three words make sense now?
senior.same.nuns is the what3words location for Bletchley Park, the historical home of British codebreaking.
A pointer doesn’t reference the thing itself, but tells you where to find the thing.
Why Are Pointers Important?
Understanding pointers helps you to:
- Manage memory efficiently (C isn’t as cozy as other languages, you’re responsible for allocation and cleanup)
- Pass large data structures around without duplication and waste (vital on retro machines with less than 64KB RAM, total!)
- Build complex data structures such as linked lists and trees
- Interact directly with hardware or system resources (a key reason people drop to assembler, and essential for retro performance)
Without pointers, you’d be limited to working with data only within the scope of what you write, rather than being able to directly address your hardware.
A Simple C Example
int lifemeaning = 42;
int *p; // p is a pointer to an int
p = &lifemeaning;
// p now holds the address of the meaning of life, the universe, and everything
lifemeaning
contains42
.p
is a variable holding the address oflifemeaning
.- The
&
operator asks for the address oflifemeaning
, which is then stored inp
.
Imagine your program’s memory as a big city map:
lifemeaning
lives at street address0xFF
.p
holds that address (0xFF
).
Printing p
shows you the memory address it points to, like seeing the street number of your data’s house.
Getting Hexed
Memory addresses are often displayed as hexadecimal numbers (like 0xFF
) because hex is compact and maps neatly onto how bytes are grouped in memory.
In little endian systems, multi-byte values are stored with the least significant byte first. So 0x0000002A
(42) would be stored in memory as 2A 00 00 00
.
How This Helps
-
Passing Large Data Without Copying Instead of wastefully copying large chunks of data around, passing a pointer lets functions operate directly on the original data.
-
Allocating Memory Dynamically Using functions like
malloc
,calloc
, andfree
, programs can allocate memory during runtime, with pointers referencing these blocks. -
Building Complex Data Structures Linked lists, trees, and graphs rely on pointers to connect nodes dynamically.
For example, a list node might contain:
- Address of previous item
- Current item ID
- Current item value
- Address of next item
Unlike fixed-length arrays, this allows flexible, navigable structures.
C64 Screen and Colour RAM
On the Commodore 64, the screen display is just a block of memory starting at $0400
(1024 decimal). Each byte represents a character cell. The colours live separately, starting at $D800
(55296 decimal).
With cc65, we can use pointers to write directly into those areas:
#include
#define SCREEN ((uint8_t*)0x0400)
#define COLOR ((uint8_t*)0xD800)
void main(void) {
const char *msg = "HELLO POINTERS!";
uint8_t i = 0;
while (msg[i] != 0) {
SCREEN[i] = msg[i]; // write character to screen RAM
COLOR[i] = 1; // set text colour (1 = white)
i++;
}
}
What's happening:
SCREEN
andCOLOR
are pointers to the fixed addresses of the screen and colour memory.- Writing to
SCREEN[i]
draws a character at positioni
. - Writing to
COLOR[i]
sets its colour.
This shows how with pointers you can talk directly to the hardware, it's either much slower or even impossible without them.
Wait - That's An Array!
Good catch! Arrays and pointers aren’t identical in C, but they’re very closely related.
When you declare an array like this:
int numbers[3] = {10, 20, 30};
.. numbers
represents the address of the first element in the array. You can then use a pointer to walk through the array:
int *p = numbers; // points to numbers[0]
printf("%d\n", *p); // prints 10
p++; // increment p, ie. move to the next element
printf("%d\n", *p); // prints 20
The array name (numbers
) behaves like a constant pointer to its first element. In fact, numbers[i]
is equivalent to *(numbers + i)
Pointer arithmetic is very useful, it lets you move through arrays as if you’re stepping through memory.
This is why strings in C are just arrays of characters, with a pointer to the first one, and very often with a specific value or character to mark the end of the string.
On retro systems like the Commodore 64, this is especially handy because screen RAM or sprite data can be treated like arrays of bytes that you then walk through using pointers.
Danger Danger!
Working directly with memory is powerful but risky. The most common pitfalls include:
- Dangling pointers: using freed memory
- Memory leaks: forgetting to free allocated memory
- Buffer overflows: writing past allocated space
Always initialise your pointers and carefully manage memory to avoid nasty bugs, crashes, or security issues.