UMBC CMSC202, Computer Science II, Fall 1998,
Sections 0101, 0102, 0103, 0104
6. Danger of Destructors
Thursday September 17, 1998
[Previous Lecture]
[Next Lecture]
Assigned Reading: 3.10-3.14
Handouts (available on-line):
Programs from this lecture.
Topics Covered:
- Last lecture, someone asked when the construtor is called for
global variables and static local variables. The following
program and
sample run shows that the
constructor for global variables is called before the execution
of the main program. The constructor for a static local variable
is called when the function is called for the first time.
- Next we discussed some common pitfalls when we work with classes that
include destructors. In the first sequence of programs, we work with a
simple Record class. (See header file
and implementation.) Besides, the
destructor and the constructors, the Record class only has a member
function that dumps out the contents of the object. The main feature of
the implementation is that the constructors and destructors print out
debugging messages.
- In the first program, the main
function calls a function foo() which has a local variable
R of type Record. The sample
run shows that when the function foo() terminates, the
destructor is called to destroy the local variable R.
- The second program is
more curious. Both the main program and foo() have
local variables of type Record. This time the local variables
are initialized using the alternate constructor. Surprisingly,
the sample run shows that
the destructor is called before the first printf() statement of
the main program is executed. Apparently, when the alternate
constructor is called, it creates a temporary object at address
ffffffadc0. The values of this temporary object are
copied to the Record S, then the temporary object
is destroyed using the destructor. (Creating the Record R
in foo() has a similar effect.) Thus, having a destructor
in a class means that you have to contend with the destructor
being called perhaps in situations that are not expected.
Curiously, compiling and running the same program using the GNU
C++ compiler, g++, does not result in the extra calls to the destructor.
- The third program shows a different
way to invoke the alternate constructor for Record. Using this particular
syntax, the CC compiler does not produce the extra calls to the destructor.
See sample run.
- However, the situation is not solved completely. In the fourth program, the function foo()
returns a Record value. In this case, the g++ compiler produces an extra
call to the destructor. Apparently, the return value from the function is
stored in a temporary object. Once the return value is assigned to the
Record P, the temporary object is destroyed. See sample run.
- So, why are we so paranoid about these calls to the destructor? Well,
suppose that the Record class is slightly more complicated and includes
members with dynamically allocated memory, say a string. When a Record
object is destroyed, we should naturally free the space used by the string.
However, if the destructor is called unexpectedly, we may end up freeing a
string that we still want to use. See the new header file and implementation of the Record class.
- In our fifth program (first using the
new Record class), we see the problems with the CC compiler and the
definition:
Record S = Record("S") ;
First, a temporary object is created with data string "S". The
fields of the object are copied to the Record S. Then, the
temporary object is destroyed, freeing the memory used by "S".
However, as the sample run shows, the
Record S is still using that memory location. The output from the
constructor, the destructor and the id() function was:
Alternate Constructor: this=ffffffadc0, s=(100121fa,"S"),str=(10013010,"S")
Destructor: this=ffffffadc0, str=(10013010,"S")
Identify S: id: this=ffffffadd0, str=(10013010,"S")
Notice that the temporary object and the Record S both use memory
location 10013010 to store the string "S" and that the destructor
has already freed this space. Thus, at line 1 of the main program, we are
already in trouble.
- Using the GNU g++ compiler avoided this particular problem, but using
the g++ compiler does not solve all of our problems. In the fifth program,
we pass the Record S by value to the function foo().
This value is copied to the parameter T. When the function
foo() terminates, T is destroyed. Again, the space used
by the string in S is inadvertently freed. Even the code
generated by the g++ compiler has this problem.
- Our sixth program shows that using the
new syntax for invoking the alternate constructor solves the first problem
in the fifth program. However, the sample
run shows that we still have the problem with the Record T
being freed.
- Our seventh program demonstrates real
difficulties when the function foo() also returns the value of the
Record R. Now, not only is the space used by S freed,
the Record P is also pointing to freed space. Subsequent calls to
the strdup() function reuses this freed space. In the sample run using the CC compiler, the record
S holds the string "Hello" instead of "S" and
P holds "World" instead of "R". The sample run
for the g++ case is even stranger. Since the g++ compiler generates code
that uses a temporary object to hold the return value from foo(),
the space used by R is freed twice --- once when R is
destroyed and another time when the temporary object is destroyed. Thus,
subsequent calls to strdup() created a strange situation where
memory location 10000360 is reused without being freed:
str1=(10000360,"Hello")
str2=(10000360,"World")
Very strange!
- So, what is the solution? One solution is to work with pointers to
objects rather than the objects themselves when we have a class that
uses dynamically allocated memory. That way the objects are not
destroyed automatically. Our eighth
program shows one such solution. Note that we only use the alternate
constructor together with the new operator. So, no temporary objects are
created, even with the CC compiler. The sample run shows that the destructor is only
called when we explicitly use the delete operator.
- Another solution is to pass parameters by reference. In our ninth program, the formal parameter T
of the function foo() is a reference parameter. This prevents the
destruction of T a the end of the function call. Since the
compiler knows that T is only an alias for an object, it does not
generate code to destroy this object when T is no longer used.
This approach does not resolve the probelm completely. The GNU C++
compiler still has a problem with functions that return objects with
dynamically allocated memory. (See sample
run.)
- In our tenth program the
function foo() takes a reference parameter and also returns
an object by reference. Using this approach, even the GNU C++
compiler produces code that works as we intended. See sample run. However,
this approach results in a memory leak because the dynamically
allocated record that was created in the function foo()
never gets freed.
[Previous Lecture]
[Next Lecture]
Last Modified:
22 Jul 2024 11:28:50 EDT
by
Richard Chang
Back up
to Fall 1998 CMSC 202 Section Homepage