<<

Memory management

László Daróczy Homework 1 - Solution

• Explanation - one possible solution

#include

//Function to test primes bool is_prime(unsigned long _num) { for(unsigned int i=2;i<_num;i++) { if(_num%i==0) return false;//Divisor found } return true;//No divisor found } int main(int argc, char** argv) { //Read n unsigned long N=0; std::cout << "How many primes do you want? " << std::ends; std::cin >> N; //Search method unsigned long found=0; //Number of found primes unsigned long tested=2; //Tested number while(found

• Memory management covers the allocation and deallocation of memory, decides where to allocate memory, the memory management scheme, etc. •Why is it important? ➡Memory is finite (more than you think!) ➡Memory operations require the most time •DDR3 2x8 GB 1600 MHz CL11 -> 100 EUR •DDR3 2x8 GB 1600 MHz CL7 -> 215 EUR ➡In parallel access to memory is even more important (race conditions!) ➡Special hardware have special requirements, e.g., GPGPU •Accessing global memory: 400-600 clock cycles! •Accessing in a specific pattern increases bandwidth 16-32x! Memory allocation

• Memory allocation happens in two different ways in C++: ➡automatic allocation: C++ defines, when and how the object will be allocated and the memory freed ➡compiler has full control over the process ➡allocation is done in the stack ➡dynamic allocation: the user has to take care to allocate and free the necessary memory ➡developer has full control over process ➡allocation is done on the heap

Best practice: - Most important rule of programming: do not overcomplicate things!!!! - if it works do not touch it!!!! Automatic allocation

• Automatic allocation by example: ➡memory is allocated at the definition ➡memory is freed at the end of the scope //… { //code… Memory is created for the object int a; ~object is constructed //code…

} End of scope: memory is freed ~object is destructed Automatic allocation

• Notes: ➡variable is only visible and accessible in its scope ➡automatic allocation is done in the stack!!!! ➡stack is a limited small memory location for programs (e.g., 1 MB)

- using too much automatic allocations can lead to crash due to stack overflow, e.g.,

