C Pointers
A pointer in C is a variable that stores a memory address. Instead of holding an ordinary value like 42, it holds the location where a value can be found.
Pointers matter because they let C programs share data without copying it, let functions change caller variables, and form the basis of arrays, strings, dynamic memory, and many data structures.
Overview: How Pointers Work
Every object in a running C program lives somewhere in memory. A variable name such as score is the convenient source-code name for one object; the expression &score asks C for that object’s address. A pointer variable stores such an address.
A pointer has its own type. If int *p stores the address of an int, then dereferencing p with *p tells C to treat the bytes at that address as an int. A double * points to a double, a char * points to a char, and a struct Student * points to a struct Student. The pointer type matters because different types can have different sizes, alignment requirements, and interpretations.
A pointer variable is still just a variable. It has an address of its own, and its stored value can be changed to point somewhere else. This distinction is important: assigning to the pointer changes where it points, while assigning through the pointer changes the object it points at.
The most important safety rule is simple: only dereference a pointer when it points to a valid object of the correct type. Dereferencing an uninitialized pointer, a NULL pointer, or a pointer to an object whose lifetime has ended causes undefined behavior. Undefined behavior means the C standard no longer promises what the program will do.
Syntax
type *pointer_name = &object;
value = *pointer_name;
*pointer_name = new_value;
| Syntax | Meaning |
|---|---|
int *p; |
Declare p as a pointer to int. |
&x |
Get the address of x. |
*p |
Dereference p: access the object at the stored address. |
p = &y; |
Make p store the address of y. |
*p = 10; |
Store 10 into the object pointed to by p. |
p == NULL |
Test whether p points to no object. |
Whitespace around the asterisk is mostly style. Many C programmers write int *p because it reads as “p points to an int.” Be careful with multiple declarations: int *a, b; makes only a a pointer; b is a plain int.
Examples
Read And Change A Variable Through A Pointer
#include <stdio.h>
int main(void)
{
int score = 70;
int *score_ptr = &score;
printf("Before: %d\n", score);
printf("Through pointer: %d\n", *score_ptr);
*score_ptr = 95;
printf("After: %d\n", score);
printf("Same address: %s\n", score_ptr == &score ? "yes" : "no");
return 0;
}
Output:
Before: 70
Through pointer: 70
After: 95
Same address: yes
The pointer score_ptr stores the address of score. Reading *score_ptr gets the value at that address. Assigning to *score_ptr changes the original score, not a copy.
Use Pointers To Let A Function Modify Values
#include <stdio.h>
void swap_ints(int *left, int *right)
{
int temporary = *left;
*left = *right;
*right = temporary;
}
int main(void)
{
int first = 12;
int second = 40;
printf("Before: first=%d second=%d\n", first, second);
swap_ints(&first, &second);
printf("After: first=%d second=%d\n", first, second);
return 0;
}
Output:
Before: first=12 second=40
After: first=40 second=12
C passes function arguments by value, so a function normally receives copies. By passing addresses, swap_ints can dereference its pointer parameters and update the caller’s original variables.
Work With A Struct Through A Pointer
#include <stdio.h>
struct Account {
int id;
double balance;
};
void deposit(struct Account *account, double amount)
{
account->balance += amount;
}
int main(void)
{
struct Account checking = { 101, 125.50 };
struct Account *account_ptr = &checking;
printf("Account %d: $%.2f\n", account_ptr->id, account_ptr->balance);
deposit(account_ptr, 24.50);
printf("Account %d: $%.2f\n", checking.id, checking.balance);
return 0;
}
Output:
Account 101: $125.50
Account 101: $150.00
The -> operator is shorthand for dereferencing a struct pointer and then selecting a member. account_ptr->balance means the same thing as (*account_ptr).balance, but it is easier to read and avoids precedence mistakes.
Check For NULL Before Dereferencing
#include <stdio.h>
void print_value(const int *value_ptr)
{
if (value_ptr == NULL) {
printf("No value\n");
return;
}
printf("Value: %d\n", *value_ptr);
}
int main(void)
{
int count = 3;
print_value(&count);
print_value(NULL);
return 0;
}
Output:
Value: 3
No value
NULL is used when a pointer intentionally points to no object. This example also uses const int *, which means the function may read the pointed-to int but should not modify it through that pointer.
How It Works Step By Step
- The statement
int score = 70;creates anintobject and stores70in it. - The expression
&scoreproduces the address of that object. - The declaration
int *score_ptr = &score;creates a pointer variable and stores that address in it. - When C evaluates
*score_ptr, it looks at the address stored insidescore_ptr, goes to that memory location, and accesses anintthere. - When C evaluates
*score_ptr = 95;, it stores95into that memory location. Since that location belongs toscore, readingscoreafterward gives95.
The compiler uses the pointer’s type to generate the right memory access. On many systems, an int is four bytes and a double is eight bytes, but portable C code should use sizeof instead of assuming exact sizes.
Common Mistakes
Dereferencing Before Initialization
A declaration such as int *p; does not automatically make p point to a useful place. It has an indeterminate value until you initialize it. The corrected pattern is to initialize the pointer with a real address or with NULL and check it before use.
Changing The Pointer Instead Of The Pointed-To Value
If p already points to x, the assignment p = &y; makes it point to y. The assignment *p = 10; changes the object it points to. Mixing those up is a common source of bugs.
Forgetting Parentheses With Struct Pointers
The expression *account_ptr.balance is not the same as (*account_ptr).balance. Member access binds tightly, so use account_ptr->balance for struct pointers.
Best Practices
- Initialize pointers when you declare them, either to a valid address or to
NULL. - Check for
NULLbefore dereferencing whenNULLis an allowed input. - Keep pointer types matched to the objects they point at.
- Use
constin pointer parameters when a function should read but not modify the pointed-to object. - Prefer one pointer declaration per line, such as
int *left;andint *right;, to avoid confusing mixed declarations. - Do not return the address of a local automatic variable; it stops existing when the function returns.
- Use clear names such as
score_ptroraccountso readers know whether a variable is a pointer.
Practice Exercises
- Write a function
void add_bonus(int *score, int bonus)that increases the caller’s score by the bonus amount. - Create two
doublevariables, point onedouble *at the first, print through the pointer, then make it point at the second and print again. - Define a
struct Bookwith a title length and page count. Write a function that receives astruct Book *and increases the page count.
Summary
- A pointer stores a memory address.
&gets the address of an object, and*dereferences a pointer.- The pointer’s type tells C what kind of object is expected at the stored address.
- Pointer parameters let functions modify caller variables.
->is the standard way to access struct members through a pointer.- Only dereference pointers that are initialized, valid, and appropriate for the object they point to.
