11. C++ and Memory – Object-Oriented Programming with ANSI and Turbo C++

11

CHAPTER

C++ and Memory

C
H
A
P
T
E
R

O
U
T
L
I
N
E
—• 11.1 Introduction
—• 11.2 Memory Models
—• 11.3 The new and delete Operators
—• 11.4 Heap Consumption
—• 11.5 Overloading new and delete Operators
—• 11.6 Execution Sequence of Constructors and Destructors
—• 11.7 Specifying Address of an Object
—• 11.8 Dynamic Objects
—• 11.9 Calling Convention

11.1 INTRODUCTION

Memory is one of the critical resources of a computer system. One must be conscious about the memory modules while an application is being developed. In this chapter, we will learn stack, heap, dynamic object, and calling conventions.

11.2 MEMORY MODELS

The memory model sets the supportable size of code and data areas as shown in Figure 11.1. Before compiling and linking the source code, we need to specify the appropriate memory model. Using memory models, we can set the size limits of the data and code. C/C++ programs always use different segments for code and data. The memory model you opt decides the default method of memory addressing. The default memory model is small. Table 11.1 describes properties of all memory models.

(1) Tiny

Use the tiny model when memory is at an absolute premium. All four segment registers (CS, DS, ES, SS) are initialized with same address and all addressing is accomplished using 16 bits. Total memory capacity in this case is 64 K bytes. In short, memory capacity is abbreviated as KB. This means that the code, data, and stack must all fit within the same 64 KB segment. Programs are executed quickly if this model is selected. Near pointers are always used. Tiny model programs can be converted to .COM format.

Fig.11.1 Memory Organization

(2) Small

All code should fit in a single 64 KB segment and all data should fit in a second 64 KB segment. All pointers are 16 bits in length. Execution speed is same as tiny model. Use this model for average size programs. Near pointers are always used.

(3) Medium

All data should fit in a single 64 KB segment. However, the code is allowed to use multiple segments. All pointers to data are 16 bits, but all jumps and calls require 32 bit addresses. With this, access to data is fast. However, slower program execution is observed with this model. This, model is suitable for big programs that do not keep a large amount of data in memory. Far pointers are used for code but not for data.

(4) Compact

All code should fit in 64 KB segment but the data can use multiple segments. However, no data item can surpass 64 KB. All pointers to data are 32 bits, but jumps and calls can use 16 bit addresses. Slow access to data and quick code execution will be observed in this setting.

Compact model is preferred if your program is small but you require pointing large amount of data. The compact model is the opposite of the medium model. Far pointers are preferred for data but not for code. Code is limited to 64 KB, while data has a 1 MB range. All functions are near by default and all data pointers are far by default.

(5) Large

Both code and data are allowed to use multiple segments. All pointers are 32 bits in length. However, no single data item can exceed 64 KB. Code execution is slower.

Large model is preferred for very big programs only. Far pointers are used for both code and data, specifying both a 1 MB range. All functions and data pointers are far by default.

(6) Huge

Both code and data are allowed to use multiple segments. Every pointer is of 32 bits in length. Code execution is the slowest.

Huge model is preferred for very big programs only. Far pointers are used for both code and data. Turbo C++ usually bounds the size of all data to 64 K. The huge memory model sets aside that limit, permitting data to hold more than 64 K. The huge model permits multiple data segments (each 64 K in size), up to 1 MB for code, and 64 K for stack. All functions and data pointers are supposed to be far.

Table 11.1 Memory models

(7) Segment and Offset Address

Every address has two parts, segment and offset. We can separate these address parts using following two macros defined in dos.h header file.

FP_SEG( ) – This macro is used to obtain segment address of the given pointer variable.

FP_OFF ( ) – This macro is used to obtain offset address of the given pointer variable.

The following program illustrates working of both these macros.

11.1 Write a program to obtain segment and offset address.

# include <dos.h>
# include <iostream.h>
# include <constream.h>


