Pointers
Warning
Pointers are quite dangerous, spawning vulnerabilities like buffer overflows.
Mechanism used to refer to memory addresses. Pointers encode the object's address and object's type.
Declaring a pointer
For example, to declare a pointer to a pointed-to type of int
:
int* counter;
Address-of operator (&)
For any given variable, to get the memory address of that variable, use the address-of (&) operator
// declare and initialize a normal int variable
int counter{22};
printf("The counter is %d", counter);
// Declare and initialize an int pointer that "points" to the address of `counter` variable
int* counter_ptr = &counter;
printf("Counter address is at %p", counter_ptr);
Note
The length of the value of the pointer on x86 (32-bit, 8 hex) systems is
4 bytes whereas on x64 (64-bit, 16 hex) systems. The size of a pointer depends
on the size of the machine's CPU general purpose register (i.e. eax
)
Dereferencing operator (*)
The dereferencing operator (*
) is a unary operator that retrieves the object to which
the pointer points to. (The inverse of the address-of operator)
int counter{22};
int* counter_address = &counter;
// Get the value of counter via it's pointer
printf("The value of counter: %d\n", *counter_address); // -> 22
printf("counter address is: %p\n", counter_address);
// Modify the value of an object via dereferenced pointer
*counter_address = 44;
printf("The value of counter: %d\n", *counter_address); // -> 44
printf("counter address is: %p\n", counter_address); // same as last address
Note
Think of dereferencing as "get me the value of the object, not its memory address"
Member-of-Pointer operator (->)
The member-of-pointer operator does two operations:
- It dereferences a pointer
- It accesses a member of the pointed-to object
struct Phone {
bool blocked{false};
};
int main() {
Phone phone{};
Phone *phone_ptr = ☎
printf("Is phone blocked: %d\n", phone_ptr->blocked);
// Use the member-of-operator to access the object and modify its member
phone_ptr->blocked = true;
printf("Is phone blocked: %d\n", phone_ptr->blocked);
// The hard way - dereference and access
printf("Is phone blocked: %d\n", (*phone_ptr).blocked);
}
Pointers and arrays
Pointers and arrays have a lot of similarities. Pointers encode object addresses, whereas arrays encode the location and length of contiguous objects.
An array can decay into a pointer very easily. A decayed array loses length information and becomes a pointer to the first element in the array.
int my_array[]{ 1, 2, 3, 4, 5};
int* my_array_ptr = my_array;
Here's an example of the ease in which an array can decay into a pointer:
struct Book {
int year{};
Book(int year) {
this->year = year;
}
};
void print_year(Book* book_ptr) {
printf("Book year: %d\n", book_ptr->year);
}
int main() {
Book books[]{ Book(1999), Book(2000), Book(2001)};
print_year(books);
}
Pointer arithmetic
Note
Pointer arithmetic is the set of rules for addition and subtraction on pointers
To get the n-th element of an array, you have to perform some pointer arithmetic.
Two options:
First approach is with square brackets with address-of (&) operator
Book* third_book_ptr = &books[2];
Second approach is to use arithmetic
Book* third_book_ptr = books + 2;
void and std::byte Pointers
When the pointed-to type is irrelevant, you can use the void*
pointer. It can
never be dereferenced since there is no underlying type to point to.
void pointer arithmetic is forbidden, impossible for a void pointer to have any offsets.
When you want to interact with raw memory at byte level, like copying data between files
you can use a std::byte
pointer
nullptr and Booleans
Pointers can have a special value of nullptr
-- a pointer that doesn't point
to anything. Used for indicating that there is no more memory left to allocate
Pointers have an implicit conversion to bool
-- any pointer that points to something
is true
while a nullptr is false