📜 ⬆️ ⬇️

About memory management in C / C ++

On stackoverflow, a resonating question was asked with a recent post about C ++ redundancy. An extended answer was given by C # developer, Eric Lippert.

Question:

I have the following code:
int * foo() { int a = 5; return &a; } int main() { int* p = foo(); cout << *p; *p = 8; cout << *p; } 

')
It works without exception and gives
5 8

How is this possible? Is memory local variable available outside the function?

Answer:
You are renting a room in a hotel. You put the book in the top drawer of the bedside table and go to sleep. The next morning you leave the hotel, but you “forget” to hand over the key. You stole the key!

A week later, you return to the hotel, do not register, secretly crawl into your old room with your stolen key and open the box. Your book is still there. Incredible!

How is this possible? Is the contents of the bedside table available without renting a room?

Well, it is clear that this scenario can happen in reality without problems. There is no such mysterious force that will remove the book as soon as you leave the hotel. There is no such mysterious force that will prevent you from entering a room with a stolen key.

Hotel administration is not required to clean your book. You have not signed a contract with her that will oblige them to destroy your book after you leave. If you illegally enter your room with a stolen key in order to pick it up, the hotel security does not have to catch you. You have not signed a contract with them that says “if I try to get into my room, you must stop me”. But you signed a contract with them that says "I promise not to make my way into my room after discharge." And you violated this contract.

In this situation, anything can happen. The book can be in place if you are lucky. Someone else's book might be there, and yours in a hotel firebox. Someone can sit there at the moment when you enter, and tear your book to shreds. The hotel could remove the nightstand and put a wardrobe there. The hotel itself could be demolished and replaced with a football stadium and you will die from the explosion while you climb around.

You do not know what can happen - when you checked out of the hotel and stole a key to use it illegally later, you gave up your right to live in a predictable and safe world, because you decided to break the rules of the system.

C ++ is not a safe language . He will gladly give you break the rules. If you try to do something illegal and stupid, such as entering the room where access is denied, and climbing boxes that may not be there, C ++ will not stop you. Safer languages ​​solve this problem by reducing your capabilities. For example, more strict control of the keys.

Update
My God, this answer gets a lot of attention. I thought it would be appropriate to bring a little more technical points.

Compilers are engaged in generating code that manages the storage of data manipulated by this code. There are many different ways to generate code for memory management, but in the intervening time, two simple ways have gained ground.

The first is to have some kind of “long-lived” place where the “lifetime” of each byte cannot be easily predicted. The compiler creates a call to the heap manager, which knows how to dynamically allocate storage space as needed and free it when it is not needed.

The second way is to have some kind of “short-lived” place where the lifetime of each byte is well known, and specifically, the lifetime of such places follows the nesting pattern. That is, the memory allocation for the longest-lived among the short-lived variables clearly overlaps the memory allocation for the shorter-lived ones.

Local variables follow the second method. When a method starts execution, its local variables come alive. When a method calls another method, those local variables come alive. They die before the variables of the first method die. The relative order of the beginning and end of the life of a place to store variables can be calculated in advance.

For this reason, local variables are usually created on the stack, because the stack has a FILO property — the first one arrived, the last one left.

It’s as if the hotel has decided to rent out the rooms in succession, and you cannot check out until all the guests in the rooms with the number more than yours have left.

So let's think about the stack. In many operating systems, you get one stack per process and this stack has a fixed size. When you call a method, something is put on the stack. If you pass a pointer to a stack from your method, as the question author did here, then this is just a pointer somewhere in memory. In our analogy, when you check out of a hotel, you leave the room with the largest number. If no one is accommodated there, and you get there illegally, then your belongings will be guaranteed on the spot in this particular hotel .

We use stacks for temporary storage because they are very cheap and simple. An implementation of C ++ does not have to use a stack to store local variables — it can use a bunch. But she doesn’t do that, because it will make the program slower.

C ++ implementation is not obliged to touch your garbage in the stack, so that you can return to it illegally later. It is absolutely legal for the compiler to generate code that will reset everything in the “room” that you released. Again, he does not, because it is an expensive operation.

The C ++ implementation is not obliged to ensure that when the stack is compressed, its old addresses still point to the correct page of memory. She is allowed to tell the OS that “we are done with this page. Until I say otherwise, throw an exception that will kill the process if someone touches it. ” Again, implementations do not, because it is slow and not necessary.

Instead, implementations give you the opportunity to make mistakes without consequences. In most cases. Until one day, something really goes wrong and the process will explode.

This is problematic. There are many rules and it is very easy to break them by accident. I did it repeatedly. And what's worse, these problems come to the surface when the memory was corrupted for billions of nanoseconds, and it is already very difficult to find the culprit.

More memory-safe languages ​​solve this problem by limiting the possibilities. In "normal" C #, it is simply impossible to take the address of a local variable and return it somewhere or save it for future use. You can take the address of a variable, but the language is so cleverly designed that it is impossible to use this address after the death of its variable. To take the address and return it from the method, you need to translate the compiler into a special "unsafe" mode and include the word "unsafe" in your program to remind you that you are doing something dangerous and can break the rules.

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


All Articles