more auto_ptr confusion

J

Jeff

After reading all I could find about auto_ptr, I decided to write a
little program to test my comprehension:

#include <iostream>
#include <memory>

class A {
public:
void say_hi() {
std::cout << "hi!" << std::endl;
}
void say_ih() {
std::cout << "!ih" << std::endl;
}
};

void f(std::auto_ptr<A> fs_pa) {
fs_pa->say_hi();
}

int main() {
std::auto_ptr<A> pa(new A);
pa->say_ih();
f(pa);
pa->say_ih();
}

This is what I expected would happen:
1. a new A is created, and its ownership is assumed by pa.
2. the say_ih() function is called with no problems
3. when f() is called, pa is copied into fs_pa, thereby transfering
ownership of the A object to fs_pa, and leaving pa with a null pointer
4. the A object's say_hi() function is called through fs_pa
5. fs_pa goes out of scope, and therefore runs its deconstructor,
which calls 'delete' on the chunk of memory which holds the A object
6. when we return to main(), pa now holds a null pointer, and
"pa->say_ih();" should cause a runtime error.

To my surprise, this piece of code produced no errors at either compile
time or at run time. Can anybody tell me what the problem is with my
reasoning?
 
K

Kai-Uwe Bux

Jeff said:
After reading all I could find about auto_ptr, I decided to write a
little program to test my comprehension:

#include <iostream>
#include <memory>

class A {
public:
void say_hi() {
std::cout << "hi!" << std::endl;
}
void say_ih() {
std::cout << "!ih" << std::endl;
}
};

void f(std::auto_ptr<A> fs_pa) {
fs_pa->say_hi();
}

int main() {
std::auto_ptr<A> pa(new A);
pa->say_ih();
f(pa);
pa->say_ih();
}

This is what I expected would happen:
1. a new A is created, and its ownership is assumed by pa.
2. the say_ih() function is called with no problems
3. when f() is called, pa is copied into fs_pa, thereby transfering
ownership of the A object to fs_pa, and leaving pa with a null pointer
4. the A object's say_hi() function is called through fs_pa
5. fs_pa goes out of scope, and therefore runs its deconstructor,
which calls 'delete' on the chunk of memory which holds the A object
6. when we return to main(), pa now holds a null pointer, and
"pa->say_ih();" should cause a runtime error.

It causes undefined behavior.
To my surprise, this piece of code produced no errors at either compile
time or at run time. Can anybody tell me what the problem is with my
reasoning?

Undefined behavior does not necessarily involve a runtime error.


Best

Kai-Uwe Bux
 
J

Jonathan Mcdougall

Jeff said:
After reading all I could find about auto_ptr, I decided to write a
little program to test my comprehension:

#include <iostream>
#include <memory>

class A {
public:
void say_hi() {
std::cout << "hi!" << std::endl;
}
void say_ih() {
std::cout << "!ih" << std::endl;
}
};

void f(std::auto_ptr<A> fs_pa) {
fs_pa->say_hi();
}

int main() {
std::auto_ptr<A> pa(new A);
pa->say_ih();
f(pa);
pa->say_ih();
}

This is what I expected would happen:
1. a new A is created, and its ownership is assumed by pa.
2. the say_ih() function is called with no problems
3. when f() is called, pa is copied into fs_pa, thereby transfering
ownership of the A object to fs_pa, and leaving pa with a null pointer

No, it leaves pa with no object to own. Use it and you'll get undefined
behavior.
4. the A object's say_hi() function is called through fs_pa
5. fs_pa goes out of scope, and therefore runs its deconstructor,

"destructor" (by the way)
which calls 'delete' on the chunk of memory which holds the A object
6. when we return to main(), pa now holds a null pointer, and
"pa->say_ih();" should cause a runtime error.

No, it causes undefined behavior.
To my surprise, this piece of code produced no errors at either compile
time or at run time. Can anybody tell me what the problem is with my
reasoning?

You associate "undefined behavior" with "crash". UB may do anything,
from crashing to nothing.


Jonathan
 
B

Butterfly

Jonathan said:
No, it leaves pa with no object to own. Use it and you'll get undefined
behavior.


"destructor" (by the way)


No, it causes undefined behavior.


You associate "undefined behavior" with "crash". UB may do anything,
from crashing to nothing.


Jonathan

which compiler did you use ? If you use VS 6.0, ps is not NULL. But if
you use .net 2003 or above, you will get what you expected ( ps is NULL
).
I still don't test it but I remember that VS 6.0 does not take care of
this issue.
 
D

ddh

please use this:

class A {
public:
void say_hi() {
std::cout << "hi!" << std::endl;
}
virtual void say_ih() {
std::cout << "!ih" << std::endl;
}
};

instead of your class A.

:)
 
D

ddh

OKay,

A normal member function is just like C function, they have an address,
and the compile add "this" as the first parameter automatically. so
what ever the auto_ptr hold, you call pa->say_ih(), it just jump to the
address of "say_ih()", and push the value("this") into stack. and
"this" hasn't been refered in say_ih(), so there is no access violation
here.

But if say_ih() is virtual, to get the address of it, "this" must be
accessed to get the vtbl. and because "this" has already been released,
so the program must crash for an access violation.

sorry for my poor english. i hope i express my opinion correctly. :)
 
J

Jonathan Mcdougall

ddh said:
OKay,

A normal member function is just like C function, they have an address,
and the compile add "this" as the first parameter automatically. so
what ever the auto_ptr hold, you call pa->say_ih(), it just jump to the
address of "say_ih()", and push the value("this") into stack. and
"this" hasn't been refered in say_ih(), so there is no access violation
here.

But if say_ih() is virtual, to get the address of it, "this" must be
accessed to get the vtbl. and because "this" has already been released,
so the program must crash for an access violation.

sorry for my poor english. i hope i express my opinion correctly. :)

Several things here

1) you don't seem to understand that there are guidelines for posting
in this newsgroup. You have been ignoring what I said about etiquette
for a few posts now. Please, tell us if you just don't understand,
we'll be happy to explain it again. However, if you think your posting
style is more appropriate than what's recommended, just let us know.

2) what is written in your post has nothing to do with standard C++,
the subject of this newsgroup. Implementation do not necessarily use
vtables for virtual dispatch and undefined behavior does not
necessarily mean "access violation". Please, try to restrict your
comments to keep them on topic.


Jonathan
 
J

Jeff

ddh said:
OKay,

A normal member function is just like C function, they have an address,
and the compile add "this" as the first parameter automatically. so
what ever the auto_ptr hold, you call pa->say_ih(), it just jump to the
address of "say_ih()", and push the value("this") into stack. and
"this" hasn't been refered in say_ih(), so there is no access violation
here.

But if say_ih() is virtual, to get the address of it, "this" must be
accessed to get the vtbl. and because "this" has already been released,
so the program must crash for an access violation.

sorry for my poor english. i hope i express my opinion correctly. :)

Yes, when I added "virtual" it made the program do what I originally
thought it would do. So if I understand right, what actually happened
in my program above (the one without 'virtual') was that the compiler
saw 'pa', realized that it was was of type auto_ptr<A>, and so simply
found A::say_ih() -- the fact that pa no longer pointed to anything was
irrelevant, right?

Thanks very much for your help!
 
J

Jeff

Jonathan said:
2) what is written in your post has nothing to do with standard C++,
the subject of this newsgroup. Implementation do not necessarily use
vtables for virtual dispatch and undefined behavior does not
necessarily mean "access violation". Please, try to restrict your
comments to keep them on topic.


Jonathan


I think I have to disagree with you here; the observation that adding
'virtual' would very likely cause the undefined behavior to manifest
itself in the form of error messages really did help me understand what
was going on here.

That having been said, I do appreciate your help -- thanks!
 
J

Jonathan Mcdougall

Jeff said:
Yes, when I added "virtual" it made the program do what I originally
thought it would do. So if I understand right, what actually happened
in my program above (the one without 'virtual') was that the compiler
saw 'pa', realized that it was was of type auto_ptr<A>, and so simply
found A::say_ih() -- the fact that pa no longer pointed to anything was
irrelevant, right?

Dereferencing an invalid std::auto_ptr results in undefined behavior.
The standard states that "permissible undefined behavior ranges from
ignoring the situation completely with unpredictable results, to
behaving during translation or program execution in a documented manner
characteristic of the environment (with or without the issuance of a
diagnostic message), to terminating a translation or execution (with
the issuance of a diagnostic message)." (1.3.12)

What happened in your case was that the dereferencing of an invalid
std::auto_ptr was "ignored with unpredictable results". From that very
statement until the end of the program, the behavior is undefined.

However, a sensible behavior would be that as long as you don't
illegally access memory, your program won't crash. It seems, in your
case, that std::auto_ptr has an unchecked operator-> (that may be
unfortunate during development) and that, on your compiler, invoking a
member function on an invalid pointer does not crash if you don't use
the this pointer.

Understand that this is "undefined behavior". Running your program on
another machine could very well blow up your monitor.


Jonathan
 
J

Jeff

Jonathan said:
Understand that this is "undefined behavior". Running your program on
another machine could very well blow up your monitor.


Jonathan

Lol -- I hadn't realized that learning C++ could be so hazardous to my
health!!
 
N

Noah Roberts

Jeff said:
Yes, when I added "virtual" it made the program do what I originally
thought it would do. So if I understand right, what actually happened
in my program above (the one without 'virtual') was that the compiler
saw 'pa', realized that it was was of type auto_ptr<A>, and so simply
found A::say_ih() -- the fact that pa no longer pointed to anything was
irrelevant, right?

class A
{
public:
void x() {}
}

A * a = 0;

a->x();

This is pretty much the same as calling some function...

A_x(struct Aimpl * this) {}

Calling that function with a null pointer will not pose any problems as
the function never looks at or reads 'this'...therefor neither will
a->x().

By changing x to be virtual you have introduced a step in between that
does in fact dereference the null pointer. "Aimpl" will have a table
of function pointers that needs referencing before the call...you end
up with something more like:

