Passing pointers* to functions???

A little confused when it comes to passing pointers to functions? When you pass by reference &, the function that you pass it to acts on the address & does not make a copy. The same is true for pointers? Why then are there 2 different addresses each time I run the code as if a copy has been made? When I issue "delete pFish;" in the DeletePointer function how does it know to delete both?

When I try to "delete pTuna;" in main it throws an exception & confirms that it has indeed been deleted...but still there are 2 different pointer addresses? Did the function make a copy & delete both addresses?

&pTuna = 0x0098F880
&pFish = 0x0098F780




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 #include <iostream>
using namespace std;

class Fish
{
public:
	Fish()
	{
		cout << "FISH CONSTRUCTOR" << endl;
	}

	~Fish()
	{
		cout << "FISH DESTRUCTOR" << endl;
	}
};

class Tuna : public Fish
{
public:
	Tuna()
	{
		cout << "Tuna CONSTRUCTOR" << endl;
	}

	~Tuna()
	{
		cout << "Tuna DESTRUCTOR" << endl;
	}
};

void DeletePointer(Fish* pFish)
{
	cout << "&pFish = " << &pFish << endl;
	delete pFish;
}

int main()
{
	Tuna* pTuna = new Tuna;
	cout << "&pTuna = " << &pTuna << endl;
	DeletePointer(pTuna);

	cout << "*******************" << endl;

	cout << "&pTuna = " << &pTuna << endl;

	//delete pTuna;
	//cout << "&pTuna = " << &pTuna << endl;

	return 0;
}
Notice that your Tuna destructor is never called. If you're going to be deleting a Fish pointer, but that Fish pointer is actually a Tuna subclass, you need to make sure you have a virtual destructor in the base class.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Fish
{
public:
	Fish()
	{
		cout << "FISH CONSTRUCTOR" << endl;
	}

	virtual ~Fish()
	{
		cout << "FISH DESTRUCTOR" << endl;
	}
};


This will allow ~Tuna() to correctly be called when deleting a base-class pointer.
https://isocpp.org/blog/2019/08/quick-q-when-to-use-virtual-destructors

Second, about passing pointers: Your DeletePointer function passes the pointer by value. So the pointer itself is copied, meaning the address of the pointers (&pTuna, &pFish) will be different.

Both values of pTuna and pFish will be the same, but the address of the pointer is just whatever it happens to be when the pointer is copied to the DeletePointer function. The address of the pointer (&pointer) is not really important.

When you say "delete pFish", you are deleting the memory that the pointer points to. The pointer itself is not modified, so line 41 will always be the same as line 46.
Last edited on
Yes, actually the lesson was designed to demonstrate the need for a virtual destructor.

Is this considered a memory leak though, or was it just enough to delete the memory that it was pointing to...through the DeletePointer function "delete pFish;"? Because of the delete pTuna has nothing to point to but still retains a &pTuna pointer address block?
one delete for one new.

so if you have this...
whatever * wp1 = new whatever;
whatever *wp2 = wp1;
delete wp2; //wp1 is also invalid. the memory at the location that both pointers point to is the same and either you own it (via new) or not (after delete).

it is a great idea to make a pointer null after deletion.
it is dangerous (bug prone) to have the above code, even so:
...
from the delete:
delete wp2;
wp2 = nullptr; //good practice.
...
*wp1 = 11;//oops. there isnt a good way to delete wp1.
the bug/dangerous part is having 2 things point to the same place then using them carelessly.

a better answer is
whatever * wp1 = new whatever;
whatever *&wp2 = wp1; //its weird, but the type is a "whatever pointer reference", or a whatever pointer that is a reference to wp1. the same thing can be seen in function parameters, eg foo(whatever *& wp) <--- this lets you new wp and the reference news the pointer passed into the function as one example. without the &, the new memory isnt back-fed to the original...
delete wp2;
wp2 = nullptr; //and so does wp1, via the reference.

Thanks, makes more sense now, although I have to investigate *& some more.
Is this considered improper & throws a breakpoint/error after the delete, lets say if done standalone or the address value passed in an array?

int num1 = 55;
int* ptr1 = new int;
ptr1 = &num1;

num1 is on the stack & ptr1 on the heap, and "ptr1 = &num1;" changes the address that ptr1 has been pointing to and this is BAD.

I understand that I could have easily not created a "new" pointer & written just "ptr1 = &num1;" or "*ptr1 = num1;" BUT it is good to know my limits on how the compiler treats pointers. Funny, how sometimes certain lines of code just magically work & the compiler "understands" & other times when it seems intuitive for it to & it doesn't.
Last edited on
it leaks memory, and that is 'bad'. There is no warning and most compilers cannot detect the error here but some third party tools can find these (and they also find totally correct code and complain about nothing a lot, too).

