I know * defines a pointer... does ** define a pointer to a pointer?
If so, why?
Is a pointer to a pointer some times known as a reference? Just need clarification for the following very simple hash.
Generally pointers are used to pass the location of larger structures when passng the entire contents would be too costly.
I've seen pointers to pointers used in the quantlib project to create "handles" as each "observer" holds a pointer to a "term structure" pointer that might change at run-time, hence the pointer held the location of another pointer.
However I see no correlation here?
class hash_entry
{
private:
int key;
int value;
public:
hash_entry(int key, int value)
{
this->key = key;
this->value = value;
}
int getKey()
{
return key;
}
int getValue()
{
return value;
}
};
class hash_map
{
private:
hash_entry **table;
static const int TABLE_SIZE = 128;
public:
hash_map()
{
table = new hash_entry*[TABLE_SIZE];
for (int i = 0; i < TABLE_SIZE; i++)
table[i] = NULL;
}
int get(int key)
{
int hash = (key % TABLE_SIZE);
while (table[hash] != NULL && table[hash]->getKey() != key)
hash = (hash + 1) % TABLE_SIZE;
if (table[hash] == NULL)
return -1;
else
return table[hash]->getValue();
}
void put(int key, int value)
{
int hash = (key % TABLE_SIZE);
while (table[hash] != NULL && table[hash]->getKey() != key)
hash = (hash + 1) % TABLE_SIZE;
if (table[hash] != NULL)
delete table[hash];
table[hash] = new hash_entry(key, value);
}
~hash_map()
{
for (int i = 0; i < TABLE_SIZE; i++)
if (table[i] != NULL)
delete table[i];
delete[] table;
}
};
Yes, ** defines a pointer to a pointer (because the spec says so). No, I can't quite imagine anybody calling that a reference.
As to why they're using it in this case, they're writing (very C-like) code to dynamically allocate an array of pointers to X. This code:
hash_entry **table;
[ ... ]
hash_map() {
table = new hash_entry*[TABLE_SIZE];
Is roughly equivalent to:
std::vector<hash_entry *> table(TABLE_SIZE);
(though for the moment, I haven't split it up as you'd need to for a class member).
Yes, ** is a pointer to a pointer, but no, that's not the same as a reference. In this context, it's being used to create a dynamically-allocated two-dimensional array of hash_entry.
By the way, are you sure this code compiles? I see some things that appear to be syntax errors.
You are correct that ** refers to a pointer to a pointer (though incorrect that this is a reference - references are denoted with the ampersand - int & foo, for example).
In this case, it is being used not in the way you have described (as "handles"), but as an array of pointers to hash_entrys
The line:
table = new hash_entry*[TABLE_SIZE];
means "allocate a block of memory to hold hash_entry pointers, of size TABLE_SIZE * sizeof(hash_entry*). From there, the array is initialized to NULL, after which you can fill each entry in the array with a pointer as needed.
Yes, ** represents a pointer to a pointer.
One reason to add that extra level of indirection is so that you can move the thing ultimately pointed to around in physical memory without the code pointing to that something needing to be directly informed of the new address.
If a memory manager wants to move something in memory (for example, to reduce holes in a heap), it can allocate new memory, move the object, and update the address pointed to by the pointer-pointer to point to that new address.
Specifically in this case, if you have more hash entries than TABLE_SIZE, the code could reallocate hash_entry with a larger block of memory and update table with the new address of hash_entry. The rest of the code can be ignorant to the re-allocation.
I know * defines a pointer...does ** define a pointer to a pointer?
Yes.
If so why?
I'll assume you mean here why would someone use one, not how the types work (see any c reference for that). There are a few places I've seen and used them:
Dynamic array of pointers. (This is what we have here)
Dynamic 2-d arrays. (This is actually a subset of 1, a dynamic array where each element points to another dynamic array)
"Returning" a pointer via pointer passed as arg (*arg = ptr_to_return). See, e.g. getline()
Is a pointer to a pointer some times known as a reference? Just need clarification for the following very simple hash.
No, a reference is essentially passing by reference but letting the compiler keep track of the pointer for you.
Generally pointers are used to pass the location of larger structures when passng the entire contents would be too costly.
Yes, but not exclusively. Consider a method that changes a member of a struct. You need it to change YOUR copy of the struct, not the local copy it would have if you passed by value.
In your example here, hash_entry **table; is being used as a (dynamically sized) array of pointers to hash_entrys. It could, perhaps more clearly, be declared hash_entry *table[];
Related
This question already has answers here:
How do I declare a 2d array in C++ using new?
(29 answers)
Closed 1 year ago.
So I have a Node struct
struct Node
{
int x = 0;
};
I make 20 Node*s. My understanding is that Node** is a pointer to the start of an array that holds pointers to Nodes.
constexpr int mazeSize = 20;
Node** testMaze = new Node * [mazeSize];
After this, I started getting warnings and errors when I tried to do anything with it. Examples:
testMaze[0]->position.x == 0; //->Using uninitialized memory `*testMaze` and
What I understood from this error: *testMaze is dereferencing the array of pointers, which means it's referring to the first Node object in that array. If this is the case than how would I initialize it? If I simply created the Node* as so:
Node* node = new Node;
node->x = 0;
Than it works fine and there is no need to initialize it, so why not with the way I am doing it? I also don't understand how to initialize a struct.
Another examlpe:
testMaze[0]->x == testMaze[1]->x //->Runtime error: Access violation reading error
testMaze[0]->x = 0; //->Runtime error: Access violation writing error
How can I fix these problems? Thanks.
Problems:
constexpr int mazeSize = 20;
Node** testMaze = new Node *[mazeSize]; //this makes 20 pointers, but the pointers dont point to anything
testMaze[0]->position.x == 0; //undefined, as testMaze's pointers do not point to anything
This works because you make a new node, not a new pointer to a pointer to a node.
Node * node = new Node;
node->x = 0;
As for this:
testMaze[0]->x == testMaze[1]->x; //This is an undefined pointer, it doesn't point to anything, and you are trying to access it, so UNDEFINED behavior
testMaze[0]->x = 0; //This is a undefined pointer, it doesn't point to anything, and you are trying to access it, so UNDEFINED behavior
}
I would just do this:
Node** Make(int size) {
Node** temp = new Node * [size];
Node* pool = new Node[size];
for (int i = 0; i < size; ++i) {
temp[i] = &pool[i];
}
}
This makes your array of pointers, makes your pointers point to actual pointers which point to actual values.
Also, don't forget to delete[] your Node**s and Node*s, or you'll have a memory leak!
Always remember that pointer types are separate, concrete types with their own type and size. A pointer to T T* basically boils down to a small chunk of memory that is used to store an address to another memory area, which (hopefully) contains an instance of some type T.
With
Node** testMaze = new Node * [mazeSize];
you are allocating an array of maxSize pointer sized elements of type Node* and sizeof(Node*) (which on modern platforms is usually 4 or 8 bytes, depending if your executable is meant to run in 32 bit or 64 bit mode).
These newly created pointers are not initialized, and thus point to invalid addresses, unless you also zero initialize the array:
Node** testMaze = new Node * [mazeSize] {};
assert (testMaze[0] == nullptr); // holds true
In order to get an maxSize instances of Node, you have to, well, create maxSize instances of Node:
Node** testMaze = new Node* [mazeSize];
for (std::ptrdiff_t i {}; i < maxSize; ++i) {
testMaze[i] = new Node { /* insert parameters here */ };
}
Given that you are using constexpr, I infer the revision of C++ you are targeting is C++11 or newer. In this case, you should be aware that operator new and operator new[] are almost always the wrong choice when writing modern C++, given that they only returns pointers whose ownership and lifetime you then have to manage by hand.
You most definitely should start using STL containers, such as std::vector and std::array, which are easier to use and can avoid you a great deal of unnecessary pain. If you insist on using new, at least give a look to std::unique_ptr, which wraps a pointer or C array and automatically calls delete or delete[] as soon as it goes out of scope.
Not exactly sure how to word the title but I'll explain as best I can.
I have a program that originally used a 2D array of a set size and so it was defined as:
typedef char Map[Row][Col];
I'm now trying to dynamically allocate memory for it and it has now also become of variable size based on input. It's now defined as:
typedef char** Map;
In my main method, I originally had:
Map map;
readUserInput(map);
Basically readUserInput takes the map array as a parameter, and assigns values to it based on user input. The map then contains values and is used in other functions.
I've updated the readUserInput function so that it dynamically sizes the array and it allocates/deallocates memory for it. This works fine, but the problem comes from the fact that now in the main method, map is not being updated. The above code in main now looks like:
Map map = nullptr;
readUserInput(map);
but after running the readUserInput function, map is still null. Inside of the function, map is updated fine, so I'm not understanding the difference made between the changes.
What you pass to function is a pointer to array and fuction can't change it. But replacing array with pointer to pointer is incorrect in most case.Pointer to pointer suggest that have a 1D array of pointers. Which may (or may not) point to other arrays. Such data organization sometimes referred to as jagged arrays, because it allows each row to be of separate length. But on practtice jagged arrays and their subclass, sparse matrices, usually implemented as 1D array to avoid re-allocation.
To avoid decaying and to actually store a monolithic array in memory, you should use 1d array and, preferably, encapsulation for pointer arithmetic and reallocation, and then pass reference to object that stores all required states. Reference ensures that object is mutable by function ( a smart-pointer-less version for an example):
class Map
{
int rows, cols;
char *data;
public:
Map() : rows(), cols(), data(nullptr) {}
Map(int r, int c) : rows(r), cols(c), data(new char[r*c]()) {}
~Map() { delete[] data; }
void resize(int r, int c) {
if(rows == r && cols == c) return;
char* tmp = new char[r*c]();
if(data)
{
// copy old data here if required
delete[] data;
}
row = r; col = c;
data = tmp;
}
char& operator() (int r, int c) { return data[r*cols + c]; }
char operator() (int r, int c) const { return data[r*cols + c]; }
};
NB: this class requires a copy and move operations to be implemented if any copy must be allowed.
The function prototype would be:
void readUserInput(Map& map);
With such class you can do dynamic resizing, store its size, and address element as simple as this:
int main()
{
Map test(4, 5); // declaring and allocating memory
test.resize(3,3); // reallocating
test(1,1) = 3; // writing
//reading
std::cout << +test(1,1) << std::endl;
}
The function should accept the array by reference in the C terms like
readUserInput( &map );
when the function is declared like
void readUserInput( Map *map );
or in the C++ terms when the function is declared like for example
void readUserInput( Map &map );
and called like
readUserInput(map);
Instead of allocating dynamically arrays you could use the container std::vector<std::string>.
The code you have used is a pure C-style code, and is prone to many mistakes:
You use typedef instead of: using Map = char**;
You use a function which gets a pointer and fills it, which is more common in C than in C++.
You use raw pointer instead of smart pointers (added in C++11), which may cause a memory leak in the end.
I've updated the readUserInput function so that it dynamically sizes the array and it allocates/deallocates memory for it.
This means that now it should be a class named Map, since it should be able to allocate/deallocate, insert and remove values, and is a valid container. Actually, you are creating a type of std::vector here, and if you don't create it for you own learning process, I strongly suggest you to use the std containers!
It is possible to pass both pointer and references in C++, notice that:
You can pass a reference only if the value isn't nullptr.
When there should be a value, reference is recommended.
In this case, your function should look like
void readUserInput(Map* map);
and should be called using:
readUserInput(&map);
in Visual Studio 2010 i create a while statement in which i assign a pointer to pointer to a map.
Example:
std::map<int,std::tuple<int,std::string>** > dmap;
int i=0;
while (i<3){
std::tuple<int,std::string>* t = new std::tuple<int,std::string>(10+i,std::string("test"));
dmap[i] = &t;
dmap[i + 1 ] = &t;
i++;
}
.
.
.
for (auto it = d.begin();it!=d.end();++it)
{
if(*(it->second) != nullptr){
delete *(it->second);
*(it->second) = nullptr;
}
}
The problem is that the address of &t is always the same and so at the end the map always contains , for all keys that i entered, the last *t value.
What's the problem? (Resolved)
[Edit]
Now i modify the code beacause before it was incomplete, if i want to avoid to delete nullptr i need to have a pointer to pointer. Or not?
The problem is that you're putting a pointer to a local variable t into the map. After each loop, t is destroyed and the pointer is no longer valid.
I've no idea why you're using pointers at all, let alone pointers to pointers. You probably want to put the tuples themselves in the map:
std::map<int,std::tuple<int,std::string>> dmap;
for (int i = 0; i<3; ++i){
dmap[i] = {10+i, "test"};
}
i create a while statement in which i assign a pointer to pointer to a map
Sorry for saying this, but it sounds to me like you have bigger problems than the fact that t is the same (this looks like the xy problem).
Consider (in order) one of these alternatives:
store your tuples by value
store your tuples by single pointer (worse than "by value", better than "by pointer to pointer"). If you can do this, consider declaring your map over std::shared_ptr<std::tuple<...>>)
if you really need a map of pointers to pointers to tuples, consider creating a minimal proxy object that acts like a smart pointer to pointer internally (and manages the allocations for you in a safe manner) and like a regular type from the outside (and redeclare your map accordingly).
Either way, if you really need a map of pointers to pointers to tuples (for some reason), the allocation should be done like this:
std::map<int,std::tuple<int,std::string>**> dmap;
int i=0;
while (i<3) {
*dmap[ i ] = new std::tuple<int,std::string>{10 + i, "test"};
++i;
}
(The way you did it added the address of the same local (stack) variable to the map, which would lead to undefined behavior after you exit the local function).
Why are you interested in std::tuple<int,std::string>** ?
Wouldn't a std::tuple<int,std::string>* be sufficient ?
std::map<int,std::tuple<int,std::string>* > dmap;
int i=0;
while (i<3){
std::tuple<int,std::string>* t = new std::tuple<int,std::string>(10+i,std::string("test"));
dmap[i] = t;
i++;
}
Well, the address of t is always the same, because it is local variable that is stored on your stack. Each time you enter the block, t will be allocated on the same spot (as you're destroying t after you get out of your while body).
Instead, you need to allocate it on the heap (if this is really what you want to do).
std::tuple<int,std::string>** t = new std::tuple<int,std::string>*();
*t = new std::tuple<int,std::string>(10+i,std::string("test"));
dmap[i] = t;
I can't see what you're trying to accomplish, but this would be a better solution:
std::map<int,std::tuple<int,std::string>* > dmap;
int i=0;
while (i<3){
std::tuple<int,std::string>* t = new std::tuple<int,std::string>(10+i,std::string("test"));
dmap[i] = t;
i++;
}
Even better would be to use smart pointer instead raw one.
Even better would be to store objects by value (no pointers at all).
I'm working on a project that makes me store an array of objects whose constructor is
Item(char* item, int itemType){
char temp[200];
for(int i = 0; i < 200; i++){
temp[i] = '\0';
if(item[i] != '\0'){
temp[i] = item[i];
}
}
_item = item;
_itemType = itemType;
_tweetIDs = NULL;
}
Don't worry about _tweetIDs, that's another functional part of my program and isn't related to my problem.
This array is stored within a class:
ItemList()
How this works is that the functional part of my program parses a line of input and puts it into the Item(char*, int) object. This is how it adds the line:
int addItem(char* item, int type){
char temp1[200];
for(int i = 0; i < 200; i++){
temp1[i] = '\0';
}
int j = 0;
while(item[j] != '\0'){
temp1[j] = item[j];
j++;
}
_items[_size] = Item(temp1, type);
_size++;
return _size;
}
Where _items is the Item() array and _size is a field that is incremented every time an Item() is added.
My issue comes when I have to print the contents of the list.
I have a method that does that:
void printList(){
for(int i = 0; i < 500; i++){
if(_items[i] != NULL){
cout << "[" << i << "] ";
_items[i]->printContents();
}
}
}
I tested printContents() in the constructor of Item() and tested printList in the addItem method and they both work when called within the class itself. The issue comes when I have to call the print method outside the class body.
In the main method, I create a List object:
List itemList;
The default constructor sets all members of the Item() array to NULL and initializes _size.
After adding a few Item() objects into the array (Which I confirmed is increasing in size through the debugger), I tried to print it out. When I call:
itemList.printList();
It gives me the right amount of indexes (And lines), but the char array is just a bunch of garbage. I used the debugger to try and find out where it went wrong. In the addItem() method, I called printList to check the array, and the output from that is fine. Then, I called itemList.printList() right after the last addItem() call, and it gave me garbage. In between the addItem() and itemList.printList(), the char array is lost or something along those lines.
Any idea what's going wrong? I'll give you any more code if you need it.
In your Item constructor, you are setting what I presume is a member _item as such:
_item = item;
This just assigns the pointer value of the location pointed to by item into _item. It does not actually copy the string!
The next time you go to read this location, it might be valid - chances are, though, it will be garbage, as you are seeing.
What you are looking for is a function like strcpy (as a side note, there's no need to do quite so much manual copying - just pass that pointer around and copy it once - in the Item constructor).
EDIT, to address your comment:
strcpy made your program crash because you are using it on unallocated memory.
You have to allocate memory for an array using new[] in c++
Take note on the lifetime of a variable.
If you declare temp1 as static array, then it will be destroyed immediately by the end of function addItem.
At the end, all object that refers to this memory location will be invalid.
And ....
If you want to pass a reference to an array do it this way:
Item(char** item, int itemType)
I'm imagining your definition of class Item minimally looks like this:
class Item
{
Item(char* item, int itemType);
private:
char *_item;
};
Your constructor must allocate memory for _item in order to make a copy of what gets passed in via the constructor. Failure to do that will inevitable result in memory problems and exceptions. Alternatively, you can use something like a vector of char.
In Item constructor you create local array char temp[200], you copy there what is pointed by char * item and then you don't use temp[200] any more. What's the point of doing that?
Later you assign passed pointer to _item member. The pointer points to local variable char temp1[200] in addItem(). When addItem() finishes then temp1 is destroyed and so _item in Item class points to garbage.
What you probably need to do is to allocate memory either statically in _item definition or dynamically using new (and then not forget to release it). I think the first solution will be safer for you. In the latter case you would also have to take care of copy constructor and assign operator. So, you need to change _item definition from char * _item to char _item[200], and then you can use strncpy:
Item(char* item, int itemType) {
strncpy(_item, item, 200);
}
i'm self-teaching c++ and i get how pointers work. but the doc i'm using is quite literal and the examples don't really go into why or when pointers would be used. a couple of real world examples would help me retain the knowledge.
You use pointers when you want your objects to exist longer than the current stack. You can also use them to avoid copying objects into containers.
// TODO: Remember to call DeleteObjects() when you're done here!!
std::vector<MyObject*> Objects;
void Test()
{
MyObject *const pObject = new MyObject();
Objects.push_back(pObject);
}
void DeleteObjects()
{
std::vector<MyObject*>::iterator it = Objects.begin(), itEnd = Objects.end();
for (; it != itEnd; ++it)
{
delete *it;
}
Objects.clear();
}
This is not an easy question to give a short and easy answer to, and I'm sure there's plenty of resources out there talking about pointers. Basically, whenever you'd like to use indirection (which may be even recursively) you need pointers.
Say for example a binary tree data structure, where each node have pointers to it's left and right sub trees, where either might a pointing to 0 (or NULL, meaning invalid pointer) to signify there's no sub tree there. This structure might look like this (not very C++-y, but that's a different story)
struct TreeNode
{
TreeNode* left;
TreeNode* right;
}
You can't use anything BUT a pointer in this case, as it'd be an infinitely large structure.
Uploading multiple data from a function. The caller would supply addresses of memory locations to be overwritten by the function
Dynamic memory allocation. Allocators would return pointers to newly allocated objects.
Passing array arguments: pass address instead of copying, to save performance for constant data.
This is not really a C++ matter, rather it is a C matter. For the beginner level, I would love to recommend the book Understanding Pointers in C
A simple example use of pointers is in linked lists. More info on wikipedia.
Pointers are useful when you need a function to return more than one variable. As an example, consider you are shopping at a grocery store. Each product has a name and a price. The name would be a string and the price a double. If there were a function called "buy", and you wanted to return both the name and price of the item, you might want to use a pointer.
void print_values(int* iptr, int size)
{
int i;
for (i=0; i < size; i++)
{
printf("%d ", *(iptr++));
}
printf("\n");
}
int main()
{
int table[256];
memset(table, 0, sizeof(table));
print_values(table, sizeof(table)/sizeof(int));
}
Or like a array of functions (example):
#define ___FUNC_B { func_1, func_2, func3 }
void ( __closure __fastcall * __FUNC_B [__FUNC_MAX] )( TObject* ) = ___FUNC_B;
Usage objects by pointers is in many cases is better:
CClass *ptr = new CClass();
/* something */
delete ptr;
If you have many objects and you must for example get it in a some arrange (eg. sort) you can use pointers for sort pointers to objects non objects:
vector <CClass*> Vptr;
for (i=0; i < 100; i++)
{
Vptr.push_back(new CClass());
}
sort(Vptr.begin(), Vptr.end(), __cmp_func);
/* something */
while (!Vptr.empty())
{
delete *(Vptr.begin());
Vptr.erase(Vptr.begin());
}
For dynamic memory alocation in C language:
char *memory = (char*) malloc(1024);
if (memory == NULL)
{
exit(1);
}
/* you have alocated 1KB in memory */
memory = (char*) realloc(2048);
if (memory == NULL)
{
exit(1);
}
/* you have alocated 2KB in memory */
free(memory);
/* you have nothing */
whats an example usage of c++
pointers?
Pointers address the following issues:
avoiding copying large chunks of memory around. That was in C at least, in C++ the preferred method is to use references. You can still use pointers if you want though.
allocating memory at runtime. This is needed when you have no idea at compilation how much memory you will use.
remembering the addresses of (member) functions for deferred calls (and callbacks).
allocating memory that outlasts it's current scope (it's still allocated after scope is finished).
sharing an object between multiple entities (multiple objects, multiple threads and so on). This means that you can pass the address of an object around and all entities using it will access the same data, not identical copies of it.
Sometimes pointers are also used as handles. That is, if you want to allow client code to uniquely identify a chunk of data without caring (or knowing) what the data is, you cast the address of the data (the pointer) to int/some other type and pass it around as a handle. This is commonly found in APIs that offer handles to client code but don't allow client code access to the real data (see use of WinAPI's HANDLE, HWND and so on - those are pointers in the internal implementation, but you don't know - or care - what the actual data is in order to use it).
Okay, I've seen so many terrible responses that I feel myself obligated to add yet another one.
First things first: we are talking C++ here. So many C uses are completely invalidated.
Terrible uses of pointers
You should learn RAII: this example is completely unsafe in the face of exception
// BAD
void func(size_t n)
{
int* array = new int[n];
// .. use array
delete[] array;
}
// GOOD
void func(size_t n)
{
std::vector<int> array(n, 0);
// .. use array
}
Rule of Thumb: if you see delete, you're doing it wrong. Chances are that if you see new too, though it's not as true because of argument forwarding issues.
Use references whenever possible
// BAD: Contract: do not pass NULL
void func(int* i);
// GOOD
void func(int& i);
Whenever the passing NULL does not make sense (and you don't need to rebind), use either a plain value or a (const) reference instead of a pointer.
Good uses of pointers:
Aliasing
void printSorted(std::vector<BigType> const& values)
{
std::vector<BigType*> references = from(values);
std::sort(references.begin(), references.end(), ByPointer());
std::transform(references.begin(), references.end(),
std::ostream_iterator<BigType>(std::cout, " "),
Dereference());
}
Optional Result
Object* find(Key const& key);
this is equivalent to:
boost::optional<Object&> find(Key const& key);
but quite less verbose.
Clone method
The use of a bare pointer as the return type of the clone method is mandated by the Boost Cloneable concept:
struct Base
{
virtual ~Base() {}
virtual Base* clone() const = 0;
};
There is a sound reason: taking advantage of covariant return types for the overloading of virtual methods. This allows us to write:
struct Derived: Base
{
virtual Derived* clone() const { return new Derived(*this); }
};
Thus taking full advantage, when cloning from a Derived const&, of the fact that we know that what is returned is at least a Derived.
It is up to the programmer to take care of the allocated memory unfortunately, so it must be used in conjunction with Smart Containers:
std::unique_ptr<Base> u = derived.clone();
A pointer can be considered a simple variable, but instead of saving a value it saves an adress to the position of the memory that stores a value.
Think the memory as a block of drawers and in each drawer you can put a value, to make it easier to find the values, you numerus the drawers. So, a position of memory would be a drawer and the block would be the full memory.
So, when you create a pointer, for example:
int* drawer = 0;
You are referring to the drawer which is labelled with the number 0 and contains an integer value, now you may think, ok, but, how can I get that value? Well, it's simple:
int value = *drawer;
By the same way you can store a new value on that drawer (memory address):
*drawer = 15;
Now comes the fun, the block of drawers is magic, and a drawer can take you to another drawer of the block, if we label them with the same number, the value that one stores is the same in the other:
int* drawer = 0;
int* drawer_copy = drawer;
*drawer = 15;
And what happens? That "drawer_copy", which is referring to the address 0 as "drawer" allows you to access to the integer value 15.
We can also save the address of a normal variable, we use the "&" prefix to get that adress:
int value = 15;
int* drawer = &value;
If we do this now:
value = 5;
"*drawer" will return a 5.
As you can see, pointers allow the user to have more control on the memory and to have reserved memory permanently, once you have declared a pointer you can preserve an address and access to it whenever you want. :)