Presentation is loading. Please wait.

Presentation is loading. Please wait.

Pointers and references

Similar presentations


Presentation on theme: "Pointers and references"— Presentation transcript:

1 Pointers and references
Lecture 3 Pointers and references

2 Introduction In Java: there are only primitive types and Objects. Primitives: allocated on the stack Objects: on the heap. In C++: primitives and objects may be allocated on the stack or on the heap. In C/C++: anything can be reached by holding an address to its location. The type of a variable that holds an address to a memory location is called a pointer.

3 Double Variable (8 bytes)
Introduction The main memory can be thought of as an array (of bytes). A pointer is actually only an index in this array (In essence: an int). Types (like int, long, double, etc.) usually require more than one byte. The pointer holds the address to the first byte in the sequence of bytes holding the value. Pointers hold the memory address of a primitive or an object which resides either on the heap or on the stack. Double Variable (8 bytes) Pointer

4 Introduction: example
The int value 7 will be stored: (32bit). Suppose that this value resides on the memory starting in the address , therefore the address of p equals to 51084; The value inside this address is 7 while the address itself is

5 Introduction Pointers are primitive types.
A pointer to a type type_a is of type type_a*, where type_a* is a primitive type, regardless of the type type_a. Supposed that ptr is a pointer to some memory of a variable. Reading this variable will be done by *ptr. In the previous example: the type of p is int, and the type of the address of p, that is &p, is int*

6 Example The following takes place:
A primitive of type int* is allocated. On main’s activation frame. The space allocated is associated with i_ptr. A primitive int is allocated on the heap. initialized to 10. The address of the allocated int is in i_ptr. The operator << of std::cout is passed the content (by value) i_ptr points to. That is: std::cout's operator << is called with the integer 10. #include <iostream> int main() { int *i_ptr = new int(10); std::cout << *i_ptr << std::endl; return 0; }

7 The memory structure

