Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Assigning Addresses to Pointers
Give your pointers real targets, initialise them at declaration, copy one pointer into another and make unused pointers safe with NULL. Free C tutorial for embedded systems learners.
A freshly declared pointer is an empty promise. In this lecture of our free C programming course we cover assigning addresses to pointers: how to point at a variable, how to initialise safely, how to copy a pointer, and what NULL really guarantees.
These habits are the dividing line between code that works and firmware that crashes at 3 a.m. For anyone studying embedded C programming through this free embedded C course, safe pointer initialisation is not optional.
Table of Contents
What You Will Learn
By the end of this lecture you can:
- Explain why a pointer must never be used before it is assigned.
- Assign the address of a variable to a pointer using
&. - Declare and initialise a pointer in a single statement.
- Explain why the base type of pointer and target must match.
- Copy one pointer to another so both point to the same variable.
- Use NULL to mark a pointer that points to nothing, and test for it.
Prerequisites
Complete the earlier lectures in this chapter: memory and addresses, the address operator, and declaring pointer variables. You need to be able to write int *p; and to understand that &x gives the address of x. That is all we need to get going.
Why Uninitialised Pointers Are Dangerous
When you declare an ordinary local variable and do not set it, it holds garbage. A pointer behaves the same way, but the consequence is far worse. A local int with garbage gives you a wrong number. A local pointer with garbage points to a random place in memory. If you then read through it you get unpredictable data; if you write through it you may overwrite something unrelated, such as another variable, a return address or a hardware register.
Assigning Addresses to Pointers
You give a pointer a target with an ordinary assignment, putting the address operator on the right-hand side:
int level = 30;
float ratio = 2.5f;
int *ip;
float *fp;
ip = &level; /* ip now holds the address of level */
fp = ∶ /* fp now holds the address of ratio */
After these two assignments, ip points to level and fp points to ratio. Note that we assign without a star on the left: ip = &level; changes what ip holds. The star in a later statement means something different, and we study that in the next lecture.
Initialising at Declaration
You can combine declaration and assignment, which is the safest style because the pointer never exists in an unset state:
int level = 30, *ip = &level;
float ratio = 2.5f, *fp = ∶
Here the variable is declared before the pointer, in the same statement. That order matters: the compiler must already know about level to take its address. Writing int *ip = &level, level = 30; would use level before its declaration finishes and is an error. In the real world, many teams prefer one declaration per line anyway:
int level = 30;
int *ip = &level;
Matching the Base Type
An int * is meant to hold the address of an int. If you assign the address of a float to it, you break the contract: later, reading through the pointer would interpret float bits as an integer. Older textbooks say the compiler stays silent and the output is simply wrong. Modern GCC is stricter. Try this:
int main(void)
{
float ratio = 2.5f;
int *ip = ∶
return 0;
}
GCC 13 reports:
bad2.c: In function 'main':
bad2.c:4:15: warning: initialization of 'int *' from incompatible pointer type 'float *' [-Wincompatible-pointer-types]
4 | int *ip = ∶
| ^
Since GCC 14, this incompatible-pointer-types problem is an error by default, not just a warning, so such code no longer compiles at all. Never silence this diagnostic with a cast just to get rid of it. The warning is telling you the truth.
| Assignment | Result |
|---|---|
int *ip = &int_var; | Correct: types match |
float *fp = &float_var; | Correct: types match |
int *ip = &float_var; | Incompatible pointer type: warning or error |
char *cp = &int_var; | Incompatible pointer type: warning or error |
Copying One Pointer into Another
You may assign one pointer to another when their base types are the same. The assignment copies the address, so afterwards both pointers point to the same target. No data is duplicated. There is still only one variable.
int *first = &gain;
int *second = first; /* second now holds the same address */
This is called aliasing, and it is powerful and dangerous in equal measure: a change made through one pointer will be visible through the other. In embedded code, a pointer to a shared buffer given to both an interrupt handler and the main loop is a classic example.
The NULL Pointer
Sometimes you have a pointer but no valid target yet, or you are done with its target. For that case C gives you a special value, the null pointer. You write it with the macro NULL, defined in <stddef.h> and also made available by <stdio.h>, <stdlib.h> and <string.h>:
#include <stddef.h>
int *ptr = NULL;
The language guarantees that a null pointer compares unequal to the address of any real object or function. That gives you a reliable way to say “this pointer points to nothing”, and to test it before use:
if (ptr != NULL) {
/* safe to follow ptr here */
}
You may assign the constant zero to a pointer of any type, and the compiler turns it into a null pointer of that type. Writing NULL is clearer than writing 0, so always prefer it. Strictly speaking, the internal bit pattern of a null pointer is not required to be all zeros on every machine, but you never need to care: just use NULL and compare against NULL.
| State | How it happens | Safe to follow? |
|---|---|---|
| Wild (uninitialised) | Declared, never assigned | No, unknown target |
| Valid | Assigned &variable of the right type | Yes, while the variable exists |
| Null | Assigned NULL | No, points to nothing; test first |
| Dangling | Target no longer exists | No, target has gone |
Demo Program
This program ties the whole lecture together: it points one pointer at a variable, copies it, compares the two, makes a null pointer and tests it.
#include <stdio.h>
#include <stddef.h>
int main(void)
{
int gain = 40;
int *first = &gain;
int *second = first;
int *idle = NULL;
printf("first holds %p\n", (void *)first);
printf("second holds %p\n", (void *)second);
printf("same target? %s\n", first == second ? "yes" : "no");
printf("idle holds %p\n", (void *)idle);
if (idle == NULL)
printf("idle points nowhere, safe to skip\n");
float ratio = 2.5f, *rp = ∶
printf("ratio is %.1f and rp %s\n", ratio, rp == &ratio ? "points to it" : "is elsewhere");
return 0;
}
Build and run
gcc -Wall -Wextra -o ptr_assign ptr_assign.c
./ptr_assign
Sample output
first holds 0x7fff17aa5960
second holds 0x7fff17aa5960
same target? yes
idle holds (nil)
idle points nowhere, safe to skip
ratio is 2.5 and rp points to it
The two addresses are identical because second is a copy of first. Your address will differ. The text (nil) is how the GNU C library prints a null pointer with %p; other libraries may print 0x0 or (null), which is another reason never to depend on the printed form.
Pointer Assignment in Embedded C Programming
In c programming for embedded systems, these rules have very practical consequences:
- Initialise every pointer. Local variables on a microcontroller stack hold leftovers from earlier calls. A wild pointer can write into a peripheral register and toggle a motor.
- NULL as “not configured”. Callback pointers and optional buffers are set to NULL until the application provides them, and drivers check before using them.
- Address zero is not always protected. On a PC with Linux, following a null pointer usually stops the program with a segmentation fault, which is a helpful alarm. On many bare-metal microcontrollers address 0 is a perfectly valid location (often an alias of flash or the vector table), so following a null pointer may read or write something real without any fault. Your own NULL checks are the only protection.
- Do not point at stack data that dies. Never store the address of a local variable in a global pointer and use it after the function returns.
Habit worth building today
Write int *p = NULL; instead of int *p; whenever you cannot assign the real target yet. A pointer that is NULL can be tested. A wild pointer cannot.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Using a pointer before assigning it | Wild pointer, crash or silent corruption | Assign &var or NULL immediately |
| Assigning the address of a different type | Warning or error, wrong data read | Match the base type |
Writing int *p = &x, x; (variable after pointer) | Compile error | Declare the variable first |
| Assuming a copy of a pointer copies the data | Surprise changes through the alias | Remember only the address is copied |
| Skipping the NULL check | Crash on a PC, silent corruption on bare metal | Test if (p != NULL) before use |
Comparing pointers to integers like p == 5 | Meaningless, compiler warning | Compare pointers with pointers or NULL |
Best Practices
- Initialise every pointer at declaration, to a real address or NULL.
- Compile with
-Wall -Wextraand treat incompatible pointer warnings as bugs. - Use
NULL, not a bare0, so the intent is obvious. - Check pointers received as function parameters before using them.
- Keep pointer lifetimes shorter than the lifetime of their targets.
Security note
Uninitialised and dangling pointers are behind a large share of memory-safety vulnerabilities. Setting pointers to NULL after you finish with their target removes a whole class of “use after free” accidents, because a NULL check can then catch the stale pointer.
Key Takeaways
- Never use a pointer before it holds a valid address or NULL.
ptr = &variable;makes a pointer point to a variable.- A pointer can be initialised at declaration if the variable is declared first.
- Pointer and target must have matching base types.
- Assigning one pointer to another copies the address, not the data.
- NULL marks a pointer that points to nothing, and you should test for it.
Conclusion
Your pointers now point at real things. We have a variable, a pointer holding its address, and a safe way to say “nothing here”. The final lecture in this part of the free C tutorial follows the pointer to reach the target, which is called dereferencing. It is the step that finally lets you read and change a variable without using its name. As practice, write a program with three int variables and one pointer, and make the pointer visit each variable in turn, printing its address each time.
Frequently Asked Questions
How do I assign an address to a pointer in C?
Use the address operator on a variable of the matching type: ptr = &variable;.
What is a wild pointer?
A wild pointer is one that was declared but never assigned, so it holds an unknown address. Following it is unsafe.
What does NULL mean in C?
NULL is the null pointer constant. A pointer set to NULL is guaranteed not to point to any valid object or function.
Can I assign a float address to an int pointer?
You should not. The types are incompatible. GCC warns about it, and since GCC 14 it is an error by default.
What happens when I copy one pointer to another?
Only the address is copied. Both pointers then point to the same variable, so a change made through one is visible through the other.
Does dereferencing NULL always crash?
No. On Linux it normally causes a segmentation fault, but on many bare-metal microcontrollers address 0 is valid memory, so it may not fault at all. Always check for NULL yourself.
Is NULL the same as 0?
Assigning the constant 0 to a pointer produces a null pointer, but writing NULL is clearer and is the recommended style.
Next in the Free C Programming Course
Time to follow the pointer: dereferencing, the indirection operator, and reading and writing variables through their addresses, then a quiz and interview questions.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

1 Comment