Pointers
Topics
Pointer Introduction
What is a Pointer?
- Pointers store a memory location
- “pointer” = these “point” to a memory location.
- All pointers have the same size:
- 64-bit machine: 8-bytes (64-bits)
- 32-bit machine: 4-bytes (32-bits)
Pointer Syntax
- Declaring
- A pointer is marked by an asterisk (
*
)
1
2
3
|
int *my_pointer
// An integer resides at the memory address this points to.
// Asterisk denotes this variable stores a memory address. (i.e., it is a pointer)
|
Address-of Syntax
- Address-of
- Gets the memory address of a variable.
- Use: ‘
&
’ – The “address-of” operator
1
2
3
|
int k = 22;
cout << k; // Prints 22 (value of integer k)
cout << &k; // Prints memory address of k
|
Using Pointers
- We use the
*
in 2 ways: to declare pointers, & dereference
1
2
3
4
5
6
7
8
9
10
11
|
int k;
int *ptr_k; // When declaring: Asterisk makes a pointer
ptr_k = &k; // Puts memory address of k into pointer ptr_k
*ptr_k = 771;
// Using the * when getting/setting is dereference operator.
// This puts the value 771 into the variable located by ptr_k.
printf("%d\n", k); // Prints 771
|
Dynamically Allocating Memory
Allocating New Memory
- The new keyword creates a new dynamic variable of a specified type and returns a pointer that points to this new variable.
1
2
3
4
5
6
7
|
int *ptr; // Declaring a pointer
ptr = new int; // Creates a new ”nameless” integer in memory that we can only access via pointer
*ptr = 60; // Dereference ptr to give that new integer a value.
printf("%d\n", *ptr); // Prints 60
|
Allocating New Memory: Objects
- This is using
new
with a class (the only difference is we are using the class constructor with the new call, like Java)
1
2
3
4
5
|
Dog *pup; // Declaring a pointer to a Dog type
pup = new Dog(); // Creates a new ”nameless” Dog in memory using the constructor that we can only access via pointer
(*pup).age = 10; // Dereference ptr to give access to the object, the use dot operator to access a public field or method
|
Allocating New Memory: Arrays
- This is using
new[]
with an array – just like the normal new call, only we allocate the size of data type of the array *
the number of elements given by the new call.
1
2
3
4
5
6
|
int *ptr_arr; // Declaring an integer pointer
ptr_arr = new int[5]; // Creates 4 new contiguous integers in memory and returns a pointer to the first one. Allocated sizeof(int) * 5 = 4 bytes * 5 = 20 bytes.
ptr_arr[2] = 10; // [] automatically dereferences the pointer for us. We can use the int pointer just like an array.
*(ptr_arr + 2) = 10; // This is equivalent to the statement above. See the pointer arithmetic section.
|
De-allocating New Memory
- The delete keyword destroys a dynamic variable of a specified type and returns that memory to the freestore to be used by other parts of the program if needed.
1
2
3
4
5
6
7
|
*ptr = 70; // Same pointer from above
delete ptr; // Destroys the “nameless” variable that held 60 then 70.
*ptr = 60; // DON’T DO THIS! To re-use ptr again, we would need to make another new call. We are trying to reference a non-NULL pointer that is in free memory.
*ptr = NULL; // This gives us a seg fault now if we try to access ptr – which helps us find our bugs.
|
De-allocating Memory: Objects
- The delete keyword also destroys objects – used just like above. All of the consequences after the “delete” call are the same.
1
2
3
4
5
6
7
|
(*pup).age = 5; // Same pointer from previous above
delete pup; // Destroys the “nameless” object of type Dog.
(*pup).age = 7; // DON’T DO THIS! Same reasons as before
*pup = NULL; // This gives us a seg fault now if we try to access pup – which helps us find our bugs.
|
De-allocating Memory: Arrays
- This is using
delete[]
with an array – just like the normal delete call, only we make sure we destroy all the elements in the array.
1
2
3
4
5
6
|
int *ptr_arr; // Declaring an integer pointer
ptr_arr = new int[4]; // Creates 4 new contiguous integers in memory and returns a pointer to the first one
ptr_arr[2] = 10; // [] automatically deferences the pointer for us. We can use the int pointer just like an array.
*(ptr_arr + 2) = 10; // This is equivalent to the statement above. See the pointer arithmetic section.
|
Pointer Arithmetic
-
We can use the subscript operator (brackets []) to treat a pointer like an array
- Adding and subtracting from a pointer walks us to the next data type (of the pointer) in contiguous memory.
- It does NOT increase/decrease by a single byte, unless the data type is a char (chars are 1 byte).
- This is analogous to moving to the next element in the array.
1
2
|
int *a = (int *)0x64; // 100 in hex
cout << (a + 10);
|
- When adding to a pointer:
- Formula:
- $result = current + offset \times data\_size$
- $current = 100, offset = 10, data\_size =4$
- $result = 100 + 10 \times 4 = 140 =$
0x8C
1
2
3
4
5
6
7
8
|
int array[] = {1, 2, 3, 4, 5};
int *ptr_a = array + 1;
cout << *(ptr_a + 1);
// ptr_a = array + 1 (4) = array + 4 = &2
// ptr_a + 1 = &2 + 1 (4) = &2 + 4 = &3
// *(ptr_a + 1) = *(&3) = 3
|
Subscript on a Pointer
1
2
|
int *k = (int *)100;
k[10] = 30;
|
- Subscript automatically dereferences!!
- k[10] is equivalent to *(k + 10)
Pointers with Classes
- Pointers can point to any data type in memory. String is a class that’s defined in
#include <string>
, so we’ll use it as an example.
1
2
3
4
5
|
string somestring = "Hello";
string *ptrstring = &somestring;
cout << ptrstring; // prints address
cout << *ptrstring; // prints value
|
Class Members w/ Dot Operator
- You must dereference the pointer to access class members.
1
2
3
4
|
string somestring = "Hello";
string *ptrstring = &somestring;
cout << (*ptrstring).length();
|
Arrow Operator – Easier!
- You can use the arrow operator (
->
) instead of the dot operator to save you time! It will deference the pointer for you, and it is cleaner syntax.
1
2
3
4
5
6
|
string somestring = "Hello";
string *mystring = &somestring;
cout << (*mystring).length();
//IS EQUIVALENT TO
cout << mystring->length();
|
Pointers as Function Parameters
- The next two sections show a function that has a one-line body.
- The first section is DE-REFERENCING an int pointer parameter, therefore changing an integer.
- The second section is assigning a new address to the pointer. You won’t do this all that often in code. This one is tricky since C++ is pass-by-value by default, so it will only change the address within the scope of the function ONLY. We won’t have examples of this in class or exams, but this is just to show you in case you come across it coding on your own.
DE-REFERENCING
In the main program, we are calling the function "func"
with the address of “j”. Since func
takes a single address as a parameter, this works just fine.
The function “func
” will deference the parameter and change its value to 22. The is changing the value at the ADDRESS OF J.
When we come back to main, j is now 22.
Pass-by-reference is a way to avoid using pointer syntax, but getting the same result. To change this to a pass-by-reference integer parameter, we would code everything up as so:
1
2
3
4
5
6
7
8
9
10
|
void func(int *i)
{
*i = 22;
}
int main()
{
int j = 55;
func(&j);
cout << j;
}
|
ASSIGNING A NEW ADDRESS TO THE POINTER
- The actual pointer is pass-by-value
This is the tricky one, in my opinion. The difference between this program and the slide previous is that the one-liner body is changing the DE-REFERNCED address, and the one-liner body in this slide is changing the ADDRESS ITSELF.
Since C++ is pass-by-value by default, this will pass a copy of j (think j_copy = 0x200
) to the function “func
”. J_copy is “plugged in” for the parameter i, we change i to 0x100
, then j_copy gets destroyed after the function is over.
When we come back to main, we are using OG j again, and it’s still 0x200
.
1
2
3
4
5
6
7
8
9
|
void func(int *i)
{
i = (int *)0x100;
}
int main() {
int *j = (int *)0x200;
func(j);
cout << j;
}
|
Pointer Constants
- Three ways to specify “constant”
- const before the *
-
const int *p;
- Makes the VALUE at the memory address being pointed to constant.
- const after the *
-
int * const p;
- Makes the MEMORY ADDRESS being pointed to read-only (cannot update the memory address)
- const before and after the *
-
const int * const p;
- Makes the MEMORY ADDRESS pointed to constant and the VALUE at the memory address being pointed to constant.