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 computing 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 • 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 • 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 * 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 (Segmentation fault). 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 •Bug: error in the software •Debugging: finding and correcting the error • Origin of the term: •1940: a moth got stuck in the relay of the computer 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 • 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 " < • You can perform arithmetic operations on pointers as well •Example: int array[9]; int * pointer=array; //Points to array[0] •pointer++; //Points now to array[1] •pointer— — ; //Points now to array[0] •pointer+=4; //Points now to array[4]; •pointer-=2; //Points now to array[2]; •(pointer+3)-(pointer-1) //4 object difference Dynamic allocation • Let’s examine pointers by an example! //Section 4… //The same works with dynamic allocation as well //Create & initialize int * array2=new int[9]; for(int i=0;i<9;i++) array2[i]=i; //Check, if memory is continuous for(int i=0;i<9;i++) std::cout << " Element " < • It is possible to create multi-dimensional arrays as well ➡Automatic allocation: int matrix[4][4]; ➡Dynamic allocation: int ** matrix2=new int*[4]; for(int i=0;i<4;i++) matrix2[i]=new int[4]; //..code for(int i=0;i<4;i++) delete[] matrix2[i]; delete[] matrix2; ➡First memory for 4 addresses are allocated ➡Afterwards, repeatedly memory space for 4 integers are allocated, and the memory address is stored at the appropriate location ➡The lines of the matrix are not necessarily continuous Multi-dimensional arrays • It is possible to create multi-dimensional arrays as well matrix2 … … new matrix2[0]…matrix2[3] … … new new … … … … matrix2[0][0..3] matrix2[1][0..3] Multi-dimensional arrays • It is possible to create continuous multi-dimensional arrays? 1. Allocate space for 16 objects (buffer) … … 2. Create the matrix as normal matrix2 … … new matrix2[0]…matrix2[3] … … 3. Set the addresses manually e.g., matrix2[0]=buffer+0; matrix2[1]=buffer+4; Dynamic allocation • Let’s examine pointers by an example! //Section 5... //Automatic allocation int matrix[10][10]; for(int i=0;i<10;i++) for(int j=0;j<10;j++) matrix[i][j]=i*j; //Fill up //Allocate dynamically int ** matrix2=new int*[10]; for(int i=0;i<10;i++) { matrix2[i]=new int[10]; for(int j=0;j<10;j++) matrix2[i][j]=i*j; //Fill up } //Is it continuous? for(int i=1;i<10;i++) std::cout << " Mem offset:" << matrix2[i]-matrix2[i-1] << std::endl; 12 Deallocate for(int i="0;i<10;i++)" delete[] matrix2[i]; delete[] matrix2; Make it continuous int * buffer="new" int[10*10]; int ** matrix3="new" int*[10]; for(int i="0;i<10;i++)" { matrix3[i]="buffer+10*i;" Set address for(int j="0;j<10;j++)" matrix3[i][j]="i*j;" Fill up } Is it continuous? for(int i="1;i<10;i++)" std::cout << "Mem offset:" << matrix3[i]-matrix3[i-1] << std::endl; 10! *Deallocate* delete[] buffer; Note: memory allocator< p> • 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!