void main ( )
{
  clrscr( );
  int  ch;
  cout<<"\n Complete Address of ch : "<<&ch;
  cout <<"\n Segment address : "<<hex<<FP_SEG(&ch);
  cout <<"\n Offset  address : "<<hex<<FP_OFF(&ch);
}

OUTPUT

Complete Address of ch : 0x8f34fff2
Segment address : 8f34
Offset address : fff2

Explanation: In the above program, variable ch is declared. The first cout statement displays whole address of the variable ch i.e., 0x8f34fff2. The macro FP_SEG( ) returns segment address. The statement used is FP_SEG(&ch). Here, the address of ch is passed to the macro. In the same way, offset address is obtained using statement FP_OFF(&ch). You can observe the complete address as displayed above. The separated addresses are also same. Figure 11.2 makes it clearer.

Fig. 11.2 Segment and offset address

The far, huge and near are keywords. They are summarized in Table 11.2.

The far pointer: A far pointer is a 32 bit pointer and contains both segment and offset address parts.
The huge pointer: A huge pointer is 32 bits long and contains both segment and offset address parts.
The near pointer: A near pointer is 16 bits long and uses the contents of CS or DS register for segment part. The offset part of the address is stored in 16 bits near pointer.

Table 11.2 Keywords summary

11.2 Write a program to declare far, near and huge pointers. Display their sizes.

# include <iostream.h>
# include <constream.h>


void main( )
{
  clrscr( );
  char  far *f;   // far pointer declaration
  char near *n;   //  near pointer declaration
  char huge *h;   //  huge pointer declaration
  cout <<"\n Size of far pointer  : "<<sizeof(f);
  cout <<"\n Size of near pointer : "<<sizeof(n);
  cout <<"\n Size of huge pointer : "<<sizeof(h);
}

OUTPUT

Size of far pointer: 4
Size of near pointer: 2
Size of huge pointer: 4

Explanation: In the above program, the pointer f, n, and h are declared. In pointer declaration, the pointers are preceded by keywords far, near and huge. The sizeof( ) operator displays the size of pointers in bytes.

11.3 Write a program to use far pointer.

# include <iostream.h>
# include <constream.h>


void main( )
{
  clrscr( );
  char far *s;                // pointer declaration
  s=(char far*)0xB8000000L;   // starting address
  *s=‘W’;                     // displays character on the screen
}

OUTPUT
W

Explanation: Character pointer *s is declared. It is a far pointer. The address 0xB8000000L is assigned to it. It is starting address outside the data segment. The address is converted to char far* type by applying type casting. The statement *s=′W′ displays the character on the screen.

11.3 THE new AND delete OPERATORS

So far, we have used the new and delete operators in short programs, but for applying them in huge applications we need to understand them completely. A small mistake in syntax may cause a critical error and possibly corrupt memory heap. Before studying memory heap in detail let us repeat few points regarding new and delete operators.

(1) The new operator not only creates an object but also allocates memory.

(2) The new operator allocates correct amount of memory from the heap that is also called as a free store.

(3) The object created and memory allocated by using new operator should be deleted by the delete operator otherwise such mismatch operations may corrupt the heap or may crash the system. According to ANSI standard, it is a valid outcome for this invalid operation and the compiler should have routines to handle such errors.

(4) The delete operator not only destroys object but also releases allocated memory.

(5) The new operator creates an object and it remains in the memory until it is released using delete operator. Sometimes the object deleted using the delete operator remains in memory.

(6) If we send a null pointer to delete operator, it is secure. Using delete to zero has no result.

(7) The statement delete x does not destroy the pointer x. It destroys the object associated with it.

(8) Do not apply C functions such as malloc( ), realloc( ) or free( ) with new and delete operators. These functions are unfit to object-oriented techniques.

(9) Do not destroy the pointer repetitively or more than one time. First time the object is destroyed and memory is released. If for the second time the same object is deleted, the object is sent to the destructor and no doubt, it will corrupt the heap .

