Introduction to Programming and C++

Contents Previous Topic Next Topic

Each time that an object is either:

  • passed-by-value to a function,
  • passed as an argument to a newly declared variable of the same type,
  • returned-by-value from a function and assigned to a newly declared variable

a copy of the object being transferred is made.

Copying Built-in Data Types

For built-in data types, this is quite obvious:

#include <iostream>
using namespace std;

int incr( int n ) {
	++n;

	return n;
}

int main() {
	int var = 3;

	cout << var << " and " << incr( var ) << endl;

	return 0;
}

First, when var is passed to id, a copy is made. Thus, when n is modified inside of the function incr, the original variable var is left unchanged. Similarly, when the incremented parameter n is returned from incr, a copy is returned.

That a copy of the parameter (or a local variable, for that matter) is returned is important, as after the variable is returned, the local variable/parameter ceases to exists: the compiler may allocate that memory for another function call, or perhaps the OS may reclaim that memory.

Copying Objects

When an object (an instance of a class) is passed- or returned-by-value to or from a function, it, too, is copied in the sense that memory is allocated for a new instance and all of the values of the member variables are copied over to the new instance.

For instance, consider the following class:

#include <iostream>
using namespace std;

class Coord {
	private:
		double x;
		double y;

	public:
		Coord( double, double );

		double get_x() const;
		double get_y() const;

		void set_x( double );
		void set_y( double );
};

Coord::Coord( double x1, double y1 ):x( x1 ), y( y1 ) {
	// empty constructor
}

double Coord::get_x() const { return x; }
double Coord::get_y() const { return y; }

void Coord::set_x( double x1 ) { x = x1; }
void Coord::set_y( double y1 ) { y = y1; }

Coord reflect( Coord coord ) {
	coord.set_x( -coord.get_x() );
	coord.set_y( -coord.get_y() );

	return coord;
}

int main() {
	Coord pt( -5.32, 3.25 );

	cout << "Original: (" << pt.get_x() << ", "
	     << pt.get_y() << ")" << endl;

	cout << "Copied point: (" << reflect( pt ).get_x() << ", "
	     << reflect( pt ).get_y() << ")" << endl;

	// pt is unchanged
	cout << "Original: (" << pt.get_x() << ", "
	     << pt.get_y() << ")" << endl;

	return 0;
}

Copying Pointers

Suppose, however, the class stored a pointer to an array. In this case, simply the address stored by the pointer would be copied over, as opposed to copying the entire array. Consequently, if such an object is passed to a function, the pointer would still point to the same memory location as the original object. Thus, changes to the "copied" function inside the function could affect the original argument.

This is a modification of the Coord class which stores an array of two values. That array is dynamically created.

#include <iostream>
using namespace std;

class Coord {
	private:
		double * pts;

	public:
		Coord( double, double );
		~Coord();

		double get_x() const;
		double get_y() const;

		void set_x( double );
		void set_y( double );
};

Coord::Coord( double x, double y ):pts( new double[2] ) {
	pts[0] = x;
	pts[1] = y;
}

// dynamic allocation (new[]) requires the equivalent
// dynamic deallcation (delete[])

Coord::~Coord() {
	// Set both to zero to indicate this array has been deleted
	pts[0] = 0.0;
	pts[1] = 0.0;
	delete [] pts;
}

double Coord::get_x() const { return pts[0]; }
double Coord::get_y() const { return pts[1]; }

void Coord::set_x( double x ) { pts[0] = x; }
void Coord::set_y( double y ) { pts[1] = y; }

void reflect( Coord coord ) {
	coord.set_x( -coord.get_x() );
	coord.set_y( -coord.get_y() );
}

int main() {
	Coord pt( -5.32, 3.25 );

	cout << "Original: (" << pt.get_x() << ", "
	     << pt.get_y() << ")" << endl;

	reflect( pt );

	cout << "Original: (" << pt.get_x() << ", "
	     << pt.get_y() << ")" << endl;

	return 0;
}

When you execute this, it prints out the original, reflect( Coord ) is called, but then two things occur: the point has changed and a error occurs:

[ece250@eceweb ~]$ g++ file.cpp 
[ece250@eceweb ~]$ ./a.out 
Original: (-5.32, 3.25)
Original: (0, 0)
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x000000001dedd010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3c4cc7245f]
/lib64/libc.so.6(cfree+0x4b)[0x3c4cc728bb]
./a.out(__gxx_personality_v0+0x274)[0x400aa4]
./a.out(__gxx_personality_v0+0x3b3)[0x400be3]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3c4cc1d994]
./a.out(__gxx_personality_v0+0x49)[0x400879]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:05 6817422             /home/ece250/public_html/intro/2m/a.out
00601000-00602000 rw-p 00001000 08:05 6817422             /home/ece250/public_html/intro/2m/a.out
1dedd000-1defe000 rw-p 1dedd000 00:00 0                   [heap]
3c4c800000-3c4c81c000 r-xp 00000000 08:03 518691          /lib64/ld-2.5.so
3c4ca1c000-3c4ca1d000 r--p 0001c000 08:03 518691          /lib64/ld-2.5.so
3c4ca1d000-3c4ca1e000 rw-p 0001d000 08:03 518691          /lib64/ld-2.5.so
3c4cc00000-3c4cd4e000 r-xp 00000000 08:03 518868          /lib64/libc-2.5.so
3c4cd4e000-3c4cf4e000 ---p 0014e000 08:03 518868          /lib64/libc-2.5.so
3c4cf4e000-3c4cf52000 r--p 0014e000 08:03 518868          /lib64/libc-2.5.so
3c4cf52000-3c4cf53000 rw-p 00152000 08:03 518868          /lib64/libc-2.5.so
3c4cf53000-3c4cf58000 rw-p 3c4cf53000 00:00 0 
3c4d800000-3c4d882000 r-xp 00000000 08:03 518983          /lib64/libm-2.5.so
3c4d882000-3c4da81000 ---p 00082000 08:03 518983          /lib64/libm-2.5.so
3c4da81000-3c4da82000 r--p 00081000 08:03 518983          /lib64/libm-2.5.so
3c4da82000-3c4da83000 rw-p 00082000 08:03 518983          /lib64/libm-2.5.so
3c5aa00000-3c5aa0d000 r-xp 00000000 08:03 518977          /lib64/libgcc_s-4.1.2-20080825.so.1
3c5aa0d000-3c5ac0d000 ---p 0000d000 08:03 518977          /lib64/libgcc_s-4.1.2-20080825.so.1
3c5ac0d000-3c5ac0e000 rw-p 0000d000 08:03 518977          /lib64/libgcc_s-4.1.2-20080825.so.1
3c5fe00000-3c5fee6000 r-xp 00000000 08:03 2110885         /usr/lib64/libstdc++.so.6.0.8
3c5fee6000-3c600e5000 ---p 000e6000 08:03 2110885         /usr/lib64/libstdc++.so.6.0.8
3c600e5000-3c600eb000 r--p 000e5000 08:03 2110885         /usr/lib64/libstdc++.so.6.0.8
3c600eb000-3c600ee000 rw-p 000eb000 08:03 2110885         /usr/lib64/libstdc++.so.6.0.8
3c600ee000-3c60100000 rw-p 3c600ee000 00:00 0 
2ac9e933e000-2ac9e9341000 rw-p 2ac9e933e000 00:00 0 
2ac9e9356000-2ac9e9358000 rw-p 2ac9e9356000 00:00 0 
7fff11d92000-7fff11da7000 rw-p 7ffffffe9000 00:00 0       [stack]
7fff11dfd000-7fff11e00000 r-xp 7fff11dfd000 00:00 0       [vdso]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0   [vsyscall]
Abort
[ece250@eceweb 2m]$ 

The details of the printout are beyond this course; however, what is important is that the correct error is detected: a pointer was deleted twice ("double free").

The problem is, when pt was passed to void reflect( Coord coord ), a copy was made and when that routine exited, that copy was destroyed. The copy, however, was only shallow and therefore, when the new object inside the function was constructed, the address of the member variable pts of the argument was assigned to the member variable coord.pts. When the function reflect exited, that memory location was returned to the operating system in the destructor. Unfortunately, pt.pts continued to store that same address, so when main() exited, delete [] was called on the same memory location for a second time.

We can solve this by making a deeper copy of the object:

#include <iostream>
using namespace std;

class Coord {
	private:
		double * pts;

	public:
		Coord( double, double );
		Coord( Coord const & );
		~Coord();

		double get_x() const;
		double get_y() const;

		void set_x( double );
		void set_y( double );
};

Coord::Coord( double x, double y ):pts( new double[2] ) {
	pts[0] = x;
	pts[1] = y;
}

// We will create a new array for the copied coordinate
// and only copy over the two values from the object
// being copied.
Coord::Coord( Coord const &coord ):pts( new double[2] ) {
	pts[0] = coord.pts[0];
	pts[1] = coord.pts[1];
}

// dynamic allocation (new[]) requires the equivalent
// dynamic deallcation (delete[])

Coord::~Coord() {
	pts[0] = 0.0;
	pts[1] = 0.0;
	delete [] pts;
}

double Coord::get_x() const { return pts[0]; }
double Coord::get_y() const { return pts[1]; }

void Coord::set_x( double x ) { pts[0] = x; }
void Coord::set_y( double y ) { pts[1] = y; }

void reflect( Coord coord ) {
	coord.set_x( -coord.get_x() );
	coord.set_y( -coord.get_y() );
}

int main() {
	Coord pt( -5.32, 3.25 );

	cout << "Original: (" << pt.get_x() << ", "
	     << pt.get_y() << ")" << endl;

	reflect( pt );

	cout << "Original: (" << pt.get_x() << ", "
	     << pt.get_y() << ")" << endl;

	return 0;
}

With this change, the array in the copy is different from the array of the argument passed to the function.

The astute user may have noticed that the function

Coord reflect( Coord coord ) {
	coord.set_x( -coord.get_x() );
	coord.set_y( -coord.get_y() );

	return coord;
}

was changed to

void reflect( Coord coord ) {
	coord.set_x( -coord.get_x() );
	coord.set_y( -coord.get_y() );
}

This is because the local variable is returned using again another copy. This copy, too, is destroyed; however, this would occur before the printing of (0, 0) in the given example and therefore it would be even more difficult to demonstrate the issue.

Note: Another solution is to track how many copies are made of an object and to then only delete the array when the last copy is being deleted.


Contents Previous Topic Top Next Topic