Aimpl *a = 0;

(*a->_vtable[1])(a);

_vtable[1] pointing to the above A_x function.

This dereferences a, which in our case is 0 or NULL.

As a purely theoretical point of view of staying strictly to the
standard none of the above is true...it is not know what when how or
why. But from a practical point of view there is no implementation
that doesn't work this way...as a C++ programmer it is good to know
these things as you *can* run into situations caused by a null _vtable
inside of a valid class on some architectures in some situations (I
just did yesturday).

Should you depend on this in your code? Definately not...you should
not depend on the definedness of undefined behavior. That doesn't mean
it isn't good to know or talk about.
 
B

Bo Persson

Noah Roberts said:
class A
{
public:
void x() {}
}

A * a = 0;

a->x();

This is pretty much the same as calling some function...

A_x(struct Aimpl * this) {}

Calling that function with a null pointer will not pose any problems
as
the function never looks at or reads 'this'...therefor neither will
a->x().

Unless you happen to run on hardware, where loading an invalid pointer
into an address register causes a hardware trap.

Guess why the behaviour is undefined. :)



Bo Persson
 
A

Andre Kostur

After reading all I could find about auto_ptr, I decided to write a little
program to test my comprehension:

#include <iostream>
#include <memory>

class A {
public:
void say_hi() {
std::cout << "hi!" << std::endl;
}
void say_ih() {
std::cout << "!ih" << std::endl;
}
};

void f(std::auto_ptr<A> fs_pa) {
fs_pa->say_hi();
}

int main() {
std::auto_ptr<A> pa(new A);
pa->say_ih();
f(pa);
pa->say_ih();
}

This is what I expected would happen: 1. a new A is created, and its
ownership is assumed by pa. 2. the say_ih() function is called with no
problems 3. when f() is called, pa is copied into fs_pa, thereby
transfering ownership of the A object to fs_pa, and leaving pa with a null
pointer 4. the A object's say_hi() function is called through fs_pa 5.

Mod #3: Assuming a standards-compilant implementation. As I recall,
certain implementations didn't set the originating pointer to NULL after
transferring ownership...

fs_pa goes out of scope, and therefore runs its deconstructor, which
calls 'delete' on the chunk of memory which holds the A object 6. when
we return to main(), pa now holds a null pointer, and "pa->say_ih();"
should cause a runtime error.

To my surprise, this piece of code produced no errors at either compile
time or at run time. Can anybody tell me what the problem is with my
reasoning?

No problems at compile time as you've done nothing syntactically wrong.

The problem is that you're assuming that Undefined Behaviour == Program
Crash. You could probably increase the chances of a program crash if your
member functions tried to access member variables of your class. However,
even that's only increasing your chances. You can't guarantee that it
will crash.
 
N

Noah Roberts

Bo said:
Unless you happen to run on hardware, where loading an invalid pointer
into an address register causes a hardware trap.

Why would it be loading it into an address register?

Answer: it wouldn't. A call to A_x in the case when it is not virtual
is resolved by the compiler. The result is the same as calling a
function. It wouldn't load into an address register because no address
is accessed...not the one you are thinking is anyway.

You are forgetting that the source code and the machine code do not
have to be at all equivelant. In these cases a-> doesn't actually
result in machine code that accesses a pointer...it results in a
function call that is passed a pointer.

It could be implemented so that all function calls really result in
something like:

struct A
{
void (*f)(A * this);

A() : f(&A_x) {}
};

now you have a virtual function...non-virtuals aren't implemented this
way...it's inefficient for that purpose.

Again, yes...it is undefined...good luck finding a case when this isn't
true though. It is undefined for such a theoretical implementation but
I don't know of any...so if you find one let me know.
 
R

Rolf Magnus

Jeff said:
After reading all I could find about auto_ptr, I decided to write a
little program to test my comprehension:

#include <iostream>
#include <memory>

class A {
public:
void say_hi() {
std::cout << "hi!" << std::endl;
}
void say_ih() {
std::cout << "!ih" << std::endl;
}
};

void f(std::auto_ptr<A> fs_pa) {
fs_pa->say_hi();
}

int main() {
std::auto_ptr<A> pa(new A);
pa->say_ih();
f(pa);
pa->say_ih();
}

This is what I expected would happen:
1. a new A is created, and its ownership is assumed by pa.
Yes.

2. the say_ih() function is called with no problems
Yes

3. when f() is called, pa is copied into fs_pa, thereby transfering
ownership of the A object to fs_pa, and leaving pa with a null pointer
Yes.

4. the A object's say_hi() function is called through fs_pa
Yes.

5. fs_pa goes out of scope, and therefore runs its deconstructor,
which calls 'delete' on the chunk of memory which holds the A object
Yes.

6. when we return to main(), pa now holds a null pointer, and
"pa->say_ih();" should cause a runtime error.

No. It should invoke undefined behavior. That means anything can happen,
including appearing to produce no error at all.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,120
Latest member
ShelaWalli
Top