1 / 72

310201 Fundamental Programming

310201 Fundamental Programming. Pointers and Dynamic Memory. Topics for this Week : Week 9. Pointers, Dynamic Memory, Memory Addresses, New and Delete Operations. Address of Objects - & Operator. All C++ variables occupy memory within the computer.

durin
Télécharger la présentation

310201 Fundamental Programming

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 310201Fundamental Programming Pointers and Dynamic Memory.

  2. Topics for this Week : Week 9 • Pointers, • Dynamic Memory, • Memory Addresses, • New and Delete Operations.

  3. Address of Objects - & Operator • All C++ variables occupy memory within the computer. • All memory locations within the computer can be accessed by using a unique address to that segment of memory. • If, for instance, we made the declarations : int i = 8, k = 12; • then we have declared 2 integers : i and k, and assigned initial values to them.

  4. Memory Address (Will vary depending on your PC, setup, etc). Contents Name/Tag 0x118f2410 8 i 0x118f2412 12 k Address of Objects - & Operator (cont …) • Remembering the fact that standard integers in C++ require 2 bytes of memory, then the data in memory would look something like this :

  5. Address of Objects - & Operator (cont …) • C++ allows us to find out the memory address at which an object is located by using a special version of the ampersand & operator. • WARNING: The ampersand & operator is used in several different roles, for example, it is used in : • Pass by reference operator. i.e. Reference Parameter, • Bitwise AND, • Address of memory variable. i.e. an address of an object. • You must ensure that you are aware of which particular role the ampersand & operator is playing in the different parts of a program !

  6. Address of Objects - & Operator (cont …) • An & (ampersand) in front of an object name means “give me the memory address of the object” : int i = 8, k = 12; // Declare and initialise 2 integers. cout << "Memory address of i = " << &i << endl; cout << "Memory address of k = " << &k << endl; • The output from this program would be something like : Memory address of i = 0x118f2412 Memory address of k = 0x118f2410

  7. Pointers • An object that can store the address of another object is called a pointer. • Pointers are declared by using the asterisk * operator. • WARNING: The asterisk * operator can be used in different roles, for example, it is used in : • Multiplication of numbers, • Declaring a pointer, and, • De-referencing operator for pointers (more on this soon). • You must ensure that you are aware of which particular role the asterisk * operator is playing in the different parts of a program.

  8. Pointer and Address Example int i = 8, j =12; // Declare and initialise 2 integers. int *iPtr = &i; // iPtr is a pointer to an integer stored at address &i int *jPtr = &j; // jPtr is a pointer to an integer stored at address &j cout << "At address " << iPtr << " which is the same as " << &i << ", the value " << *iPtr << " is stored." << endl; cout << "At address " << jPtr << " which is the same as " << &j << ", the value " << *jPtr << " is stored." << endl; • The output from this program would be something like : At address 0x4e772430 which is the same as 0x4e772430, the value 8 is stored. At address 0x4e77242e which is the same as 0x4e77242e, the value 12 is stored.

  9. Pointer and Address Example (cont …) • In this example : • Two integers (i and j) are declared and given initial values. • Then, 2 pointers (iPtr and jPtr) are declared - using the asterisk * operator - and initialised to the addresses - using the ampersand & operator - of the 2 integers (i and j) respectively. • Then some lines of output are displayed using the pointers, the ampersand & operator, and the asterisk * operator. • The result of this is that iPtr contains the memory address the address of integer i. And, *iPtr contains the contents or value of the integer i. Similarly for j and jPtr.

  10. Pointer and Address Example (cont …) • The use of the ampersand & operator and asterisk * operator is very important in this example. • You cannot use these operators inter-changeability : • &P means the address of a particular object P. • *P means the contents of memory pointed to by P.

  11. Pointer and Address Example (cont …) • Consider the following : Name/Tag Stored in Memory Memory Address i ===========> 8 0x4e772430 *iPtr ========================> 0x4e772430 • where : • i is a direct reference to an address value. i.e. 8. • &i is the address of the value referred to by i. i.e. 0x4e772430. • iPtr contains the address of the value referred to by i. i.e. 0x4e772430. • *iPtr is the contents of the memory pointed to by iPtr, which is the contents of the memory location 0x4e772430, which is the value 8 – which, ofcourse, is the value of i.

  12. Pointer De-Referencing • In other words iPtr is an indirect reference to (in this case) the integer object referred to by i. • To obtain direct access to an object via an indirect reference we must use the asterisk (*) operator on the indirect reference. • This is called pointer de-referencing.

  13. Pointers in General • It so happens that the concept of pointers is probably only exceeded in its importance to modern computing by the concept of Object Oriented Programming (OOP). • In fact, without pointers, OOP could not be implemented ! • The concept of pointers could be argued as one of the single most important concepts in all of programming.

  14. Pointers and Strings • Consider the following pointer declaration : char *str_ptr = "HAL"; • This places the string "HAL" into an array pointed to by str_ptr, and automatically appends the null terminator ‘\0’ to the end. • That is : • An array of 4 characters is allocated ! • str_ptr points to the start of this array !

  15. Pointers and Strings (cont …) • If we do the following : cout << *str_ptr << endl; • then we would see "H" displayed onto the screen only - this is the memory location pointed to by str_ptr pointer. • Similarly : cout << str_ptr[0] << endl; • would also display just the "H" onto the screen, and once again this is the value at index location 0 of the array pointed to by str_ptr.

  16. Pointers and Strings (cont …) • However, the following : cout << str_ptr << endl; • would display the entire string "HAL" to the screen, and because we are using cout, output automatically stops at the null terminator ‘\0’.

  17. Pointer De-referencing • The asterisk (*) operator can be used as a modifier in declaring a pointer. • It can also be used in a different role to act as a de-referencing operator. • Before we can obtain direct access to an object via an indirect reference we must perform an operation on the indirect reference called pointer de-referencing.

  18. Pointer De-referencing (cont …) int num1, num2; // Declare two integers. int *nPtr = &num1; // Declare and initialise an integer pointer. num1 = 6; // Assigns 6 to num1. num2 = *nPtr; // Assign value of num1 to num2 via pointer de-referencing. *nPtr = 7; // Assign value 7 to num1 via pointer de-referencing. cout << "num1 = " << num1 << endl; cout << "num2 = " << num2 << endl;

  19. Pointer Operations - Initialisation • We have already seen that a pointer can be initialised to an address of an existing object. For example : int num = 2; int *numpointer = &num; • However, you cannot do this : double DoubleVar ; int *iPtr = &DoubleVar; // ERROR !! • This is not valid, because the pointer type must match the type of whatever it points at. • In this case, iPtr should be of type double like DoubleVar, instead of an int.

  20. Let’s Recap – with some exercises … • What would be the result of this code : int i; int *My_Ptr; My_Ptr = &i; *My_Ptr = 25; • Is this code valid ? Why ? int i = 25; int *My_Ptr = i;

  21. Pointer Operations - the NULL Address • It is valid to do this : char *cPtr = 0; // Initialise to the NULL address int *iPtr = NULL; int *kPtr = 0; • The NULL (or 0) address is a special value that can be assigned to a pointer to indicate that it is not currently pointing to any particular memory location. • This is not the same as the pointer variable being undefined. i.e. having no particular value.

  22. Pointer Operations - the NULL Address (cont …) • We can use the NULL memory address to see whether the pointer is defined before using it : int *iPtr = 0; ::::: other code to manipulate the pointer if (iPtr == 0) // iPtr is not pointing to a memory location else // iPtr is pointing to a memory location

  23. Pointer Operations - the NULL Address (cont …) • Or, instead of 0, we can use the NULL keyword directly :  int *iPtr = NULL; ::::: other code to manipulate the pointer if (iPtr == NULL) // iPtr is not pointing to a memory location else // iPtr is pointing to a memory location

  24. Pointer Operations - Assignment • As we have already seen, pointers can be used to assign values to the objects that they point at. • Once a pointer points at something, it can easily be changed to point at another object of the same type. • The statement : jPtr = iPtr; // Assignment. • has the effect of changing the memory address pointed to by jPtr, to the same memory address pointed to by iPtr.

  25. Pointer Operations – Assignment (cont …) int i = 8, j =12; // Declare and initialise 2 integers int *iPtr = &i, *jPtr = &j; // Declare and initialise 2 integer pointers cout << "Before pointer assignment :" << endl; cout << "\nAt address " << iPtr << ", the value " << *iPtr << " is stored" << endl; cout << "At address " << jPtr << ", the value " << *jPtr << " is stored" << endl; jPtr = iPtr; // Assignment. cout << "After pointer assignment :" << endl; cout << "\nAt address " << iPtr << ", the value " << *iPtr << " is stored" << endl; cout << "At address " << jPtr << ", the value " << *jPtr << " is stored" << endl;

  26. Pointer Operations – Assignment (cont …) • The output produced by this code would be something like : Before pointer assignment : At address 0x4faf24ac, the value 8 is stored At address 0x4faf24aa, the value 12 is stored After pointer assignment : At address 0x4faf24ac, the value 8 is stored At address 0x4faf24ac, the value 8 is stored

  27. Pointer Operations - Comparison • You may use the relational operators to compare two pointers as long as they are of the same type. •  For example, the following comparisons are legal : if (iPtr == jPtr) // as long as both pointers are same type. if (iPtr != 0) // check pointer is not pointing to NULL address. • You can also use of other comparison operators : >, <, >=, <=, etc, but these do not make much sense for pointers, because they contain memory addresses.

  28. Exercise #2 ** • What would be output by this code : int i = 3, k=3; int *iPtr = &i; int *kPtr = &k; if (iPtr == kPtr) cout << "Same"; else cout << "Different";

  29. Exercise #3 ** • What would be output by this code : int i = 3, k=3; int *iPtr = &i; int *kPtr = &k; if (&iPtr == &kPtr) cout << "Same"; else cout << "Different";

  30. Exercise #4 ** • What would be output by this code : int i = 3, k=3; int *iPtr = &i; int *kPtr = &k; if (*iPtr == *kPtr) cout << "Same"; else cout << "Different";

  31. Pointer Operations - Arithmetic • The various operators such as : ++, --, += , -= may also be used on pointers to enable us to rapidly move through memory locations, but be very careful where you start and finish in memory. • As with arrays, C++ will not warn or inform you if you go outside the memory locations allowed for the pointer.

  32. Pointer Operations – Arithmetic (cont …) • Here we are initialising an array - without even using the array's name : double dArray[10]; double *dPtr = dArray; // Points to the address of dArray[0]. for(int x = 0; x < 10; x++) { *dPtr = 0.0; // Set elements to zero dPtr++; // Increment memory location pointed at } // to point to next array element.

  33. Sizeof : Advanced • For pointer arithmetic it is sometimes useful to know the size of a particular object in memory. • The C++ sizeof function provides this information. • For example : sizeof (double); // Would return 8. sizeof (int); // Would return 2 or 4 depending on your // operating system / computer hardware. • sizeof returns the number of bytes of storage for a particular object or data type.

  34. Sizeof : Advanced (cont …) • This is also true for arrays : float fArray [20]; cout << sizeof (fArray); // Would output 20 x 4 = 80 cout << sizeof (fArray) / sizeof (float); // Would output 80 / 4 = 20 • As you can see from this example sizeof (fArray) returns the total number of bytes of memory reserved for the array. • In order to obtain the number of elements in the array, we need to divide this value by sizeof (float) !

  35. Sizeof : Structures and Arrays of Structures struct People { char Name [20]; // 20 characters = 20 bytes of memory char Address [50]; // 50 characters = 50 bytes of memory float Age; // 4 bytes of memory int Weight; // 2 bytes of memory // Total = 76 bytes of memory per object delcared. }; void main () { People Village [10]; // Declare array of 10 People cout << sizeof (People); // Would output 76 cout << sizeof (Village); // Would output 76 * 10 = 760 cout << sizeof (Village) / sizeof (People); // Would output 760 / 76 = 10 }

  36. Pointers, Strings, and (Ptr + i)[k] notation • As we have seen earlier, this code : char *str_ptr = "Hi There"; • declares array containing the text plus the null terminator '\0', and : cout << *str_ptr << endl; • would output just a "H". And : cout << str_ptr[0] << endl; • would also output just a "H".

  37. Pointers, Strings, and (Ptr + i)[k] notation (cont …) • However : cout << str_ptr[5] << endl; • would output the contents of location 5 (i.e. the 6th element) in the array. i.e. "e". • In a similar way : cout << (str_ptr + 3)[2] << endl; • would jump to the 3rd location in the string / array and then use this as the base of an array and jump to the 2nd location in this array, and this would output the letter "e" to the screen. This has the same effect as : cout << str_ptr[5] << endl;

  38. Pointers - HAL and IBM Example • Using pointers with array notation : char *str_ptr = "HAL"; for (int i = 0; i < 3; i++) { str_ptr [i] = str_ptr[i] + 1; } cout << str_ptr << endl;

  39. Pointers - HAL and IBM Example (cont …) • Or, we could do the same thing using pointers with pointer notation : char *str_ptr = "HAL"; for (int i = 0; i < 3; i++) { *(str_ptr + i) = *(str_ptr + i) + 1; } cout << str_ptr << endl;

  40. Pointer Arithmetic and Scaling • Arithmetic operations on pointers are scaled. • That is, when you perform pointer arithmetic, the compiler scales the result relative to the type of data being pointed to, rather than by bytes of memory. • Example : float *fPtr; // Floats occupy 4 bytes of memory !! ++fPtr; • The ++fPtr would increment the pointer by one float location. This new memory address will be 4 bytes away from its original location – not one byte ! • It is very important that you remember this scaling aspect of pointers !

  41. Exercise #5 ** • If we had : char *str_ptr = "HAL"; • What would result in each instance below : • cout << *(str_ptr + 0) << endl; • cout << *(str_ptr + 1) << endl; • cout << *(str_ptr + 2) << endl; • cout << *(str_ptr + 3) << endl; • cout << *(str_ptr + 4) << endl; • cout << *(str_ptr - 1) << endl; • cout << *(str_ptr – 15)[17] << endl; • Warning: Be extremely careful when using pointers !!

  42. Pointers and Call by Reference • Pointers can be used to implement a call by reference parameter passing mechanism. • In fact in C, the precursor of C++, this was the only method possible of passing parameters ! • Function to swap two integer values, and has 2 pointers as parameters : void swap (int *x, int *y) { int temp = *x; // temp = value in x *x = *y; // set x’s value to y’s *y = temp; // set y’s value to x’s original value } // Exercise : Why isn't temp a pointer ?

  43. Pointers and Call by Reference (cont …) void main() { int a = 10; int b = 20; swap (&a, &b);   // Exercise : Why use '&' ? cout << "\na = " << a << ", b = " << b << endl; }

  44. Pointers and Call by Reference (cont …) void main() { int a = 10; int b = 20; int *p = &a; // Exercise : What's happening here ? int *q = &b; swap (p, q); // Exercise : How does this work ?? cout << "\na = " << a << ", b = " << b << endl; }

  45. Pointers and the New and Delete Operations • When we allocate memory at compile-time, for example to an array object, we are allocating a fixed amount of memory which cannot be changed during program operation. The only way to change the array's size is to change our source code and re-compile our program. Such arrays are said to be static arrays. • For example, this code : const int Length = 20; double List [Length]; • declares an array of 20 double length floating point elements, with valid index locations 0 to 19. This array is fixed in size ! i.e a static array !

  46. Pointers and the New and Delete Operations (cont …) • To allow us to dynamically allocate memory (i.e. so we can declare an array of variable size) at run-time, we require the C++ new and delete operations. • The new operator dynamically allocates or reserves memory and has the form : new <Type of object> • The new operation returns a pointer and so is used in conjunction with a pointer variable. • If the request for run-time memory can not be granted then new operation returns the NULL memory address (i.e. zero), otherwise it allocates a block of memory large enough to hold the required object(s), and returns a pointer to the start of this allocated memory.

  47. Pointers and the New and Delete Operations (cont …) • When we declare a pointer that does not point to an existing object, then all this pointer can hold is a memory address, it cannot be used to store any real data until the new operation has been used. • Also, when we use the new operation, we should check the pointer's value to see if the memory allocation was successful.

  48. Pointers and the New and Delete Operations (cont …) double *dPtr; // Define a Pointer of type double. Uses no memory ! dPtr = new double; // Allocate 8 bytes of memory. i.e. sizeof(double). if (dPtr == 0) // Was the memory allocated successfully ? { cerr << "\n*** Out of Memory!\n"; // No ! exit(-1); // Exit the program. } cout << "\nEnter a double float value : "; cin >> *dPtr; // Dereference Ptr to store an input value. cout << "\nYou entered : " << *dPtr; // Dereference Ptr to display value.

  49. Pointers and the New and Delete Operations (cont …) • Since we do not have an inexhaustible amount of dynamic memory to allocate from the free store or heap we should always reclaim or unallocate any previously reserved memory when it is no longer required, and this can be done using the delete operation. • For example, we could reclaim / unallocate the memory previously allocated, via the new operation above, using the following : delete dPtr; // Deallocate memory reserved earlier.

  50. Pointers and the New and Delete Operations (cont …) • If we try and use or dereference the pointer after this delete operation, such as : cout << *dPtr; • then unpredictable results may occur. For example, you could access memory no longer reserved by this program and cause a system crash. • It is good practice to set the pointer to NULL memory address (i.e. zero) after the delete operation : delete dPtr; // Reclaim the memory. dPtr = 0; // Point to the Null - for safety ! • and then test that the pointer does not point to zero before using it.

More Related