Pointers

A pointer is a variable that stores the memory address of another variable. In languages like C and C++, pointers are commonly used for dynamic memory management, arrays, and data structures.
Declaration and Initialization:
int a = 10;
int *p = &a; // p now points to a
Pointer Arithmetic
Pointer arithmetic allows you to navigate through memory addresses based on the type of data the pointer points to.
Incrementing a Pointer: When you increment a pointer, it moves to the next memory address based on the size of the type it points to.
//int pointers
int arr[] = {10, 20, 30};
int *p = arr; // p points to arr[0]
p++; // p now points to arr[1]
//char pointers
char str[] = "Hello";
char *p = str; // p points to the first character 'H'
printf("%c\n", *p); // Output: H
p++; // Increment the pointer to point to the next character
printf("%c\n", *p); // Output: e
p++; // Increment again
printf("%c\n", *p); // Output: l
Subtracting Pointers: If you subtract one pointer from another (of the same type), it returns the number of elements between them.
int *p1 = &arr[0];
int *p2 = &arr[2];
int diff = p2 - p1; // diff is 2
Common Pitfalls
Null Pointers: A null pointer does not point to any valid memory location. dereferencing a null pointer leads to undefined behavior.
int *p = NULL; // *p = 5; // This will cause a runtime error.
Dangling Pointers: A dangling pointer points to a memory location that has been freed or de-allocated. This can happen after using free()
on dynamically allocated memory.
int *p = malloc(sizeof(int));
free(p);
// p is now a dangling pointer.
//to avoid the dangling pointer scenario
//case:1
int* ptr = (int*)malloc(sizeof(int));
*ptr = 42;
free(ptr); // Free the allocated memory
ptr = NULL; // Set the pointer to nullptr
if (ptr) { // Check if ptr is valid before dereferencing
std::cout << *ptr; // This won't execute
}
//case:2 Avoid Returning Pointers to Local Variables
int* createArray() {
int arr[10]; // Local variable
return arr; // Dangerous: arr will be destroyed after function returns
}
//Instead, allocate memory on the heap and then return:
int* createArray() {
int* arr = new int[10]; // Allocate on heap
return arr; // Safe to return
}
Memory Leaks: A memory leak occurs when you allocate memory but forget to free it, leading to wasted memory resources. This is common in long-running applications.
int *p = malloc(sizeof(int));
// Do something with p
// free(p); // If you forget this, it's a memory leak.
//in cpp
int* ptr = new int; // Allocate memory
*ptr = 42;
// ... use ptr ...
delete ptr; // Free memory
//For arrays:
int* arr = new int[10]; // Allocate array
// ... use arr ...
delete[] arr; // Free memory
The new
operator for a single object constructs the object and returns a pointer to it. When you call delete
, it invokes the destructor (if any) and then frees the memory.
on the other hand new[]
operator allocates memory for an array and constructs each element. When you call delete[]
, it invokes the destructors for each element in the array (if applicable) and then frees the entire block of memory.
Smart Pointers
Smart pointers are a safer alternative to raw pointers that manage the lifetime of dynamically allocated objects. They automatically release the memory when it is no longer needed, helping to prevent memory leaks.
Types of Smart Pointers in C++:
std::unique_ptr
: Ensures a unique ownership of the managed object. It cannot be copied, only moved.std::shared_ptr
: Allows multipleshared_ptr
instances to share ownership of the same dynamically allocated object. The object is destroyed when the lastshared_ptr
is destroyed.std::weak_ptr
: Works withstd::shared_ptr
to break cyclic dependencies by providing a non-owning reference.
Examples:
std::unique_ptr
:
#include <memory>
void uniquePtrExample() {
std::unique_ptr<int> ptr = std::make_unique<int>(10);
std::cout << *ptr << std::endl; // Access the managed object
}
std::shared_ptr
:
#include <memory>
void sharedPtrExample() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
std::shared_ptr<int> ptr2 = ptr1; // Shared ownership
std::cout << *ptr2 << std::endl;
}
Scenarios for std::shared_ptr
:
Shared Ownership: When an object needs to be accessed by multiple parts of a program, such as shared data structures (graphs, trees).
Factory Functions: When you want to return a shared resource from a function.
Polymorphic Objects: When dealing with polymorphism, and you need to manage the lifetime of base class pointers.
std::weak_ptr
:
#include <memory>
#include <iostream>
void weakPtrExample() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(30);
std::weak_ptr<int> weakPtr = sharedPtr; // Does not affect reference count
if (auto sp = weakPtr.lock()) {
std::cout << *sp << std::endl; // Access the shared object if still available
}
}
Subscribe to my newsletter
Read articles from Shashi Shekhar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