(10) If the object created is not deleted, it occupies the memory unnecessarily. It is a good habit to destroy the object and release the system resources.

Table 11.3 shows the difference between new and malloc( ).

Table 11.3 Difference between new and malloc( )

new malloc( )
Creates objects
Returns pointer of relevant type
It is possible to overload newoperator
Allocates memory
Returns void pointer
malloc ( ) cannot be overloaded

11.4 Write a program to allocate memory to store 3 integers. Use new and delete operators for allocating and deallocating memory. Initialize and display the values.

# include <iostream.h>
# include <conio.h>


int main( )
{
  clrscr( );
  int i, *p;
  p = &i;
  p= new int[3];


  *p=2;      // first element

  *(p+1)=3;  // second element
  *(p+2)=4;  // third element

  cout <<"Value   Address";
  for (int x=0;x<3;x++)
  cout <<endl<<*(p+x)<<"\t"<<(unsigned)(p+x);


  delete []p;


return 0;
}

OUTPUT

Value Address
2 3350
3 3352
4 3354

Explanation: In the above program, integer variable i and pointer *p are declared. The pointer p is initialized with address of variable i and using new operator memory for three integers is allocated to it. The pointer *p can hold three integers in successive memory locations. The pointer variable is initialized with numerical values. The for loop is used to display the contents of the pointer *p delete operator releases the memory.

11.5 Write a program to allocate memory for two objects. Initialize and display the contents and deallocate the memory.

# include <iostream.h>
# include <conio.h>


struct boy
{
  char *name;
  int age;
};



int main( )
{
  clrscr( );
  boy  *p;


  p=new boy[2];
  p->name="Rahul";
  p->age=20;


  (p+1)->name="Raj";
  (p+1)->age=21;
  for (int x=0;x<2;x++)
  {
  cout <<"\nName : "<<(p+x)->name<<endl<<"Age  : "<<(p+x)->age;


  }
  delete []p;


return 0;
}

OUPUT

Name : Rahul
Age : 20
Name : Raj
Age : 21

Explanation: In the above program, structure boy is declared and memory for two objects is allocated to the pointer p. The pointer p is initialized and the first for loop displays the contents of the pointer on the screen. Finally, the delete operator de-allocates the memory.

11.4 HEAP CONSUMPTION

The heap is used to allocate memory during program execution i.e., run-time. In assembly language, there is no such thing as a heap. All memory is for programmer and he/she can use it directly. In various ways, C/C++ has a better programming environment than assembly language. However, a cost has to be paid to use either C or C++. The cost is separation from machine. We cannot use memory anywhere; we need to ask for it. The memory from which we receive is called the heap.

The C/C++ compiler may place automatic variables on the stack. It may store static variables earlier than loading the program. The heap is the piece of memory that we allocate. Figure 11.3 shows view of heap.

Local variables are stored in the stack and code is in code space. The local variables are destroyed when a function returns. Global variables are stored in the data area. Global variables are accessible by all functions. The heap can be thought of as huge section of memory in which large number of memory locations are placed sequentially.

Fig. 11.33 Heap review

As stated earlier all local variables are stored in the stack. As soon as the function execution completes, local variables are destroyed and the stack becomes empty. The heap is not cleared until program execution completes. It is the user's task to free the memory. The memory allocated from heap remains available until the user explicitly deallocates it.

While solving problems related to memory allocation, we always believe that there is large memory available and the heap is at no time short of memory. It is bad for a program to depend on such guess work which may cause an error in application. In C, the function, malloc( ) is used to allocate memory and if this function fails to allocate memory, returns null pointer. By checking the return value of function malloc( ), failure or success of the memory allocation is tested and appropriate sub-routines are executed.

C++ allows us to apply the same logic with new operator. C++ allows us to use two function pointers known as _new_handeleri and set_new_handler. The _new_handler holds a pointer to a function. It requires no arguments and returns void.

