C++ Pointers Explained: Understand “Hard” Pointers in 5
이 글의 핵심
Learn C++ pointers using a simple address analogy: core ideas, hands-on examples with code, patterns you will see in practice, and mistakes to avoid.
Think of pointers as street addresses
Analogy: A pointer is like a street address.
variable = house (where the data actually lives)
pointer = address (information that says where the house is)
Core concepts
Variables and addresses
int age = 25; // variable: stores a value
int* ptr = &age; // pointer: stores an address
// &age: take the address of age
// ptr: a pointer that stores age's address
Picture it:
Address Name Value
0x1000 age 25
0x2000 ptr 0x1000 (address of age)
Declaring pointers
Example C/C++ code:
int* ptr; // pointer to int
double* ptr2; // pointer to double
char* ptr3; // pointer to char
// Asterisk placement is style-only:
int *ptr; // same meaning
int * ptr; // same meaning
Address-of operator (&)
Example C/C++ code:
int x = 10;
int* ptr = &x; // store x's address in ptr
std::cout << x; // 10 (value)
std::cout << &x; // 0x7fff.... (address)
std::cout << ptr; // 0x7fff.... (address, same as &x)
Dereference operator (*)
Example C/C++ code:
int x = 10;
int* ptr = &x;
std::cout << *ptr; // 10 (value ptr points to)
*ptr = 20; // change the value where ptr points
std::cout << x; // 20 (x changed!)
Hands-on examples
Example 1: swapping values (swap)
Implementation sketch for swap_wrong vs swap:
// Wrong: copies values only
void swap_wrong(int a, int b) {
int temp = a;
a = b;
b = temp;
// When the function returns, a and b are back to the originals!
}
// Correct: use pointers
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap_wrong(x, y);
std::cout << x << ", " << y; // 10, 20 (unchanged)
swap(&x, &y);
std::cout << x << ", " << y; // 20, 10 (swapped!)
}
Example 2: arrays and pointers
Example C/C++ code:
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr; // array name is the address of the first element
std::cout << *ptr; // 1 (first element)
std::cout << *(ptr+1); // 2 (second element)
std::cout << ptr[2]; // 3 (third element)
Common mistakes
Mistake 1: uninitialized pointer
Example C/C++ code:
// Dangerous
int* ptr;
*ptr = 10; // we don't know where this points! (crash)
// Correct
int* ptr = nullptr; // points to nothing
// or
int x;
int* ptr = &x; // points to x
Mistake 2: confusing pointer and value
Example C/C++ code:
int x = 10;
int* ptr = &x;
// Wrong
ptr = 20; // compile error! (assigning a value to an address slot)
// Correct
*ptr = 20; // change the value ptr points to
Mistake 3: dangling pointer
Example C/C++ code:
// Dangerous
int* ptr;
{
int x = 10;
ptr = &x;
} // x is gone
*ptr = 20; // use-after-free of a dead variable! (crash)
// Better: dynamic allocation
int* ptr = new int(10);
*ptr = 20;
delete ptr; // release when done
Pointers vs references
Example C/C++ code:
// Pointer
int x = 10;
int* ptr = &x;
*ptr = 20; // must dereference
// Reference (often simpler)
int& ref = x;
ref = 20; // no dereference
Tip: Beginners often learn references first.
Dynamic memory allocation
new and delete
Example C/C++ code:
// Single object
int* ptr = new int; // allocate
*ptr = 10; // store value
delete ptr; // free
// With initial value
int* ptr2 = new int(25);
delete ptr2;
// Array
int* arr = new int[100]; // array of 100 ints
arr[0] = 1;
delete[] arr; // array delete ([] required!)
Watch for memory leaks
Example leak vs no_leak:
// Leak
void leak() {
int* ptr = new int(10);
// forgot delete!
} // ptr goes away but the memory stays allocated
// Correct
void no_leak() {
int* ptr = new int(10);
// ... use ...
delete ptr; // always pair with new
}
Everyday analogy: Think of memory as an apartment building. The stack is like a fast elevator—quick but limited space. The heap is like a warehouse—roomy but takes longer to “fetch” from. A pointer is a sticky note that says “3rd floor, unit 302”—it records an address, not the furniture inside.
Pointer arithmetic
Example C/C++ code:
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;
std::cout << *ptr; // 10
std::cout << *(ptr+1); // 20
std::cout << *(ptr+2); // 30
// Increment the pointer
ptr++;
std::cout << *ptr; // 20
// Array-style indexing
ptr[0]; // 20
ptr[1]; // 30
Why we use pointers
1. Changing values inside a function
Example increment:
void increment(int* num) {
(*num)++;
}
int main() {
int x = 10;
increment(&x);
std::cout << x; // 11 (changed)
}
2. Passing large data efficiently
Example process:
// Inefficient: copies the whole vector
void process(std::vector<int> data) {
// ...
}
// Efficient: pass pointer
void process(std::vector<int>* data) {
// ...
}
// Often better: const reference
void process(const std::vector<int>& data) { // recommended
// ...
}
3. Arrays whose size is unknown at compile time
Example C/C++ code:
int n;
std::cin >> n;
// Size not known at compile time
int* arr = new int[n]; // size decided at runtime
// ... use ...
delete[] arr;
FAQ
Q1: Why do people say pointers are hard?
A: The idea is simple, but small mistakes often crash the program.
Why it feels hard:
- Uninitialized pointer → crash
- Missing
delete→ leak - Double
delete→ crash - Use-after-free → crash
Mitigation: use smart pointers in modern C++:
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(10);
// delete happens automatically (safer)
Q2: What is nullptr?
A: A special value meaning “points to nothing.”
int* ptr = nullptr; // points to nothing
if (ptr == nullptr) {
std::cout << "Pointer is empty" << std::endl;
}
// Check before use (safe)
if (ptr != nullptr) {
*ptr = 10;
}
Note: Prefer nullptr (C++11) over the old NULL macro.
Q3: How do pointers relate to arrays?
A: An array name is the address of its first element.
Example C/C++ code:
int arr[5] = {1, 2, 3, 4, 5};
// Array name = address of first element
int* ptr = arr; // same as &arr[0]
// Equivalent forms
arr[2]; // 3
ptr[2]; // 3
*(arr+2); // 3
*(ptr+2); // 3
Q4: What is a double pointer?
A: A pointer that points to another pointer.
Example C/C++ code:
int x = 10;
int* ptr = &x; // address of x
int** ptr2 = &ptr; // address of ptr
std::cout << x; // 10
std::cout << *ptr; // 10
std::cout << **ptr2; // 10
// ptr2 → ptr → x
When it shows up:
- Dynamic 2D arrays
- Functions that must change a pointer value
- Advanced structures (trees, graphs)
Q5: Can I learn C++ without raw pointers?
A: In modern C++, largely yes.
Instead of raw new/delete:
Example C/C++ code:
// Old style
int* ptr = new int(10);
delete ptr;
// Modern: smart pointer
auto ptr = std::make_unique<int>(10);
// automatic cleanup
// Reference (often simpler)
int x = 10;
int& ref = x;
ref = 20;
Suggested order:
- References
- Smart pointers
- Raw pointers when you actually need them
Q6: When must I still use raw pointers?
A: Typical cases:
Often required:
- C libraries (OpenGL, SDL, etc.)
- Systems programming
- Embedded development
- Maintaining legacy code
Often avoidable:
- Typical apps: references and smart pointers
- Games: mostly smart pointers
- Web servers: references often enough
Takeaway: In modern C++, you need fewer raw owning pointers in application code.
Deep dive: const pointer vs pointer to const (often confused at work)
int x = 1;
const int* p1 = &x; // pointee is const (can't change *p1 through p1)
int* const p2 = &x; // pointer itself is const (can't repoint p2)
const int* const p3 = &x; // both const
Reading tip: Many teams read declarations as: const before * → “what is pointed to”, const after * → “the pointer itself.”
Deep dive: void*, alignment, and uintptr_t
When you touch C APIs you will see void*. In C++, cast back to the correct type with static_cast before dereferencing, and use uintptr_t (<cstdint>) when you treat an address as an integer.
std::byte buffer[64];
auto* p = new (buffer) int(42);
// Bad idea: casting arbitrary addresses to int* can break alignment / strict aliasing rules
Performance: A single dereference is usually one or two cycles, but cache misses, virtual calls, and synchronization often dominate. Before blaming “pointers are slow,” look at memory access patterns.
Deep dive: debugging (GDB and sanitizers)
| Situation | Tool |
|---|---|
| Random crashes | AddressSanitizer (-fsanitize=address) |
| Suspected heap corruption | ASan + -fsanitize=undefined (where supported) |
| Leaks | LeakSanitizer (with ASan), Valgrind |
| “Where did it break?” | GDB watch on *ptr, bt |
// GDB: when you doubt whether a pointer still points to a valid object
// print ptr
// x/4wx ptr (memory dump — invalid ptr can segfault)
Deep dive: more risky patterns
| Pattern | Why it is risky | Alternative |
|---|---|---|
Double delete | Undefined behavior | Set to nullptr after delete, or use smart pointers |
Old iterators after vector reallocation | Invalidation | Use indices or reserve to stabilize ranges |
reinterpret_cast abuse | Strict aliasing issues | std::bit_cast (C++20) or redesign |
| C-style varargs + pointers | Type mismatches | Prefer modern APIs |
Deep dive: mini example — linked list node (educational)
struct Node {
int value{};
Node* next{nullptr};
};
void push_front(Node*& head, int v) {
auto* n = new Node{v, head};
head = n;
}
void free_list(Node* head) {
while (head) {
Node* next = head->next;
delete head;
head = next;
}
}
In production you would usually use std::unique_ptr<Node>. The snippet shows why rules matter when a pointer implies ownership.
Related reading (internal links)
Posts that connect to this topic:
- C++ stack vs heap: recursion crashes, memory layout, RAII and smart pointers
- C++ observer pointer guide
- [C++ functions: beginner guide with examples](/en/blog/cpp-function-basics/
Related posts
- [C++ classes and objects](/en/blog/cpp-class-object-beginner/
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [C++ Memory Management: new/delete, Stack vs Heap, and RAII](/en/blog/cpp-memory-management-deep/
- [C++ Stack vs Heap](/en/blog/cpp-series-06-1-memory-basics/
- [C++ Code Review | ](/en/blog/cpp-code-review-checklist/
이 글에서 다루는 키워드 (관련 검색어)
C++, pointers, address, dereference, beginner, tutorial 등으로 검색하시면 이 글이 도움이 됩니다.