📜 ⬆️ ⬇️

Beginner Basics for Beginners

Introduction


Today, due to the improvement and cheapening of technology, the memory and computing power volumes are growing inexorably.

According to Moore's law:
The number of transistors placed on an integrated circuit chip doubles every 24 months.
It is worth noting that two parameters change:


The same principles are projected on the amount of RAM (DRAM).
')
Now the issue with memory is not acute, as the memory volumes over the past 10 years have increased 16 times per die.

Most High Level Programming Languages ​​(PL) already “from under the box” hide from the programmer the work with memory. And, since this question was asleep, a new caste of programmers appears who do not understand or do not want to understand how the work with memory is arranged.

In this topic, we will look at the main points of working with memory on the example of the C ++ language, because it is one of the few imperative languages ​​that supports direct work with memory and supports OOP.

What is IT necessary for?


Here it is worth making a reservation, this article is designed for people who are just starting their way in C ++ or just want to have an idea about dynamic memory.

During execution, any program reserves for itself a piece of memory in DRAM. All the rest of the DRAM free space is called "Heap" (English "Heap"). Memory allocation during execution for the needs of the program comes from a heap and is called dynamic memory allocation.

The whole trouble is that if you do not take care of clearing the allocated memory when it is no longer needed, so-called memory “leakage” may occur, with which your system (program) just hangs. It is akin to a car that died in the middle of the road, because someone forgot to fill it in time.

What you should know already
Most modern PLs are equipped with garbage collectors and clear their own memory.
However, C ++ has established itself as one of the fastest-performing PLs, in part because all the work with memory in it is done manually.


new and delete


Memory allocation can be static and dynamic. Static memory allocation is called a one-time memory allocation during program compilation, and the amount of static memory does not change during execution. A classic example is the declaration of an integer variable or array. But what if the programmer does not know in advance how many elements are required in the container?
The use of dynamic memory is advisable when it is necessary to organize the allocation of memory for the needs of the program as needed.
The operator new is responsible for allocating dynamic memory in C ++, and delete is responsible for clearing it.
The new operator returns the result of its work pointer to a new instance of the class.
The syntax is:

| data type (T1) pointer | * | pointer name | = new | type T1 |;

After the new operator, you can use a constructor, for example, to initialize the class fields.
It is worth noting that the same memory leak happens exactly when a programmer loses control over its allocation.
Important to remember:
If you forget about clearing the dynamic memory of the "spent" unnecessary elements, then sooner or later there will come a critical moment when taking the memory will simply nowhere.

An example of allocating memory and clearing it:
int main{ // ,       new int *ptr = new int(); //   cout<<*ptr<<endl; // ,     delete ptr; //  delete     ,         return 0; } 


This article will not discuss the so-called "smart" pointers, since the topic is very extensive, but, briefly speaking: "Smart pointers partially automate the process of clearing the memory for the programmer."

Pointers


Pointers are responsible for working with dynamic memory in C ++. This is the topic from which newcomers spoil their appetite.

You can declare a pointer using the operator * . By default, it will point to some random memory area. In order for us to access the memory area we need, we need to pass a reference (the & operator) to the desired variable.

The pointer itself is simply a memory address, and in order to reach the data stored in this cell, it must be dereferenced.

Important retreat


If you try to display a pointer without dereferencing, then instead of a value from the memory area to which it points, the address of that memory area is displayed.
To dereference a pointer, it is enough to put the operator * in front of its name.


 int main() { // ,          int* pNum= new int(1) ; cout<<*pNum<<endl; //    ,        ,       (   int   ) pNum++; cout<<*pNum<<endl; // ,         return 0; } 



Looking at such examples I would like to ask: “Why is it even necessary if you can immediately derive a variable?”

Another example:

We have a class called Programmers, which describes members of a team of programmers who are not aware of pointers.

  class Programmers{ public: Programmers(){} Programmers(int iWeight, int iAge){ this->weight = iWeight; this->age = iAge; } int weight; int age; }; int main() { //     Programmers int size = 9; Programmers *prog [size]; //  Programmers Programmers *ptr = nullptr; //     Programmers       //          for (int i =0;i<size;i++) { ptr=new Programmers(i+100,i); prog[i]=ptr; } return 0; } 

Thus, it is possible to manipulate memory as we please. And that is why, when working with memory, you can "shoot yourself in the foot." It is worth noting that working with a pointer is much faster, since the value itself is not copied, and it only assigns a link to a specific address.

By the way, this popular keyword this provides a pointer to the current class object. Everywhere these pointers.

An example of a pointer in everyday life:

Imagine the situation when you order a dish in a restaurant. To make an order, you just need to specify the dish on the menu and you will prepare it. Similarly, other visitors to the restaurant indicate the desired item on the menu. Thus, each line in the menu is a pointer to the cooking function of a dish, and this pointer was created at the design stage of the menu itself.

Function pointer example
 //      void Chicken(){ cout<<"Wait 5 min...Chicken is cooking"<<endl; } void JustWater(){ cout<<"Take your water"<<endl; } int main() { //    void   void (*ptr)(); ptr = Chicken; ptr(); ptr=JustWater; ptr(); return 0; } 



Let's return to our programmers. Suppose now we need to put the class fields in the private section, as befits the principle of encapsulation from OOP, then we need to getter to get read access to these fields. But let us imagine that we have not 2 fields, but 100, and for this you need to write your own accessor for everyone?

Spoiler
Of course not, I don’t even understand why you opened this spoiler.

To do this, we make an “accessor” of void type and pass arguments to it by reference. The point of passing an argument by reference is that the value of the argument is not copied, but only the address is passed to the real argument. Thus, when the value of such an argument changes, the data in the memory cell of the present argument will also change.
This also affects the overall performance, since passing an argument by reference is faster than passing by value. And that's not to mention the large collection of items.

For example, the getParams method inside will change the incoming arguments and they will change their values, including in the scope, where it was called from.
The pointer will help us navigate through the array. From the theory of data structures, we know that an array is a continuous area of ​​memory whose elements are located one after another.
This means that by changing the value of the pointer to the number of bytes that the element in the array occupies, you can reach each element until the pointer goes beyond the array boundaries.
Create another pointer that will point to the first element of the array of programmers .

 class Programmers{ public: Programmers(){} Programmers(int iWeight, int iAge){ this->weight = iWeight; this->age = iAge; } //    ,   main     void getParams(int &w, int &a){ w=weight; a=age; } private: int weight; int age; }; int main() { int size = 9; Programmers *prog [size]; Programmers *ptr=nullptr; for (int i =0;i<size;i++) { ptr=new Programmers(i+100,i); prog[i]=ptr; } int w,a; int count = 9; //    //        Programmers **iter = prog; for (int i=0;i<count;i++) { ptr = *iter++; ptr->getParams(w,a); if(*(iter-1) != nullptr){ delete *(iter-1); ptr = nullptr; } cout<<w<<"\t"<<a<<endl; } return 0; } 



In this example, I want to convey to you the essence of the fact that when you change the value of the pointer address, you can access another area of ​​memory.

Data structures such as lists, vectors, etc. are based on pointers, and therefore are called dynamic data structures. And to iterate over them, it is more correct to use iterators. An iterator is a pointer to a data structure element and provides access to a container element.

Finally


Having understood the topic of pointers, working with memory becomes a pleasant part of programming, and in general there is a detailed understanding of how the machine works with memory and how to manage it. In a certain sense, behind the very concept of "Working with memory" lies a certain philosophy. At your fingertips, you change the charge on the plates, even for very small capacitors.

Source: https://habr.com/ru/post/456318/


All Articles