what happens here exactly is:
your assignment of num1 to ptr1 overwrote the one and only copy of the address provided by new. There is now NO WAY to get that value back so you can release it back to the os (delete).
if you did the above in a forever loop, your program would eventually run the system out of memory and things would slow to a crawl and then stop working entirely. Its easier to see this if you allocate much more (like a gigabyte) at a time instead of 4 or 8 bytes (it takes a long, long time to eat a gig that way. My computer would need over 8 billion loop cycles to notice a leak of one 64 bit integer every time.

the compiler cannot reason, so intuition has no meaning to it.
as for things working, the WORST POSSIBLE THING that can happen in a program is for it to "work" when it has a major bug. Trust me on this ... when it does not work, you fix it. When it works (or seems to), you go to bed and call it good.

there are complicated reasons why bad pointers work when they should not. Lets just leave it simple tonight: sometimes the 'bad' address in the pointer is 'not that bad' ... eg its still in the memory your program owns, and its an unused location, yea it will work. But you can't ensure that, its a time bomb waiting for your customers to find it so you can get up at 2am to try to debug it, which is hard, because it works on your machine, but not the customer's!

all that to say, there is a reason why smart pointers exist, and there are reasons container objects exist. The smart pointers help you avoid some mistakes, and the containers keep you from doing so much hands-on memory management. You can take a <vector> and let it do the memory management, treat its [index] as you would a pointer, and safely build anything you can with pointers, with the only danger being going out of bounds in the vector and if you fear that, there is a safe way to access it that throws an error if you do!
but at the end of the day, you can avoid even that most of the time. I can't recall the last time I used 'new' or even smart pointers for dynamic memory type work. I use pointers here and there (usually casting to byte pointers) to access existing memory (like your num1 above), and rarely for anything else anymore.

Last edited on
So I did a little more experimentation with the above code. I added two void DoSomething() methods in both the Fish base class & the Tuna derived class. Visual Studio executes both & still reaches a break point on "delete pTuna;" and it is a no go.

On the other hand, Embarcadero actually runs both DoSomething() methods & executes both the Fish & Tuna destructors when "delete pTuna;" is invoked and it runs fine. BUT when I make the Fish base class destructor, a virtual...."virtual ~Fish()" then it no longer invokes the Fish & Tuna destructors when the "delete pTuna;" is called....because the Fish virtual destructor also called the Tuna destructor.

So is it possible that in this case there is a problem of slicing & that a pTuna pointer passed to the DeletePointer slices off the Tuna part & makes only a copy of pFish, which then leaves the Tuna dangling and causes a memory leak? Otherwise, how would I be able to invoke both DoSomething() methods & invoke both destructors, if it was already deleted? So without the "virtual ~Fish()" or "delete pTuna;" it does indeed create a memory leak?

void DeletePointer(Fish* pFish)
{
cout << "&pFish = " << &pFish << endl;
delete pFish;
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
using namespace std;

class Fish
{
public:
	Fish()
	{
		cout << "FISH CONSTRUCTOR" << endl;
	}

	~Fish()
	{
		cout << "FISH DESTRUCTOR" << endl;
	}

	void DoSomething()
	{
		cout << "DID SOMETHING from Fish base class" << endl;
	}
};

class Tuna : public Fish
{
public:
	Tuna()
	{
		cout << "Tuna CONSTRUCTOR" << endl;
	}

	~Tuna()
	{
		cout << "Tuna DESTRUCTOR" << endl;
	}

	void DoSomething()
	{
		cout << "DID SOMETHING from Tuna derived class" << endl;
	}
};

void DeletePointer(Fish* pFish)
{
	cout << "&pFish = " << &pFish << endl;
	delete pFish;
}

int main()
{
	Tuna* pTuna = new Tuna;
	cout << "&pTuna = " << &pTuna << endl;
	DeletePointer(pTuna);

	cout << "*******************" << endl;

	cout << "&pTuna = " << &pTuna << endl;

	pTuna->DoSomething();
	pTuna->Fish::DoSomething();

	delete pTuna;
	//cout << "&pTuna = " << &pTuna << endl;

	return 0;
}
Last edited on
The behavior of the above program is undefined. There's no point in trying to reason about what it's doing because any conclusion you reach is invalid.

1
2
pTuna->DoSomething();
pTuna->Fish::DoSomething();
You can't deference a deleted pointer via either * or ->, even to call its member functions. Especially if the calls are virtual, which isn't the case here, but nevertheless.

 
delete pTuna;
Here you delete the pointer for a second time. 1 delete per new per pointer. Always, exactly.

1
2
3
4
5
void DeletePointer(Fish* pFish)
{
    cout << "&pFish = " << &pFish << endl;
    delete pFish;
}
This function releases a base pointer where the run time type of the object may be of a derived class. If this may happen, the destructor of the base class must be virtual, or the destructors of derived classes will not be called, since the compiler can't figure out that it needs to generate a virtual call instead of a static one. You've already been told this above, and you still haven't fixed it.
1
2
3
4
virtual ~Fish()
{
    cout << "FISH DESTRUCTOR" << endl;
}
I am not totally getting what you are saying.
but that code is broken.
line 52, you delete the pointer.

line 58, you dereference the deleted pointer. I think. I hate inheritance sometimes... not sure exactly what is going on here when you delete a pointer via polymorph ... I think its not something you would want to DO, and likely on top of this there is probably a rule of 3 or 5 violation in here that you are seeing the results of.

Let me look at this after some sleep, there is something basic here that I know is not right but I can't quite put a finger to it this time of morning. About all I can come up with atm is that fish's dtor needs to be virtual, and that is likely behind some of the weirdness.
see if reading this helps.
https://stackoverflow.com/questions/461203/when-to-use-virtual-destructors
Last edited on
As already mentioned, this program's behavior is undefined (line 52) so any attempt to understand what or how it behaves is not a useful investment of time.

Undefined behavior exists, broadly speaking, to allow the implementation (compiler, operating system, the linker, etc.) to assume certain things about your program's behavior. If your program breaks any of those assumptions its behavior can change in unpredictable ways. For example a program with undefined behavior can cause demons to fly out of your nose.

Again, the source of the undefined behavior is deleting a derived object through a pointer-to-base with a non-virtual destructor.
https://eel.is/c++draft/expr.delete#3.sentence-1
Last edited on
I'm somewhat puzzled by the phrase
and the selected deallocation function is not a destroying operator delete
Setting aside that this is the first time I'm hearing about a "destroying operator delete", since the behavior being defined or possibly not hinges on which deallocation function will be selected, when would DOD be selected and when not? I'm assuming there are cases where one might delete a pointer-to-base and the deallocation function might be a DOD, because otherwise it would not make any sense to include that contingency.
Last edited on
What I am saying is that I understand the vanilla version of...

"whatever * wp1 = new whatever;
whatever *wp2 = wp1;
delete wp2; //wp1 is also invalid. the memory at the location that both pointers point to is the same and either you own it (via new) or not (after delete)."

And it is a new revelation & beautiful...it makes sense. BUT when I pass a derived class to make a copy of the base class & delete it, then the compiler really deletes all memory associated with derived class...without the virtual Fish destructor....?? I was a little suspicious of that & my little modification to the code has empowered me to be more skeptical of it.

Otherwise those 3 lines should not work at all & crash, but they do work!
1
2
3
4
pTuna->DoSomething();
pTuna->Fish::DoSomething();	

delete pTuna;


Incidentally, the code was written as is, less the DoSomething() methods & last 3 lines of code just pasted above. It was designed to show us that a virtual destructor is needed in the base class, but in so doing I was suspicious of it causing a memory leak...something that we are warned not to do. I could see myself easily making a mistake like this & getting lost in code as a novice.

What happens when you guys run the last code? Now, if someone tells me that they are willing to bet their lives that pTuna has truly been deleted by:
1
2
3
4
5
void DeletePointer(Fish* pFish)
{
    cout << "&pFish = " << &pFish << endl;
    delete pFish;
}


And that what I am now seeing is a working freak anomaly, I couldn't argue any further. But my hunch is if you look closer under the hood, there might be more than meets the eye.



I did one more test by commenting out "//DeletePointer(pTuna)" & then deleting the pointer pTuna twice, which I expected to clearly crash & it did not. It worked instead, leading me to now believe it is something with Embarcadero compiler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
	Tuna* pTuna = new Tuna;
	cout << "&pTuna = " << &pTuna << endl;
	//DeletePointer(pTuna);

	cout << "*******************" << endl;

	cout << "&pTuna = " << &pTuna << endl;
	
	
	delete pTuna;
	pTuna->DoSomething();
	pTuna->Fish::DoSomething();

	delete pTuna;
	//cout << "&pTuna = " << &pTuna << endl;

	return 0;
}
It worked instead,

No, it did not. What you see is undefined behaviour. UB can be anything.
Just because a C++ program 'provides the expected output' in one situation, does not mean that the program is 'correct' or will work in all circumstances. This especially applies to code that uses dynamic memory/pointers.
Topic archived. No new replies allowed.