#include <stdio.h>
int main(void) {
int value = 42;
int *p = &value;
// --- READ: *p in a value position --- int copy = *p; // copy becomes 42 printf("%d\n", *p); // prints 42 printf("%d\n", *p + 1); // prints 43 — use it in an expression
// --- WRITE: *p on the left of an assignment --- *p = 99; // changes value to 99 printf("value is now %d\n", value);
*p += 10;
// 109 — read, add, write back printf("value is now %d\n", value);
return 0;
}Dereferencing Pointers in C: Reading and Modifying Memory Values
What is Dereferencing Pointers in C? Core Concept & Direct Summary
How does the unary dereference operator `*` access and modify values located at pointer memory addresses?
A pointer on its own is just a number. It becomes useful at exactly one moment: when you dereference it — when you follow the address to the data living there.
That operation is the * operator, and it does two different jobs depending on where it appears:
*pin a read position gives you the value at that address*p = valuein a write position changes the value at that address
Both are the same operator, both use the same memory, and both can fail in the same ways. This page is about the dereference operation itself: how it works, what determines how many bytes it touches, the precedence rules that trip people up, and — most usefully — the catalogue of dereference errors and what each error message is trying to tell you.
*(Suggested image: the hero diagram — a pointer box with an arrow to a data box; two labeled paths along the arrow: "read: *p → 42" and "write: p = 99 →" giving the same visual treatment)
How Does Dereferencing Pointers in C Work? In-Depth Mechanics & Architecture
The Two Directions of a Dereference
The same address supports both reading and writing. Which one you get depends on where the expression sits:
Output:
Two things worth noticing:
- Reading through a pointer and reading the variable directly give the same answer — because it's the same memory. There's no copy involved.
- Writing through the pointer changes the original variable.
*p = 99madevalue99. The*is what makes the left-hand side a location rather than a value.
The structural reason — in C, an expression is either:
- an lvalue (something with a location that can be assigned to), or
- an rvalue (just a value, with no persistent location)
p is an lvalue (the pointer variable itself). *p is also an lvalue (the thing it points at). That's why *p = 99 is legal while 5 = 99 isn't. Dereferencing converts an address into a location you can assign to.
(Suggested image: the same expression *p shown in two boxes — one labeled "rvalue position → reads 42", one labeled "lvalue position → writes here")
How Many Bytes a Dereference Touches
This is where pointer types become essential. A dereference doesn't just "read memory" — it reads a specific number of bytes, decided by the pointer's type:
Output:
All four pointers are the same size (8 bytes on a 64-bit system), but dereferencing each touches a different amount of memory. *ip reads 4 bytes and interprets them as an int; *cp reads 1 byte as a char.
This is precisely why pointer types matter, and it's the source of a whole family of bugs. If you dereference an int * where the data was actually a double, you read 4 bytes of an 8-byte value and interpret the bit pattern as an integer — producing a large, meaningless number. No error, no warning, just nonsense.
The cast silences the compiler's warning and gives you the bug anyway. (This technique is legitimate when you deliberately want to inspect raw bytes — type punning — but it must be intentional, not accidental.)
(Suggested image: four pointers of different types pointing at the same address, each with a labeled bracket showing how many bytes it reads — the type controls the window width)
Dereference Precedence: The Rules That Bite
* has specific precedence relative to the operators around it. These four combinations look similar and mean four different things:
The key fact: postfix ++ binds tighter than *. So *p++ parses as *(p++) — the pointer moves. To modify the value, you must parenthesize: (*p)++.
The advice: parenthesize whenever you combine * with ++, --, or any other operator. You'll get the behavior you meant, and the next reader won't have to parse precedence rules in their head.
A related trap: & and * combined.
&*p and *&x are both legal and both no-ops in terms of value — but the types can differ subtly, which matters in advanced code. For everyday writing, the useful takeaway is that * and & are inverses (as covered in the pointers pillar page).
(Suggested image: the five expressions as a table graphic with arrows showing what moves in each case — the pointer or the pointed-to value)
Dereferencing in Different Contexts
The * symbol appears in more places than people realize. Here's the full set, so you can recognize each:
1. Declaration
This is the confusing one: at declaration time, * is part of the type, not an operation. int *p means "p is a pointer to int" — nothing is being dereferenced.
2. Reading through it
3. Writing through it
4. Passing a dereferenced value
5. Dereferencing an array element's address
6. Dereferencing a pointer to a struct
That last pair is worth knowing: (*sp).x and sp->x are identical. The -> operator exists because(*ptr).member is ugly and error-prone. (Struct pointers get their own tutorial in the structures cluster.)
7. Dereferencing a function pointer
Both forms work; modern style omits the *. (From the function pointers tutorial.)
8. Dereferencing void * — not allowed directly
void * can hold any address, but its pointee size is unknown, so the compiler refuses to guess. You must cast to a concrete type — which is exactly what qsort's comparison functions do.
(Suggested image: the eight contexts as a grid of small code cards, each labeled with which role * is playing)
Dereference Errors: The Catalogue
Almost every pointer crash you'll meet is one of these. Recognizing them by symptom saves enormous debugging time.
Error 1: Dereferencing NULL
Symptom: immediate crash at that line. This is the good error — it points exactly at the bug. (See the NULL pointers tutorial for the defensive patterns.)
Error 2: Dereferencing an uninitialized pointer
Symptom: might crash, might silently corrupt memory, might appear to work. The worst kind of bug, because there's nothing to check — garbage rarely equals zero, so a NULL check gives a false negative. Fix: always initialize to NULL or a real target.
Error 3: Dereferencing a dangling pointer
Symptom: undefined behavior that often appears to work in testing, then fails in production or under a sanitizer. (See the dangling pointers tutorial for prevention.)
Error 4: Type mismatch on dereference
Symptom: plausible-looking but wrong values — huge integers, tiny fractions. The cast suppressed the warning, but the size mismatch remains.
Error 5: Dereferencing a non-pointer
Symptom: compile-time error ("invalid type argument of unary '*'"). Easy: you need &x to get an address first.
Error 6: Writing through a const pointer
Symptom: compile-time error ("read-only parameter"). That's the const promise doing its job — this one is a feature, not a bug.
Error 7: Writing through a pointer to read-only memory
Or equivalently:
Symptom: often a crash, sometimes silent failure. String literals live in read-only memory; use char s[] = "Hello"; if you need to modify.
Error 8: Dereferencing one-past-the-end
Symptom: reading whatever lies past the array. The pointer arithmetic tutorial covered the rule: computing and comparing one-past-the-end is fine; dereferencing it is not.
Error 9: Freeing something not from malloc
Not strictly a dereference error, but the same family — invalid memory operations on addresses C never allocated for you. Stack variables and literals are not yours to free.
(Suggested image: the nine errors as a diagnostic table — error, symptom, fix — styled as a reference chart. This is the highest-value asset on the page for readers.)
A Complete Example: Reading and Writing a Bit Field
A practical program that dereferences in both directions — reading and modifying real data through pointers:
Sample Run:
Every dereference in this program is deliberate and validated:
setBit(int *value, ...)— the parameter is non-constbecause it writes. Each write is*value |= ...and*value &= ~..., both read-modify-write through the pointer.readBit(const int *value, ...)— the parameter isconstbecause it only reads. The compiler now enforces that: a stray write insidereadBitwould be a compile error.- Validation before every dereference —
if (value == NULL ...)guards each function, so passingNULLor an absurd bit index returns an error value instead of crashing. - The read-only path is separate from the write path — a design habit worth noticing: if a function only reads, mark the parameter
constand the compiler becomes your second pair of eyes.
That's the entire discipline of dereferencing in one program: know the type, check the validity, use const for reads, and be explicit about which direction the data flows.
Common Dereference Mistakes
- Dereferencing before checking for
NULL— crashes at that line. Validate any pointer from outside your function. - Dereferencing uninitialized pointers — garbage address, undetectable by NULL checks. Initialize everything.
- Mixing up
pand*p— one is an address, one is a value. Printing the wrong one gives either a hex number or nonsense. - Forgetting
*on assignment —p = 99reassigns the pointer (to address 99!) instead of the value. Compiles with a warning; crashes when used. This is a common and nasty one. - Forgetting
&when passing to a pointer parameter —swap(a, b)instead ofswap(&a, &b). - Precedence confusion —
*p++moves the pointer;(*p)++changes the value. - Type mismatches hidden by casts — a cast silences the warning, not the size mismatch.
- Dereferencing a result you never checked —
strchr,strstr,malloc,fopenand friends returnNULLon failure. Check first. - Writing through a pointer to a literal —
char *s = "abc"; s[0] = 'x';is undefined behavior. - Dereferencing a
void *directly — the compiler can't know the size. Cast to a typed pointer first. - Assuming
*p = *qcopies memory — it copies the value of one element (the pointee type's worth). For arrays/structs, usememcpyor a loop.
(Suggested image: "11 Dereference Mistakes" checklist — with #4 (p = 99 instead of *p = 99) highlighted, since it's the one that compiles and then wrecks your program)
Final Thoughts
Dereferencing is where a pointer stops being a number and becomes a way to touch data. Two directions — read and write — one operator, and a set of rules that all trace back to a single question: what type does this pointer point at? The type decides how many bytes each dereference touches, how far arithmetic moves, and what a cast might be hiding.
The habits that prevent essentially every dereference bug:
- Initialize every pointer — to a real target or
NULL - Check before you dereference anything you didn't just create
- Use
conston read-only pointer parameters so the compiler enforces your intent - Parenthesize
*when combining it with++or-- - Don't hide type mismatches with casts — fix the type instead
And when a crash happens, use the symptom to identify the cause: an immediate segfault suggests NULL; silent corruption suggests uninitialized or dangling; wrong-but-plausible values suggest a type mismatch. That diagnostic reflex, built on this page's catalogue, is what turns pointer debugging from guesswork into a short list.
Runnable C Code Example & Practical Implementation
#include <stdio.h>
int main() {
int value = 500;
int *ptr = &value;
int **dptr = &ptr;
// Pointer to pointer
printf("Direct value: %d\
", value);
printf("Single dereference: %d\
", *ptr);
printf("Double dereference: %d\
", **dptr);
**dptr = 999;
// Modifies 'value' via double pointer
printf("Updated value: %d\
", value);
return 0;
}Key Takeaways, Best Practices & Memory Safety Rules
(*ptr)++) to prevent precedence ambiguities.Frequently Asked Questions & Technical Interview Insights
Technical Interview & Viva Questions
Frequently asked C programming interview questionsQ: What is a pointer to pointer (`type**`) typically used for in C?
Q: What is the difference between `*ptr++` and `(*ptr)++`?
Related Tools, Tutorials & Authoritative References
Recommended Interactive Tools
- Online C Compiler & Runner — Interactive online GCC sandbox to execute C code in real time.
- Online C Code Editor & IDE — Multi-file C programming environment with code formatting.
- C++ Programming Tutorial — Transition from procedural C to Object-Oriented C++ with STL.
- JavaScript Programming Tutorial — Master modern JavaScript fundamentals, DOM APIs, and async patterns.
Authoritative Standards & Citations
- ISO/IEC 9899 (C Language Standard) — Official international C specification by ISO/IEC JTC 1/SC 22/WG 14.
- GNU GCC Documentation — Official reference manual for the GNU Compiler Collection and C libraries.
- The C Programming Language (K&R) — Definitive classic work by Brian Kernighan & Dennis Ritchie at Bell Labs.
- C Reference Documentation (cppreference.com) — Verified reference covering C89 through C23 language features.