If operator new fails to allocate the memory requested, it will invoke the function *_new_handler and again it will attempt the memory allocation. By default, the function *_new_handler directly closes the program. It is also possible to substitute this handler with a function that releases memory. This can be accomplished by executing the function set_new_handler directly that returns a pointer to the handler.

11.6 Write a program to use set_new_handler function.

#include <iostream.h>
#include <new.h>
#include <stdlib.h>
#include <conio.h>

void m_warn( )
  {
     cerr << "\n Cannot allocate!";
     exit(1);
  }


int main( )
 {
     clrscr( );


     set_new_handler(m_warn);
     char *k = new char[50];
     cout << "\n First allocation: k = " << hex << long(k);
     k = new char[64000U];
     cout << "\Second allocation: k = " << hex << long(k);
     set_new_handler(0);  // Reset to default.


     return 0;
}

OUPUT

First allocation: k = 8fa40d48
Cannot allocate!

Explanation: In the above program, the set_new_handler is initialized with the function m_warn( ). The m_warn( ) displays warning message. The variable p is a character pointer and by using new operator, memory for 50 characters is allocated to the pointer p. The cout statement displays the starting address of the memory allocated.

Consider the statement p = new char [64 00 0U]. In this statement the new operator attempts to allocate memory to pointer p. In case the new operator fails to allocate the memory, it calls the setnew_handler and m_warn( ) function is executed.

11.5 OVERLOADING new AND delete OPERATORS

In C++ any time when we are concerned with memory allocation and deallocation, the new and delete operators are used. These operators are invoked from compiler's library functions. These operators are part of C++ language and are very effective. Like other operators the new and delete operators are overloaded. The program given next illustrates this.

11.7 Write a program to overload new and delete operators.

# include <iostream.h>
# include <stdlib.h>
# include <new.h>
# include <conio.h>