8 Another Example class Cow { private: int _id; public:
//member initialization list Cow(int id): _id(id) {} int getId() const { return this->_id; } void setId(int newId) { this->_id = newId; void moooo() const { std::cout << "moooo: " << this->_id << std::endl; };

9 Member initialization list
The constructor of the class can initialize the fields using a member initialization list. The member initialization is executed before the body of the function. It is possible to initialize members inside the constructor body but not advised because: Implicit call to default constructor - when a data member is itself a class object, not initializing it via the initialization list means implicitly calling its default constructor! If you do initialize it in the body of the constructor you actually initialize it twice. Const members of a class can only be initialized via member initialization list.

10 Member initialization list
The order the initialization happens is according to the order the member variables are declared (not the order in the initialization list). It is hence a convention to keep the order of the list as the order of the declaration.

11 Some more code details Whenever member methods of a class T are executed, this is always of type T*. this points to the location in memory in which the instance we are working on resides. The C++ operator -> is used to access the members and methods of objects via a pointer to the object. Accessing members / methods of an object not through a pointer is done using the . (dot) operator, as in Java.

12 Back to the example int main() { Cow bety(482528404);
Cow *ula = new Cow( ); bety.moooo(); ula->moooo(); return 0; } class Cow { private: int _id; public: //member initialization list Cow(int id): _id(id) {} int getId() const { return this->_id; void setId(int newId) { this->_id = newId; void moooo() const { std::cout << "moooo: " << this->_id << std::endl; };

13 Back to the example The following takes place:
Space for a Cow is allocated on the activation frame of the main function, and the constructor of Cow is called with  this points to the address of the space on the stack. The allocated space is associated with the variable bety. Space for a pointer Cow* is allocated on the activation frame of the main function. It is associated with the variable ula. Space for a Cow is allocated on the heap, and its constructor is called with  this points to the address of the space on the heap. The address of the newly allocated Cow is saved in ula int main() { Cow bety( ); Cow *ula = new Cow( ); bety.moooo(); ula->moooo(); return 0; }

14 Back to the example The following takes place:
Space for a Cow is allocated on the activation frame of the main function, and the constructor of Cow is called with  this points to the address of the space on the stack. The allocated space is associated with the variable bety. Space for a pointer Cow* is allocated on the activation frame of the main function. It is associated with the variable ula. Space for a Cow is allocated on the heap, and its constructor is called with  this points to the address of the space on the heap. The address of the newly allocated Cow is saved in ula int main() { Cow bety( ); Cow *ula = new Cow( ); bety.moooo(); ula->moooo(); return 0; }

15 Dereferencing a Pointer and the "Address Of" operator
We saw how to dereference a pointer, using the * operator: (*ula).moooo(); It is valid, since (*ula) is of type Cow, and can be accessed using the . (dot) operator. The same can be done with ula->moooo(); We sometimes would like to get the address of a variable. To this end there is the "address of" operator, &. int i = 10; int *i_ptr = &i; i_ptr holds the address in which i is stored on the stack.

16 Pointers arguments for a function
void inc(int *i_ptr){ (*i_ptr)++; } int i = 0; inc(&i); std::cout << i << endl; and the output will be 1.

17 const pointers Any type in C++ can be marked as const, which means that its value cannot be changed. Two roles: Hints the user that this value is not going to change. Prevents bugs caused by changing this variable by mistake. const pointer is declared by adding the const keyword after the type int *const i_ptr a const pointer to an int The pointer itself can not be changed – it must be set after such a decleration.

18 const pointers const int * x x is pointer to a constant integer
Can also be int const * x int * const x  x is a constant pointer to an integer int const * const x X is a const pointer to a const integer

19 Const methods of objects
When a method of an object does not change the object's internal state, we may define the method as const. It is good practice to mark each "logical" const method as const. When a function is declared as const, it can be called on any type of object. Non- const functions can only be called by non-const objects.

20 lvalues and rvalues Every C++ expression is either an lvalue or an rvalue. An lvalue refers to an object that persists beyond a single expression. An lvalue is an object that has a name. All variables, including non-modifiable (const) variables, are lvalues. An rvalue is a temporary value, that does not persist beyond the expression that uses it.

21 lvalues and rvalues: example
int main() { int x = 3 + 4; print(x); } x is an lvalue because it persists beyond the expression that defines it is an rvalue because it evaluates to a temporary value that does not persist beyond the expression that defines it. lvalues are values that can appear in the left side of an assignment while rvalue are the rest (you can write x=3+4 and not 3+4=x).

22 The concept of references
C++ supports the concept of References. Two types of references: lvalue references and rvalue references. lvalue references (this lecture): An lvalue reference is like a const pointer to an lvalue (without using any pointer notations). A reference may only be assigned once, when it is declared (which is called the initialization of the reference) It may not be altered to reference something else later.

23 Example: int i=0; int &i_ref = i; i_ref++;
std::cout<<i<<std::endl; The output, not surprisingly, is 1. We can have a reference to any type by adding & after the type. However, we cannot have references to references (this is illegal).

24 Const lvalue References
lvalue references can only accept lvalues. int foo() { return 42; } int x = 10; int& i = 4; //illegal - 4 is not an lvalue int& j = x + 1; //illegal - again - not an lvalue int& k = foo(); //not an lvalue too!! const int& s = foo(); //this works! wait what?

25 Const lvalue References
const int& s = foo(); //this works! wait what? This is a C++ feature… the code is valid and does exactly what it appears to do. Normally, a temporary object (i.e., an rvalue) lasts only until the end of the full expression in which it appears. However, C++ specifies that binding a temporary object to a reference to const on the stack lengthens the lifetime of the temporary to the lifetime of the reference itself, and thus avoids what would otherwise be a common dangling-reference error. The temporary returned by foo() lives until s get out of scope. This only applies to stack-based references.

26 Parameter Passing All parameters are either 'in' parameters or 'out' parameters. 'in' parameters are information passed to the function, which the function does not need to change. Any operation on an 'in' parameter are not visible outside of the function. 'out' parameters are meant as a side-channel from which the function may return information, in addition to the return value. Any changes made to 'out' parameters are visible outside the scope of the function.

27 Parameter Passing In Java there are only 2 forms of (implicit) passing parameters: primitives are passed by value (as 'in' parameters). Objects by reference (possible 'out' parameters). In C++ there are 3 forms for parameter passing. All of them explicit (the programmer must explicitly state which method to use). C++ allows passing parameters By value (as 'in' parameters). By pointer (either ‘in’ or ‘out’). By reference (either ‘in’ or ‘out’).

28 By value void byVal(int i, Cow mooo){ mooo.setId(i); } Cow hemda(20); byVal(30, hemda); std::cout << hemda.getId() << std::endl; The output is 20. When we call byVal, both 30 and the entire content of hemda are copied, and placed on the activation frame. byVal performs all of its operations on these local copies only

29 By Pointer If we don’t want to copy objects or want to change the parameter we can use pointers. void byPointer(int i, Cow *mooo){ mooo->setId(i); } Cow hemda(20); byPointer(30, &hemda); std::cout << hemda.getId() << std::endl; The output is 30. byPointer received a pointer to the location of hemda on the activation frame, and changed its id.

30 By reference (lvalue) When we wish to refrain from using pointers, which are inherently unsafe, we may use references. void byReference(int i, Cow &mooo){     mooo.setId(i); } Cow hemda(20); byReference(30, hemda); std::cout << hemda.getId() << std::endl; This code produces the same output as before (30), but we did not have to pass pointers. Moreover, the compiler is allowed to optimize the reference beyond the "const pointer" abstraction from above.

31 When to Use Each Form of Parameter Passing?
In parameters: When we are not willing to pay the cost of copying and constructing a new object we will use a const reference (or pointer). void func foo(Cow const& c_ref) Out parameters: we will use either by-reference or by-pointer but when is advisable to use each ?

32 Recommendations (common practices)
For In parameters: If the data object is small, pass it by value. If the data object is an array, use a pointer (that's the only choice). Make the pointer a const. If the data object is a good-sized struct, use a const reference. If the data object is a class object, use a const reference. For Out parameters: If the data object is a built-in data type, use a pointer or a reference. If the data object is an array, use your only choice, a pointer. If the data object is a struct, or a class object, use a reference. When receiving a pointer check for nullity. (A reference cannot be null.)

33 Returning Values From Functions
As in parameter passing, values can be returned either by value (copy), reference or pointer. When returning something by reference or pointer care should be taken not to return a reference or a pointer to the soon to be demolished activation frame. Cow& f(int x) { Cow c(x); return c; // THIS IS A TRAGIC MISTAKE // c would be undefined when the function returns. }

34 Another bad example g++ 1.cpp; ./a.out
0xbffff xbffff g++ 1.cpp -O1; ./a.out 0xbffff xbffff564 2 g++ 1.cpp -O2 ; ./a.out 0xbffff xbffff g++ 1.cpp -O3 ; ./a.out 0xbffff xbffff570 1 This is bad! we can not predict how our program will work! No flag is lifted for us, a.k.a no exception, no segmentation fault. It works every time differently. #include <iostream> int* f(){ int i = 1; cout << &i << endl; return &i; } void g(){ int k = 2; cout << &k << endl; void main() { int *i = f(); cout << *i << endl; g();

35 C++ arrays Let us start with a word of caution: unless you really need to, do not use arrays in C++. Use Vectors instead. We will see some reasons why later. Arrays in C++ are just blocks of continuous memory, which store data of the same type. The memory image of an array of integers which holds the number 5 in each cell looks like this:

36 C++ arrays Each cell is of size 4 bytes which corresponds to the size of int in this example.

37 C++ arrays Accessing cells of the array is done by dereferencing a pointer to the specific cell.  Suppose a pointer to start of an array: int *arr_ptr We can access the fourth cell by using: arr_ptr[3] Which is equal to *(arr_ptr+3) Notice the pointer arithmetic: We add 3 to arr_ptr, but we do not mean 3 bytes, but 3 ints. We add 3 times sizeof(int). This is true for every type of pointer: additions/subtractions are implicitly multiplied by the size of the data the pointer points at.

38 Arrays on the Heap Arrays may be allocated on the Stack or on the Heap.  Allocating an array on the heap is achieved using new [], and deallocating it is done by delete []. int *arr = new int[100]; std::cout << arr[2] << std::endl; delete [] arr; The output of this code will always be 0, since the new[] operator initializes the array's elements using their default constructor, which in the case of int initializes them to 0.

39 Arrays on the Heap Consider the code: Cow **cow_arr = new Cow*[100];
for (int i=0; i<100; i++) cow_arr[i] = new Cow(i); delete [] cow_arr; We initialize new Cow objects on the heap, store their pointers in cow_arr. When we call delete [], we expect that the array will be deallocated. However, each element in the array is a pointer - each individual Cows we allocated will not deleted! We need to delete each Cow manually – before deleting the array!

40 Arrays on the Stack To allocate an array on the Stack, the array's size must be known in advance. Cow cow_arr[5]; Each Cow will be initialized using its default constructor. We may also tell the compiler how to initialize individual Cows: Cow cow_arr[5] = {Cow(1), Cow(21), Cow(454), Cow(8), Cow(88)}; Accessing cells of the array on the Stack is achieved in the same as through a pointer. cow_arr is basically a pointer to the beginning of the array of Cows.

41 Another example Each cell of the array is initialized twice!
The first time is when the array is constructed, by the default constructor. The second time by using the assignment operator inside the loop.


Download ppt "Pointers and references"

Similar presentations


Ads by Google