UMBC CMSC202, Computer Science II, Spring 1998,
Sections 0101, 0102, 0103, 0104 and Honors
Thursday April 23, 1998
Assigned Reading:
- A Book on C:
- Programming Abstractions in C:
Handouts (available on-line):
Project 5
Topics Covered:
- A long discussion on Project 5.
- 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? The point is that when we have a
class that uses dynamically allocated memory, we should work with pointers
to those objects rather than the objects themselves. 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.
Last Modified:
22 Jul 2024 11:27:43 EDT
by
Richard Chang
Back up
to Spring 1998 CMSC 202 Section Homepage