void main( )
{
  clrscr( );
  void warning( );
  void *operator new (size_t, int);
  void operator delete (void*);
  char *t= new (‘#’) char [10];
  cout <<endl<<"First allocation : p="<<(unsigned)long(t)<<endl;


  for (int k=0;k<10;k++)
  cout <<t[k];


  delete t;
  t=new (‘*’) char [64000u];
  delete t;
}


void warning( )
{
  cout <<"\n insufficient memory";
  exit(1);
}


void *operator new (size_t os, int setv)
{
  void *t;
  t=malloc(os);
  if (t==NULL)   warning( );
  memset(t,setv,os);
  return (t);
}


void operator delete(void *ss) {    free(ss);    }

OUPUT

First allocation : p=3376
##########
Insufficient memory

Explanation: In the above program, the new and delete operators are overloaded. The size_t is used to determine the size of object. The new operator calls warning( ) function when malloc( ) function returns null.

Consider the statement t=new(‘*’) char[64000u]. When memory allocation is requested by this statement, the new operator fails to allocate memory and calls the function warning( ). The delete operator when called releases the memory using free( ) function. Internally, in this program malloc( ) and free( ) functions are used to allocate and deallocate the memory.

11.6 EXECUTION SEQUENCE OF CONSTRUCTORS AND DESTRUCTORS

Overloaded new and delete operators function within the class and are always static. We know that static function can be invoked without specifying object. Hence, this pointer is absent in the body of static functions. The compiler invokes the overloaded new operator and allocates memory before executing constructor. The compiler also invokes overloaded delete operator function and deallocates memory after execution of destructor. The following program illustrates this concept.

11.8 Write a program to display the sequence of execution of constructors and destructors in classes when new and delete operators are overloaded.

#include <iostream.h>
# include <stdlib.h>
# include <string.h>
#include <conio.h>


class boy
{
  char name[10];
  public :
  void *operator new (size_t );
  void operator delete (void *q);
  boy( );
  ~boy( );


};


char limit[sizeof(boy)];

boy::boy( )

{
  cout <<endl<<"In Constructor";
}


boy::~boy( )
{
  cout <<endl<<"In Destructor";
}



void  *boy :: operator  new (size_t s )
{
  cout <<endl<<"In boy ::new operator";
  return limit;
}


void boy::operator delete (void *q)
{   cout <<endl<<"In boy ::delete operator"; }


void main( )
{
  clrscr( );
  boy *e1;
  e1 = new boy;
  delete e1;
}

OUPUT

In boy ::new operator
In Constructor
In Destructor
In boy ::delete operator

Explanation: In this program, the new and delete operators are overloaded. The class also has constructor and destructor. The overloaded new operator function is executed first followed by class constructor. We know that constructor is always used to initialize members. The constructor also allocates memory by calling new operator implicitly. Here, the new operator is overloaded. As soon as control of program reaches to constructor it invokes overloaded new operator before executing any statement in constructor.

Similarly, when object goes out of scope destructor is executed. The destructor invokes overloaded delete operator to release the memory allocated by new operator.

11.7 SPECIFYING ADDRESS OF AN OBJECT

The compiler assigns address to objects created. The programmer has no power over address of the object. However, it is possible to create object at memory location given by the program explicitly. The address specified must be big enough to hold the object. The object created in this way must be destroyed by invoking destructor explicitly. The following program clears this concept.

11.9 Write a program to create object at given memory address.

# include <iostream.h>
# include <constream.h>


class data
{
  int j;
  public:


  data ( int k)  { j=k; }
  ~data ( )        {  }


  void *operator new ( size_t, void *u)
  {
    return (data *)u;
  }


  void show( )   { cout<<"j="<<j;  }


};


void main( )
{
  clrscr( );
  void *add;
  add=(void*)0x420;
  data *t= new (add) data(10);
  cout<<"\n Address of object : "<<t;
  cout<<endl;
  t->show( );
  t->data::~data( );
}

OUPUT

Address of object : 0x8f800420
j=10

Explanation: In the above program, the operator new is overloaded. The void pointer *add is declared and initialized with address 0x420. The pointer object *p is declared and address is assigned to it. The new operator is also invoked to allocate memory. In the same statement constructor is also invoked and a value is passed to it. The member function show( ) displays the value of a member variable. Finally, the statement t->data::~data ( ); invokes the destructor to destroy the object. We can confirm the address of object by displaying it. The address of object would be same as the specified one.

11.8 DYNAMIC OBJECTS

C++ supports dynamic memory allocation. C++ allocates memory and initializes the member variables. An object can be created at run-time. Such object is called as dynamic object. The construction and destruction of dynamic object is explicitly done by the programmer. The dynamic objects can be created and destroyed by the programmer. The operator new and delete are used to allocate and deallocate memory to such objects.A dynamic object can be created using new operator as follows:

  ptr= new classname;

The new operator returns the address of object created and it is stored in the pointer ptr. The variable ptr is a pointer object of the same class. The member variables of object can be accessed using pointer and -> (arrow) operator. A dynamic object can be destroyed using delete operator as follows:

  delete ptr;

It destroys the object pointed by pointer ptr. It also invokes the destructor of a class. The following program explains creation and deletion of dynamic object.

11.10 Write a program to create dynamic object.

# include <iostream.h>
# include <constream.h>


class data
{
  int x,y;


  public:

data( )
{
  cout<<"\n Constructor ";
  x=10;
  y=50;
}


~data( ) { cout<<"\n Destructor";}


void display( )
{
  cout <<"\n x="<<x;
  cout<<"\n y="<<y;
}



};


void main( )
{
  clrscr( );
  data *d;       // declaration of object pointer
  d= new data;   // dynamic object


  d->display( );
  delete  d;     // deleting dynamic object
}

OUTPUT

Constructor
x=10
y=50
Destructor

Explanation: The d is a pointer object. The statement d= new data creates an anonymous object and assigns its address to pointer d. Such creation of object is called as dynamic object. The constructor is executed when dynamic object is created. The statement d->display( ) invokes the member function and contents of members are displayed. The statement delete d destroys the object by releasing memory and invokes destructor.

11.9 CALLING CONVENTION

Calling convention means how parameters are pushed on the stack when a function is invoked. Table 11.4 describes calling convention method of few languages.

Table 11.4 Calling convention

Language Order Parameter passed
Basic
Fortran
C
C++
Pascal
In order (left to right)
In order (left to right)
In reverse order (right to left)
In reverse order (right to left)
In order (left to right)
By address
By address
By value and address
By value, address and reference
As values

In C/C++ parameters are passed from right to left whereas in other languages such as Basic, Fortran calling convention is from left to right order. The following program explains calling convention of C++.

11.11 Write a program to demonstrate calling convention of C++.

!
# include <iostream.h>
# include <constream.h>


void main( )
{
clrscr( );
void show (int,int);
int x=2,y=3;
show (x,y);
}


void show (int x, int y)
{
  cout<<x;
  cout<<endl;
  cout<<y;
}

OUPUT
2
3

Explanation: In this program, integer variables x and y are initialized to two and three respectively. The function show ( ) is invoked and x and y are passed as per the statement show(x, y). The parameters are passed from right to left. The variable y is passed first followed by x. The values of variables with their position in the stack are displayed in Figure 11.4.

Fig. 11.4Stack with arguments

The value of right most variable is pushed first followed by other variables in sequence.

SUMMARY

(1) The memory model sets the supportable size of code and data areas.

(2) The object created and memory allocated by using new operator should be deleted and memory be released by the delete operator otherwise such mismatch operation may corrupt the heap or may crash the system. According to ANSI standard, it is a valid outcome for this invalid operation and the compiler should have routines to handle such errors.

(3) The heap is used to allocate memory during program execution i.e., run-time. The C/C++ compiler may place automatic variables on the stack. It may store static variables before loading the program. The heap is the piece of memory that we allocate.

(4) In C++, whenever memory allocation and deallocation is done the new and delete operators are used. These operators are part of C++ language and are very effective. Like other operators the new and delete operators are also overloaded.

(5) When constructors and destructors are executed, new and delete operators are invoked. When these operators are overloaded, the complier invokes these overloaded new and delete operators.

(6) An object can be created at run-time and such an object is called as dynamic object. The construction and destruction of dynamic object is explicitly done by the programmer.

(7) Calling convention means how parameters are pushed on the stack when a function is invoked. In C/C++ parameters are passed from right to left whereas in Basic, Fortran parameters are passed from left to right.

EXERCISES

[A] Answer the following questions.

(1) What are the different types of memory models?

(2) Explain the properties of new and delete operators.

(3) What do you mean by heap? Explain in detail about it.

(4) Explain overloading of new and delete operators.

(5) What do you mean by dynamic objects? How are they created?

(6) What do you mean by calling conventions?

(7) Explain the process of calling convention in C/C++.

(8) Explain the use of set_new_handler.

(9) Explain the difference between the operator new and malloc( ) function.

(10) What are segment and offset addresses?

(11) Explain the use of macros FP_SEG( ) and FP_OFF( ).

[B] Answer the following by selecting the appropriate option.

(1) The dynamic objects are created

(a) at run-time

(b) compile time

(c) both (a) and (b)

(d) none of the above

(2) The new operator is used to

(a) allocate memory

(b) deallocate memory

(c) delete object

(d) none of the above

(3) The delete operator is used to

(a) allocate memory

(b) deallocate memory

(c) create object

(d) none of the above

(4) In C/C++, when function is called, parameters are passed

(a) from right to left

(b) from left to right

(c) from top to bottom

(d) none of the above

(5) In C++, it is possible to pass values to function by

(a) call by value

(b) call by address

(c) call by reference

(d) all of the above

[C] Attempt the following programs.

(1) Write a program to create dynamic object.

(2) Write a program to allocate memory for 10 integers to a pointer. Assign and display 10 integers. Also, destroy the object.

(3) Write a program to invoke a function with parameters. How are parameters passed? Explain whether they pass from right to left or left to right.

(4) Write a program to create object at specified memory location.