void recursive(/*…*/) { double data[500]; //5000 byte stack //code… recursive(/*…*/); //Crash after 200 recursions //code… } Automatic allocation - arrays

• Array: ➡continuous (guaranteed by standard) segment of the memory containing several instances of the same object ➡can be allocated automatically or dynamically ➡automatic allocation is done in the stack! ➡by automatic allocation size of the array has to be (compile-time) constant! double array1[20]; //20 double const int N=60; int N2=6; N6+=N; long array2[N]; //60 long numbers long array3[N2]; //ERROR: non-const size Operations on arrays/1

•The elements of the arrays can be accessed using indices: double array1[20]; //20 double array1[5]=7.0; //Change element double test=array1[7]; //Read element

•You can read, write and access elements using the index operator Note: - C & C++ use indices starting from 0 - an array with 20 elements has index 0…19 - accessing element 20 results in undefined behavior! Computing the first moments

• Write a simple program to compute the first (central) moments of a series ➡which reads a single integer n from the user ➡reads n double values from user input ➡computes the first 5 central moments of the series 1 N µ = E [(X E(X))n] [X E(X)]n n i ⇡ N i i=1 ➡writes the results to the screen X ➡test the program with n=5 and 1.1; 2.1; 3.1; 4.1; 5.1 ➡(Result: 3.1; 2; 0; 6.8; 0) Computing the first moments

• Explanation #include #include int main(int argc, char** argv) { //Number of values int n; //Large array double series[2000]; //moments double moments[5]; //Read n std::cout << "Number of values in series: " << std::ends; std::cin >> n; //Read the values for (int i = 0; i < n; i++) { std::cout << "Value " << i + 1 << " in the series: " << std::ends; std::cin >> series[i]; } //To be continued Computing the first moments

• Explanation //Compute the values for (int order = 0; order < 5; order++) { double sum = 0.0; for (int i = 0; i < n; i++) { if (order == 0) sum += series[i]; else sum += pow(series[i] - moments[0], order + 1); } //compute the average moments[order] = sum / n; } //Print the values for (int order = 0; order < 5; order++) std::cout << "Moment of order " << order + 1 << ": " << moments[order] << std::endl; //Return 0 return 0; } Computing the first moments

• We do not get 0 as result for the moments due to the rounding errors! std::cout << "Moment of order " << order + 1 << ": " << moments[order] << std::endl;

std::cout << "Moment of order " << order + 1 << ": " << (fabs(moments[order])<1E-10 ? 0. : moments[order]) << std::endl; Computing the first moments

• We only get a few digits! #include #include #include int main(int argc, char** argv) { //Number of values int n; //Large array double series[2000]; //moments double moments[5]; //Read n std::cout << "Number of values in series: " << std::ends; std::cin >> n; //Use 12 digits if necessary std::cout << std::setprecision(12); Note: Stream manipulators

• Input/output streams have a state (~state machine) • State can be changed using manipulators, e.g., ➡std::setprecision(…) set decimal precision ➡std::showpoint show decimal point ➡std::skipws skip whitespaces ➡std::left adjust output to left ➡std::right adjust output to right ➡std::uppercase generate uppercase letters ➡std::scientific use scientific floating-point notation ➡and many others… Memory & addresses

• Each object is stored in the memory •The memory can be imagined as a (very) long street •On one side of the street there are land properties (e.g, 1 m2) Memory street 1121 Memory street 1122 Memory street 1123

•Each property has a unique address •There does not necessarily has to be something at a specific address (can be empty)! Memory & addresses

•If you are in front of a house, you can read the address from the shield! •If you have a large house, you need several landpieces!

•Only by knowing an address, you have no idea, what is there (or if anything is there!) ➡you have to visit & enter the piece of land to see what is there! ➡this costs time ➡if the property is not yours but you enter, the police will stop you! Memory & addresses

• The memory works exactly like that! •The memory is a long stripe (=street) of bytes (=land properties)

0x7fff5a0ca97c 0x7fff5a0ca97d 0x7fff5a0ca97e

•Each byte has a unique address •A memory location can store an object or can be free •The objects are the houses, the address is stored in a pointer •i.e., they point to a memory address Memory & addresses

•If you know the object (int a=7;), you can get the address using the address operator (&a) •If you have a large object (e.g., long), you need several bytes for the storage char int8_t

char long unsigned char

•Only by knowing an address, you have no idea, what is there (or if anything is there!) ➡you have to use the indirection operator (*pointer) to get the value ➡this costs time! ➡if the property is not yours (e.g., memory location of different program), but you enter, the operation system will stop you (and crash your program)! Memory & addresses

• Summarized: •When you have an object (e.g., int num=7;), it is stored somewhere in the memory ➡The object has a type (int), a name (num) and a value (7). •A pointer (p1) is a special variable, which stores the address of something (int * p1=&a;) ➡The pointer also has a name (p1), a type (int *) and a value (the address). ➡To get the value of the object stored at the address, use the indirection operator (*p1 will be 7). ➡To get the address of something, you can use the address operator (&a) ➡A pointer can also point to another pointer (int **). Memory & addresses

• Example - address of local objects

#include int main(int argc, char** argv) { //Create local variables int a=0;//Value is 0 int b=1;//Value is 1

int * p1=&a;//Address of a int * p2=&b;//Address of b //Print address std::cout << p1 << std::endl; std::cout << p2 << std::endl; //Print value!! std::cout << *p1 << std::endl; std::cout << *p2 << std::endl; return 0; }

0x7fff5a0ca97c 0x7fff5a0ca978 0 1 Accessing invalid memory

Note: • If you are using the indirection operator at a memory address, which does not belong to your program, the operation system will stop you and crash your software ().

Note: • If you are using the indirection operator, which belongs to your program, but not to the object you wanted to, you get strange errors (difficult to debug)! #include int main(int argc, char** argv) { long a=100; char c=’s'; //Create local variables char * p1=(char*)(&a); //Oh no, I meant a!!! std::cout << *p1 << std::endl; //Print value at location return 0; } //Output: d???? Fun fact

•Bug: error in the software •: finding and correcting the error • Origin of the term: •1940: a moth got stuck in the relay of the of Harvard •So they debugged the system… Note: void

• void is a special type in C++ •void means something has no type! •Why is it good? ➡if your function does not return any value, it can return nothing! void print_number(int value) { std::cout << value << std::endl; return; //Optional!!! } ➡if you want to store a memory address, but it is not important, what is stored there ➡E.g.,when you copy num bytes from one memory location to another

void * memcpy ( void * destination, const void * source, size_t num ); Now let’s have a coffee!

(10 minutes break) Dynamic allocation

• Dynamic allocation: the user has to take care to allocate and free the necessary memory ➡developer has full control over process ➡allocation is done on the heap (as large as the RAM) ➡when allocating a memory, C++ gives you the memory location, where you can store your object!

int * p1=new int; //We allocate a single integer *p1=7; //It has the value of 7

Best practice: - Do not allocate small objects one by one, e.g., every single integer - Allocation takes time!!!!!! Dynamic allocation

• Dynamic allocation: the user has to take care to allocate and free the necessary memory ➡pointers also have a type, which has to be consistent with the object it points to (double-> double*; int-> int *, etc.) ➡if you do not need the memory location, delete it ➡if you do not do so, the memory consumption is increasing, the memory is „leaking“ double * p2=double int; //We allocate a single double *p2=4.5; //It has the value of 4.5 //… computation delete p2; //Memory is not needed, give back to OS

Best practice: - Never mix the C++ variant (new-delete) with the C variant (malloc- free) - Never forget to delete the memory you allocated!!! - Only delete what you have allocated!!!!!!!!!!!! Dynamic allocation

• Allocation of small objects takes a long time ➡you can also allocate larger parts of the memory (array!) ➡the size of the array with dynamic allocation does not has to be constant!

int N=20+10; double * p2=new double[N]; //We allocate 30 double //… computation delete[] p2; //Memory is not needed, give back to OS Dynamic allocation

• Let’s examine pointers by an example!

#include int main(int argc, char** argv) { //Section 1: Example for simple pointers int num1=7; int num2=8; int * p1=&num1;//Points to num1 //Access value std::cout << "Value at" << p1 << ":" << *p1 << std::endl; //Same as num1 //Copy pointers int * p2=p1;//Also points to num1 std::cout << "Value at" << p2 << ":" << *p2 << std::endl; //num1 //Now different address p2=&num2; std::cout << "Now value at" << p1 << ":" << *p1 << std::endl; //num1 std::cout << "Now value at" << p2 << ":" << *p2 << std::endl; //num2 //Modify value through the pointer *p1=12; //num1 is now 12 std::cout << "num1=" << num1 << std::endl; //num1 //Section 2... return 0; } Dynamic allocation

• Let’s examine pointers by an example!

//Section 2... double num3=6.7; //Allocate object double *p3=new double; //Write & modify value *p3=5.6; (*p3)+=2.0; std::cout << "Value (p3) at" << p3 << ":" << *p3 << std::endl; //7.6 //Deallocate object delete p3; //Now point to different location p3=&num3; std::cout << "Value (p3) at" << p3 << ":" << *p3 << std::endl; //6.7 //Deallocate - should not do this - comment after test delete p3;//Comment out after test //Now change the address p3=(double*)(0x11f059b30922); //comment out after test //Results either in segmentation fault or in strange result std::cout << "Value (p3) at" << p3 << ":" << *p3 << std::endl; //Section 3... return 0; } Dynamic allocation

• Location of an array in memory •You can use the index operator or arithmetic operators to access an element: •array[5] is equivalent to *(array+5) •&(array[5]) is equivalent to array+5 •e.g., int array[9] in the memory … …

array[3] array[0] •The array resides on a continuous memory location • Automatically and dynamically allocated arrays behave similarly!!!! Dynamic allocation

• Let’s examine pointers by an example!

//Section 3... //Create & initialize int array[9]; for(int i=0;i<9;i++) array[i]=i; //Check, if memory is continuous for(int i=0;i<9;i++) std::cout << "Element " <

• When you call new, a memory allocator decides, where and how the object will be placed in the memory •It is possible to implement new memory allocators •Why is it good? ➡You want to place the object into a special place in memory (e.g., shared memory) ➡You want to store the object in an encrypted way ➡Large & small objects require different strategies Summary - Best practice

•Automatic allocation is done in the stack, dynamic allocation is done on the heap •The stack has very limited size •You should not allocate many small objects dynamically •If you use pointers, make sure you do everything correctly, otherwise strange errors and crashes will appear •For each new you should have a matching delete! •When using larger arrays, prefer dynamic allocation! •Avoid too many indirection and dereferences, they cost time! Homework Homework 2

• Step 1: Write a simple program, which • reads two integers from the user (n1,n2) • computes the first n1 prime numbers (2 is the first prime number) • allocates a matrix of size n1×n2 2 • fills up the matrix, so that element (i,j) is the product of the i-th prime number and j • computes the average & the standard deviation of the elements of the matrix • test the program with n1=35 and n2=47 • Step 2: Write a simple program, which • reads a single integer from the user (n) • computes & prints the first n lines of the Pascal’s triangle (binomial coefficients) • use a recursive function, which takes always the previous line as input • test the program with n=10 • Step 2: Write a more advanced prime detection program • reads a single integer from the user (n) • computes the first n prime numbers • the program should only test division of the new numbers by using the list of already computed primes! • test the program with n=10000 • Step 4: Send the C++ file and the output • Step 5: Read pages 1-48 & 67-150 from C++ Primer Next meeting

• Please finish the given task for the next meeting! • If you have any question, just contact me!

• Topics discussed next week: ➡ Introduction to object orientation & classes, constructor, destructor, member functions, member variables, access to member variables, friends, copy constructor, copy operator, operator overloading, example: vector class

• Next meeting: 11th of March, 9:00 Thank you for your attention!