Docsity
Docsity

Prepare for your exams
Prepare for your exams

Study with the several resources on Docsity


Earn points to download
Earn points to download

Earn points by helping other students or get them with a premium plan


Guidelines and tips
Guidelines and tips

Understanding Pointers, References, and Dynamic Memory Allocation in C: Examples, Study notes of Computer Programming

An in-depth explanation of pointers, references, and dynamic memory allocation in c. It covers the concept of pointers, their usage, call by reference, and dynamic memory allocation using the new operator. The document also discusses the importance of de-allocating memory and the consequences of not doing so.

Typology: Study notes

2011/2012

Uploaded on 08/06/2012

anchal
anchal 🇮🇳

4.6

(9)

103 documents

1 / 13

Toggle sidebar

Related documents


Partial preview of the text

Download Understanding Pointers, References, and Dynamic Memory Allocation in C: Examples and more Study notes Computer Programming in PDF only on Docsity! 469 r Lecture No. 39 Reading Material Deitel & Deitel - C++ How to Program Chapter. 5, 6, 7 Revise old topics Summary 51) Pointers 52) References 53) Call by Value 54) Call by Reference 55) Dynamic Memory Allocation 56) Assignment and Initialization 57) Copy constructor 58) Example 59) Rules for Using Dynamic Memory Allocation 60) Usage of Copy Constructor 61) Summary 62) Exercise In this lecture, we will review the concepts like pointers, references and memory allocation, discussed in the previous lectures. The review of these topics will help enhance our understanding of the programming. Let’s discuss these topics one by one. Pointers Pointer is a special type of variable that contains a memory address. It is not a variable that contains a value, rather an address of the memory that is contained inside a pointer variable. In C++ language, variables can be without type. Either we can have a void pointer or these can be typed. So we can have a pointer to an integer, a pointer to a character and a pointer to a float etc. Now we have a user-defined data type, which we call classes, so we can have pointers to classes. docsity.com 470 While talking about pointers, we actually refer to pointing to an area in memory. A pointer to an integer, points to a location opted by an integer in memory. If there is an array of integers in the memory, we can still use a pointer to point to the beginning of the array. This is a simple manipulation. To have the address of a memory location, we use & sign. The ‘&’ sign is also used in references. So we have to be very cautious while making its use. To further overcome this ambiguity, we will now recapture the concept of reference. References A reference can be considered as a special type of pointer as it also contains memory address. There are some differences between pointers and references. Pointers may point to nothing while references always have to point to something. A reference is like an alias for an object or a variable. The references should be used when we are implementing the call by reference. This helps us make our syntax easier as we can implement the call by reference with out using the * operator. Call by Value Whenever we call a function and pass an argument, an object or variable to the function, then by the default rule of C and C++, it is a call by value. It means that the original data remains at its place and a temporary copy of it is made and passed to the function. Whatever the function does with this copy, the original value, in the calling function, remains intact. This is a call by value. Call by Reference If we want a function to change something in the original object variable or whatever, that variable or object by reference would be passed. To do this, we don’t make temporary copy of that object or variable. Rather, the address of the variable is sent. When the function manipulates it, the original object will be manipulated, effecting change in its values. The use of call by reference is also important for the sake of efficiency. If we have a large object, sending of its copy will be something insufficient. It will occupy a large space on the stack. Here, we can use call by reference instead of call by value only for efficiency while we need not to change the original object. For this, we use a keyword const that means that a const (constant) reference is being passed. The function can use its values but cannot change it. Now we come to the dynamic memory allocation. Dynamic Memory Allocation In C language, we have a method to allocate dynamic memory. In it, while executing the program, we allocate some memory from the free store (heap) according to our need, use it and after using, send it back to the free store. This, dynamic memory allocation, is a very common function in programming. While writing C++ language, it was decided that it should not be implemented by a function call. There should be native operators, supposed to be very efficient. These operators are new and delete. We allocate memory with the new operator from the free store. It returns a pointer. For example, if we say p is a pointer to an integer, the statement will be written as int *p ; Now we write p = new int ; docsity.com 473 delete [] m ; This statement frees the allocated memory (whatever its size is) that is being pointed by m. Assignment and Initialization Let us discuss the assignment and initialization. We do initialization as int i = 0 ; This is declaring an integer and initializing it. The second way to do this is int i ; i = 0 ; This has the same effect as the first statement but the behavior is different. At first, a space is allocated for i before assigning a value to it.. The same applies whenever we create an object. We can either create an object, initialize it at the creation time that means constructor is being called, or we can create an object and then assigns values to its data members later. This is usually done either by set functions or with the assignment statements. Here the thing to differentiate is that if we have two objects of a class, say Matrix, m1 and m2. We have, in some way, created m1, its rows and columns have been allocated, and values have been put in these rows and columns. Now somewhere in the program, if we write m2 = m1 ; It is an assignment statement. If we have not defined the overloaded operator for assignment, the default assignment of C will be carried out. The default assignment is a member-to-member assignment. Now let’s again look at the construct for the Matrix class. In it, we have only one data member i.e. a pointer to an integer. We have written int *m ; in the class definition. So in m1, there is a pointer to an integer but m1 is a fully qualified developed object. It is, let’s say, a 5 x 5 matrix and has values for its entities. Now when we write m2 = m1 ; m2 has also a pointer m to an integer as its own data member. If we have not written an overloaded assignment operator for this class, the value of m of the object m1 will be assigned to m of the object m2. Now we have two objects, having pointer variables that hold the same address. So these are pointing to the same region in the memory. There arise many problems with this. Firstly, if we destroy m1, the destructor of m1 is called and it frees the memory. So the memory being pointed by m of the object m1 has gone back to the free store. Now what happens to m2? There is a pointer in m2, pointing to the same memory, which has been sent to the free store by the destructor of m1. The memory is no longer allocated. It has been sent to the free store. So we have a serious problem. We don’t want two objects to point to the same region in the memory. Therefore, we write an assignment operator of our own. This assignment operator is such that whenever we do some object assignment, it allocates separate docsity.com 474 memory for one object and copies the values of the second object into that space. Thus the second object becomes an independent object. So we have to be careful. Whenever we have a class with a dynamic memory allocation, there is need for writing an assignment operator for it. Copy Constructor Now we will see what a copy constructor is? We have discussed the dangers that a programmer may face during the process of assigning the objects. The same danger comes to the scene, when we pass an object like the Matrix class to a function that does some manipulations with it. Suppose, we have a function that takes an object of a class Matrix as an argument. The default mechanism of calling a function in C or C++ is call by value. Now what is the value for this object, being passed to the function? The values of the data members of the object will be placed on the stack. The function will get a temporary object, as it is a call by value. The original object remains intact. The values of data members of that temporary object will be the same as the values in the original object. Now if it is a simple class, there will be no problem. However, if there is a class with a pointer as its data member and that pointer has allocated some memory, then the value of that pointer is copied. This value is passed to the function and not the memory. Now in the function, we have a temporary object that has a pointer as data member, pointing to the same memory location as the original object. If we are just reading or displaying that object, there is no problem with it. If we change the values of the object, the values in the temporary object get changed. However, when we manipulate the pointer, it changes the values in the memory of the original object. This change in the original values is the mechanism of the call by reference and here we have done call by value. We know that a temporary object is passed to the function. So how we get around this problem? The way to resolve this problem is to create a complete copy of the object. We want that in this copy of the object the pointer value must not point to the same memory location. Rather, it must point to the memory location of its own. So we want to have a copy constructor. That means a constructor that will create a new object with a full copy of the other object. This is also known as deep copy as opposed to shallow copy. The shallow copy makes a member-to-member copy of the object without taking care whether the pointer is a pointer or an ordinary variable. The copy of the ordinary variables is perfectly valid and legal. But if we make a member copy of a pointer, there may be the problem in which a new pointer variable is created with the same value, without creating a new area of memory. Therefore, we have to write something special that is called copy constructor. The basic line in the syntax of the copy constructor is that we are trying to create a new object by passing an object of the same class as the argument. So we should think of a constructor. For example if we have the class Matrix, its prototype will be as under. Matrix (Matrix &) ; The ‘&’ sign shows that a reference of the object of type Matrix will be passed. Now whenever we write a copy constructor, there is need to be very cautious. We have to allocate a new memory for that copy. When we go into this copy constructor, at first, it is determined that how much memory the original object has allocated? Since we pass the object, so all the queries might have answers. For example, in case of Matrix class, we can find the number of rows and columns. We create a temporary object docsity.com 475 inside the constructor and allocate it a memory. Then the values of memory of the original object are copied into this memory. Now the pointer of this new object will point to this new location. As the constructor returns nothing and just creates a new object, so there is no return statement in the constructor. Thus, this copy constructor is completed. The syntax of this constructor is given below Matrix::Matrix ( const Matrix &other ) { size = other.size ; // size is a function to determine the memory allocated by object m = new int [size] ; copyvalues ( m, other ) ; } In this case, it creates a new object that actually creates memory. It does not make the shallow copy so there is no copy of the pointer value. In fact, the pointer has a new value. But the values pointed to by this pointer (i.e. the values in the new allocated memory) are the copy of the values in the memory of the original object, and then this fully constructed object is returned. Now we have the facility of copy constructor. With it, we can define new objects based on the existing objects. This copy constructor is necessary for the objects in which the memory is allocated dynamically. We can use this copy constructor without causing any problems. Suppose we have written as Matrix a (3,3) ; We have defined a constructor that takes two arguments rows and columns, as a matrix ‘initializer’. We allocate memory for these rows and columns and create an object. Then somewhere, later in the program, We write Matrix b (a) ; This statement makes a complete copy of already created object a and a new object b is created. This new object has its own pointer and own memory allocation. This memory location is different from the memory allocated inside the matrix a. Now if a dies then b is still a valid object. So a copy constructor is critically useful. It is used when we want to create a duplicate copy of an object. It is always used whenever we want to do a call by value into a function to which an object is being passed, and the object is of a class in which we have allocated the memory dynamically. Example Let’s look at an example to further understand these concepts. We write a class String. It has a data member c that is a pointer to a character. We write a member function (copy function) of the class, which can copy values in the character array of the class. There is a constructor that will allocate a space for the character arrays i.e. string. The starting address of this array will be stored in data member of the class i.e. in a pointer. We have not written any copy constructor. Now we want to make two objects of this String class, say, s1 and s2. We create object s1 and assign a string to it. We write it as docsity.com 478 The following is the output of the program which shows the use of copy constructor. The string of s1 is test1 The string of s2 is test1 The string of s1 is A new string The string of s2 is test1 The other affected part is the assignment operator itself. We know that there are dangers in the assignment operators of a class in which memory is being allocated. We cannot write an assignment operator for such a class blindly. When we write an assignment operator for such a class, that operator must first look at the fact whether there is self-assignment being done. Suppose we have an integer i. We have written as int i ; i = 10 ; And down to this we write i = i ; There is no problem with it. It is a legal statement. It is complicated if we do such assignment through pointers. In such a case, pointer is pointing to itself and even it has no problem. But when we do this with objects that have allocated dynamic memory, the method of assignment is changed. Let’s take the example of Matrix. We have an object of Matrix, say m1, which has three rows and three columns. Another object, say m2, has five rows and five columns. Somewhere in the program we write m1 = m2 ; Here m2 is a big object while m1 is a small one. We want to assign the big object to the smaller one. The assignment operator for this type of class, first de-allocates the memory reserved by the left-hand side object. It frees this by the delete operator. Then it will determine the memory required by the object on right hand side. It will get that memory from the free store by using new operator. When it gets that memory, it will copy the values and thus the statement m1 = m2; becomes effective. So assignment has a requirement. Now if we say m1 = m1 ; We have defined assignment operator. This operator will delete the memory allocated by m1 (i.e. object on L.H.S.). Now it wants to determine the memory allocated by the object on the right hand side, which in this case, is the same i.e. m1. Its memory has been deleted. So here we get a problem. To avoid such problem, whenever we write an assignment operator, for objects of the class that has done memory allocation. After this, we do other things. We have discussed the example in which we create an object of Matrix. We create it using a copy constructor by giving it another object. The syntax of it we have written as Matrix m2 (m1) ; This is the syntax of creating an object based on an existing object. We can write it in the following fashion. Matrix m2 = m1 ; While this statement, we should be very clear that it is not an assignment only. It is also a construction. So whenever we are using initialization, the assignment operator docsity.com 479 seems as equal to operator. But actually assignment operator is not called. Think about it logically that why assignment operator is not called? The assignment operator is called for the existing objects. There should be some object on the left- hand side, which will call the assignment operator. When we have written the declaration line Matrix m2 = m1 ; The m2 object has not constructed yet. This object of Matrix does not exist at the time of writing this statement. So it cannot be calling the assignment function or assignment operator. This is an example of the use of a copy constructor. Thus, there are two different ways to write it. Remember that whenever we create an object and initialize it in the declaration line, it calls the copy constructor. Let’s talk about another danger faced by the programmers when they do not provide copy constructor. The ordinary constructor is there which allocates memory for the objects of this class. Suppose we do a call by value to a function. Here, we know that a temporary copy of the object is made and provided to the function. The function does manipulations with this copy. When the function returns that temporary copy is destroyed. As no copy constructor is there, a shallow copy, with values of pointers, is made. The destructor should be there as we do memory allocation in the class. Now suppose that there is a destructor for that class. Now when this temporary object destroys its destructor executes and de-allocates the memory. Now as it was a shallow copy so its pointers were pointing to the same memory as of the original object. In this way, actually, the memory of the original object is de-allocated. So the pointer of the original object now points to nothing. Thus, in the process of function call, we destroyed the original object as it is an invalid object now. Its pointer is pointing to an unknown memory location. This is a subtle but very critical. This can be avoided by providing a copy constructor, which actually constructs a fully formed object with its own memory. That temporary object will go to the function . When it is destroyed, its destructor will de-allocate this memory. However, the original object will remain the same. Rules for Using Dynamic Memory Allocation Whenever we have a class in which we do dynamic memory allocation, there are some rules that should be followed. First, we must define a constructor for it. Otherwise, we will not be able to carry out dynamic memory allocation. This constructor should be such that it gets memory from the free store, initializes the object properly, sets the value of the pointer and returns a fully constructed object. Secondly, we must write an assignment operator for that class. This assignment operator should first check the self-assignment and then make a deep copy.. So that a properly constructed object should be achieved.. Thirdly, as we are doing dynamic memory allocation in the constructor, it is necessary to provide a destructor. This destructor should free the allocated memory. These three rules are must to follow. docsity.com 480 Usage of Copy Constructor Let us see where the copy constructors are being used? First, it is used explicitly at some places, where we write Matrix m2 (m1) ; This is an explicit call to a copy constructor. Here m1 is being passed by reference. If we want that there should be no change in m1, then it is necessary to use the key word const with it to prevent any change in m1. The presence of this key word means that the constructor will do nothing with the object, being passed to it. The use of copy constructor in this explicit way is clear. The second way to use the copy constructor is by writing the declaration line as Matrix m2 = m1 ; Here the use of copy constructor is not clear. It is not clear by the statement that copy constructor is being used. It seems an assignment operator is being used. Be careful that it is not an assignment operator. It is a copy constructor. The third use of the copy constructor is calling a function and passing it an object by value. If we have provided a copy constructor, it will be called automatically and a complete temporary copy (with memory allocation) of the object is given to the function. If we do not provide copy constructor, the call by value functions will create problems. In the function, if we change any value of the object, it will change the value in the original object. In function calling, when we do the call by value, the copy constructor is called. On the other hand, in call by reference, copy constructor is not called and the address of the original object is passed. Summary A pointer is a variable that holds memory address. The & operator is used to get the address of a variable or an object. The & sign is also used as a short hand for a reference. Whenever we have a & sign in the declaration, it implies a reference. Whenever we have & sign on right hand side of an assignment operator, it is taken as address of an object. We can do dynamic memory allocation by using pointers. In C++ language, we have two very efficient operators provided which are new and delete. We use the new operator for obtaining memory from the free store. It returns a pointer to the allocated memory. We store the value of this pointer in a pointer variable. In a class, which allocates memory dynamically, there is a data member i.e. a pointer. When we create an object of the class at run time, it will allocate memory according to our requirement. So there is no waste of memory and the situations in which we want to store large data in small memory or vice versa are prevented. So we do dynamic memory allocation inside these classes. Whenever we have dynamic memory allocation inside a class, we have to provide few things. We must provide a constructor that does the memory allocation for us producing a well-formed object. docsity.com
Docsity logo



Copyright © 2024 Ladybird Srl - Via Leonardo da Vinci 16, 10126, Torino, Italy - VAT 10816460017 - All rights reserved