Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Dereferencing Pointers in C
Follow a pointer to its target with the indirection operator, read and write variables without using their names, then test yourself with a quiz and interview questions. Free C tutorial for embedded C programming.
This is the lecture where pointers start to pay off. In this part of our free C programming course you learn dereferencing pointers in C: using the indirection operator * to reach the variable whose address a pointer holds. Until now we could only store and compare addresses. Now we can use them.
If you are following this free embedded C course to master c programming for embedded systems, dereferencing is how every hardware register is read and written. At the end of this free C tutorial page you will find a quiz and interview questions to lock in the whole opening of the pointers chapter.
Table of Contents
What You Will Learn
By the end of this lecture you can:
- Use the indirection operator
*to access the target of a pointer. - Read a variable and change it through a pointer.
- Tell apart the star in a declaration from the star in an expression.
- Explain why a pointer needs a base type, with a byte-level demonstration.
- Recognise how dereferencing a pointer to a register controls hardware.
- Answer common pointer interview questions on the opening topics.
Prerequisites
You should have worked through the earlier lectures in Chapter 8: memory and addresses, the address operator, declaring pointer variables, and assigning addresses to pointers. Make sure you can write int *p = &x; and explain what it means. This free C programming course builds each lecture on the one before, so a quick review of lecture 4 is worth five minutes.
Dereferencing Pointers in C
So far every program in this course has reached a variable by its name. A pointer lets you reach it indirectly, by going through its address. The operator that does this is the indirection operator, also called the dereference operator, and it is written *.
Put * in front of a pointer inside an expression and you get the object the pointer points to. If p holds the address of level, then *p is level: the same storage, not a copy.
int level = 87;
float gain = 4.5f;
int *lp = &level;
float *gp = &gain;
With these declarations, *lp can be used anywhere level could be, and *gp anywhere gain could be. The base type decides what you get: *lp is an int and *gp is a float.
Two stars, two meanings
The asterisk appears in two different roles, and beginners often mix them up. Position tells you which one you are looking at.
| Where you see it | Example | Meaning |
|---|---|---|
| In a declaration | int *lp; | lp is a pointer to int |
| In a declaration with initialiser | int *lp = &level; | lp is a pointer, and it starts out holding the address of level |
| In an expression | *lp = 5; | Follow lp and store 5 in the target |
| In an expression | x = *lp; | Follow lp and copy the target’s value into x |
| Between two operands | a * b | Multiplication, unrelated to pointers |
lp, the pointer, not to the object *lp. In a declaration the star only says “this is a pointer”.
Reading and Writing Through a Pointer
Because *lp is the target itself, you can use it on either side of an assignment. On the right it reads the value. On the left it changes the value of the original variable.
| Statement | Action | Effect on level |
|---|---|---|
x = *lp; | Read through the pointer | None, level stays as it was |
*lp = 10; | Write through the pointer | level becomes 10 |
*lp = *lp + 3; | Read, add 3, write back | level increases by 3 |
(*lp)++; | Increment the target | level increases by 1 |
Two identities are worth knowing. Applying the address operator and then the indirection operator brings you back where you started: *&level is simply level. And going the other way, &*lp is just lp. The two operators cancel each other out.
Parentheses protect you
Write (*lp)++ with parentheses when you want to increment the value that lp points to. Without them, *lp++ does something different (it moves the pointer). We explain why in the pointer arithmetic lecture; for now, use the parentheses.
Dereferencing Demo Program
This program reads and writes two variables through pointers, checks the two identities, and then peeks at the bytes of a 32-bit value through a byte pointer.
#include <stdio.h>
#include <stdint.h>
int main(void)
{
int level = 87;
float gain = 4.5f;
int *lp = &level;
float *gp = &gain;
printf("read via pointer: %d and %.1f\n", *lp, *gp);
*lp = *lp + 3;
*gp = 9.0f;
printf("after writing through pointers: level=%d gain=%.1f\n", level, gain);
printf("*&level = %d, &*lp == lp ? %d\n", *&level, &*lp == lp);
uint32_t word = 0x11223344;
uint8_t *bp = (uint8_t *)&word;
for (int i = 0; i < 4; i++)
printf("byte %d = 0x%02X\n", i, bp[i]);
return 0;
}
Build and run
gcc -Wall -Wextra -o deref_demo deref_demo.c
./deref_demo
Expected output on a little-endian PC
read via pointer: 87 and 4.5
after writing through pointers: level=90 gain=9.0
*&level = 90, &*lp == lp ? 1
byte 0 = 0x44
byte 1 = 0x33
byte 2 = 0x22
byte 3 = 0x11
The first line shows that *lp and *gp read the real values. After the writes, level and gain themselves have changed, even though we never assigned to their names. That is the whole point of indirection. The identity line confirms that the two operators cancel.
The last four lines look surprising at first. The 32-bit value 0x11223344 is stored with its least significant byte at the lowest address, so reading it byte by byte through a pointer gives 0x44 first. This is called little-endian storage, and it is used by x86 PCs and by ARM Cortex-M chips in their normal configuration. On a big-endian machine the order would be reversed. This demo also uses the fact that the language allows you to look at any object as a sequence of bytes through a byte pointer.
Why the Base Type Matters
In lecture 3 we promised to explain why a pointer needs a data type even though every pointer is the same size. The byte demo above is the answer. The pointer bp is a uint8_t *, so following it reads one byte. If the same address were held in a uint32_t *, following it would read four bytes and give you 0x11223344 instead. The address is identical; the base type decides the width and meaning of the read.
| Pointer type | Address held | What *ptr gives |
|---|---|---|
uint8_t * | address of word | One byte: 0x44 |
uint16_t * | address of word | Two bytes: 0x3344 |
uint32_t * | address of word | Four bytes: 0x11223344 |
(The 16-bit and 32-bit rows assume little-endian storage and a suitably aligned address, which holds for this variable.) Remember the second reason from last time too: the base type sets the step size in pointer arithmetic, which is the topic of the next stretch of this chapter.
Dereferencing in Embedded C Programming
In embedded C programming dereferencing is the mechanism behind memory-mapped input and output. A peripheral register is a memory location at a fixed address from the chip’s reference manual. To control hardware, you form a pointer to that address and dereference it:
#include <stdint.h>
/* Illustrative example for an STM32F4-style GPIOA output data register. */
/* Do NOT run this on a PC: that address does not exist there. */
#define GPIOA_ODR (*(volatile uint32_t *)0x40020014u)
void led_on(void)
{
GPIOA_ODR |= (1u << 5); /* set bit 5: drive pin PA5 high */
}
Read the macro from the inside out. 0x40020014u is just a number. The cast (volatile uint32_t *) says “treat this number as the address of a 32-bit register”. The leading * dereferences it, so GPIOA_ODR behaves like a variable that happens to live inside the hardware. Writing to it changes real pin voltages.
Why volatile is there
A register can change without your code touching it (a status flag set by hardware), and a write can have an immediate side effect. The volatile qualifier stops the compiler from optimising away or reordering accesses, so every dereference really reaches the hardware.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Dereferencing an uninitialised pointer | Undefined behaviour, random crash or corruption | Always assign a valid address first |
| Dereferencing NULL | Segmentation fault on Linux, possibly no fault at all on bare metal | Check if (p != NULL) first |
Writing p = 5; instead of *p = 5; | Changes where p points, not the target | Use the star to write the target |
Writing *p++ when you meant (*p)++ | Pointer moves instead of value changing | Use parentheses |
| Following a pointer to a variable that no longer exists | Dangling pointer, undefined behaviour | Do not keep addresses of expired locals |
| Mixing up the declaration star and the expression star | Confused code | Declaration says “is a pointer”, expression says “follow it” |
Best Practices
- Initialise, then check, then dereference.
- Use
conston pointers that are only read through. - Use
volatileon pointers to hardware registers. - Prefer named macros or
structoverlays for registers rather than raw addresses scattered through code. - Set a pointer to NULL once its target is no longer valid.
Performance and security note
An indirect access normally costs one extra memory read compared with using the variable directly, and it can prevent some compiler optimisations because the compiler cannot always tell what the pointer may refer to. Security-wise, dereferencing a bad pointer is the root of many exploits, so treat every pointer as untrusted until you have checked it.
Key Takeaways
*pin an expression means the object that p points to.- You can read it or assign to it, so a variable can be changed without using its name.
- The star in a declaration means “this is a pointer”; the star in an expression means “follow it”.
*&xisxand&*pisp.- The base type decides how many bytes a dereference reads.
- Embedded code reaches hardware by dereferencing volatile pointers to fixed addresses.
Conclusion
You now hold the full pointer toolkit for this opening part of the chapter: addresses, the address operator, pointer variables, assignment, NULL and dereferencing. Together they let a function change its caller’s variables, let a driver talk to hardware, and prepare you for pointer arithmetic, arrays, strings and dynamic memory in the lectures ahead. Work through the quiz and interview questions below before moving on. They are the same style of question you will meet in real embedded interviews, and this free C programming course is built to get you ready for exactly that.
Chapter Quiz
Try each question on paper first. Open the answer only after you have committed to your choice.
Multiple choice
Q1. If p is a pointer to int, what does *p mean in an expression?
- The address of p
- The int that p points to
- The size of p
- p multiplied by something
Show answer
B. The indirection operator follows the pointer to its target.
Q2. Which declaration makes both a and b pointers to int?
int *a, b;int* a, b;int *a, *b;int a, b *;
Show answer
C. Each name needs its own star. In A and B only a is a pointer.
Q3. After int n = 5; int *p = &n;, what is the value of *p?
- The address of n
- 0
- 5
- Undefined
Show answer
C. p points to n, so *p is n, which is 5.
Q4. On one platform, how do sizeof(char *) and sizeof(int *) compare?
- char * is smaller
- int * is smaller
- They are equal
- It depends on the value stored
Show answer
C. Every object pointer stores an address, so they have the same size. Only the base type differs.
Q5. Which expression is invalid?
&count&table[1]&289&config.mode
Show answer
C. A constant has no storage, so it has no address. The compiler reports that an lvalue is required.
Q6. What happens if you dereference a NULL pointer?
- It returns 0 safely
- It returns the address 0
- Undefined behaviour
- The compiler fixes it
Show answer
C. Undefined behaviour. On Linux this is usually a segmentation fault, but on some microcontrollers it may silently access real memory at address 0.
Q7. Which printf format should be used to print an address portably?
%d%u%f%pwith a(void *)cast
Show answer
D. %p is the standard format for pointers and works on 32-bit and 64-bit systems.
Q8. What is the value of *&x for an int variable x?
- The address of x
- x
- 0
- A compile error
Show answer
B. The two operators cancel: &x gives the address and * follows it back to x.
Predict the output
P1. What does this print?
int a = 10;
int *p = &a;
*p = *p + 5;
printf("%d", a);
Show answer
15. The write through p changes a itself.
P2. What does this print?
int x = 4, y = 9;
int *p = &x;
int *q = &y;
p = q;
*p = 1;
printf("%d %d", x, y);
Show answer
4 1. After p = q, p points to y, so *p = 1 changes y. The variable x is untouched.
P3. What does this print?
int n = 7;
int *p = &n;
int *q = p;
*q = *q * 2;
printf("%d %d", n, *p);
Show answer
14 14. p and q both point to n, so the doubling is visible through every name.
P4. What does this print?
int v = 3;
int *p = &v;
printf("%d %d", *&v, *p == v);
Show answer
3 1. *&v is v, which is 3, and the comparison *p == v is true, which prints 1.
Interview Questions
These are the pointer fundamentals that interviewers for embedded and systems roles ask again and again. Practise saying the answers out loud in your own words.
1. What is a pointer in C?
A variable that stores the memory address of another object. It has its own name, type and storage, and its type (the base type) says what kind of object it points to.
2. What is the difference between &x and *p?
&x is the address-of operator and gives the address of x. *p is the indirection operator and gives the object that pointer p points to. They are opposite operations.
3. Why does a pointer need a data type if all pointers are the same size?
The type tells the compiler how many bytes to read or write when the pointer is dereferenced, how to interpret them, and how far to step in pointer arithmetic.
4. What is a wild pointer, and how do you avoid it?
A pointer that was never initialised and holds an unknown address. Avoid it by assigning a valid address or NULL at declaration, and by setting pointers to NULL when their target becomes invalid.
5. What is a NULL pointer, and is dereferencing it always a crash?
NULL is a pointer value guaranteed not to point to any valid object. Dereferencing it is undefined behaviour. On Linux it typically faults, but on many microcontrollers address 0 is valid, so there may be no fault. You must check for NULL yourself.
6. What is the size of a pointer in C?
It depends on the platform’s address width: typically 2 bytes on 8-bit AVR, 4 on 32-bit ARM Cortex-M and 8 on 64-bit PCs. All object pointers on a platform have the same size. Use sizeof to check.
7. In int *a, b; which variables are pointers?
Only a. The star binds to the name that follows it, so b is a plain int. Write int *a, *b; or declare each on its own line.
8. Why can’t you take the address of a constant or an expression?
The address operator needs an lvalue, an object with a storage location. A constant or a computed result is a temporary value with no fixed address.
9. How would you write to a memory-mapped hardware register in C?
Cast the register’s fixed address to a volatile pointer of the right width and dereference it, for example *(volatile uint32_t *)0x40020014u = value;. The volatile keyword forces every access to really happen.
10. What is the difference between a pointer and the variable it points to?
The variable holds data. The pointer holds the address of that data. Copying a pointer copies the address, so both pointers refer to the same variable; copying the variable creates a separate value.
Frequently Asked Questions
What does dereferencing a pointer mean?
It means following the address stored in the pointer to reach the object at that address, using the * operator.
What is the difference between * in a declaration and * in an expression?
In a declaration it says the variable is a pointer. In an expression it follows the pointer to its target.
Can I change a variable through a pointer?
Yes. Writing *p = value; changes the variable that p points to, even though you never used the variable’s name.
Why does reading a uint32_t byte by byte give the lowest byte first?
Most PCs and ARM Cortex-M systems are little-endian, which stores the least significant byte at the lowest address.
Why do embedded programmers use volatile with pointers?
Hardware registers can change on their own, and writes can have side effects. Volatile prevents the compiler from optimising away or reordering accesses.
Is dereferencing used in embedded C programming?
Constantly. Every read or write of a peripheral register, buffer or driver structure through a pointer is a dereference.
Where can I find more free lessons like this?
EmbeddedPathashala offers a free embedded systems course, a free Linux device drivers course, a free Linux kernel development course and this free C programming course.
Finished the Pointer Basics?
Great work. Continue with the next lectures of the free embedded C course, where pointers meet arrays, arithmetic and functions.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments