Why is the memory divided into heap and stack? Can only one model be used? Why does each thread have a separate stack?

Reprinted from "https://mp.weixin.qq.com/s/Yh2prf3U2qbNFyH_1viitQ"
In fact, we all know that computer memory is originally a piece of memory and there is no stack.

When we were learning programming, we should have all heard the sentence "If you still want to access that piece of data after the program ends, you must use the heap (if it is not released, the data modified by the program will still exist)". I think this is actually the topic of this topic. The key is that heap and stack have their own uniqueness. Maybe you know these two things, but I still explain it to avoid other friends not knowing when they look at the answer.

All threads in a Linux process share the address space of the process, but they have their own independent (private) stack. The allocation of heap is different from that of stack. Generally, a process has a C runtime heap, and this heap is shared by all threads in the process. (All threads under a process have their own independent stack, but the heap is the heap of the shared process)

Stack: As I said in the first sentence, there is no such thing as a stack, but with the emergence of programming languages, there is a concept of "function", and these functions can call each other (just like we pass things, such as : Hu Xiaoran passes things to Hu Xiaoran 2 and passes things to Hu Xiaoran 3. Afterwards, the transfer result needs to be fed back from the back to the front. We can understand this transfer process as a call), then there is a distinction between before and after. This is the call queue. Then What is the characteristic of this queue? That is, the one who is called into the queue first will go out last, which is what we often call first in, last out (FILO). Then the stack will appear, and it also has a characteristic that the thread is independent. Yes (so its running status and local automatic variables and temporary variables can be stored), the life cycle is with the thread, and the corresponding stack ends when the thread ends (the stack is initialized when the thread starts, and the stack of each thread is independent of each other, therefore, The stack is thread safe. The operating system will automatically switch the stack when switching threads, that is, switching the SS/ESP register. The stack space does not need to be allocated and released explicitly in high-level languages.). Of course, what I mean is a memory stack. In fact, a "stack" is a data structure, a linear table that limits insertion and deletion operations only to the end of the table. Isn't this feature exactly in line with the FILO I just mentioned. So you can understand the concept of the memory stack in C++ or Java (jvm), that is, the author of the programming language uses the data structure "stack" to manage memory (to be more detailed, the modern CPU architecture determines that the stack is the management The best data structure for function calls and local variables. Because the CPU already provides ready-made instructions).

picture

Heap: It can be regarded as a special data structure, like the binary tree we often use. The memory heap is simpler to explain. It is a piece of memory that can be allocated freely. It is a space shared by everyone and is divided into a global heap and a local heap. The global heap is all unallocated space, and the local heap is the space allocated by the user. The heap is allocated when the operating system initializes the process. You can also ask the system for additional heap during operation, but remember to return it to the operating system when it is used up, otherwise there will be a memory leak. It allows the program to dynamically apply for a memory space of a certain size during runtime. For example, the programmer applies for a piece of memory from the operating system. When the system receives the application from the program, it will traverse a linked list recording free memory addresses to find the first The heap node whose space is larger than the requested space is then deleted from the free node list, and the space of the node is allocated to the program. Its characteristics are that the allocation speed is slow, the addresses are discontinuous, easy to fragment, and are applied for by the programmer. At the same time, the programmer must be responsible for destroying them, otherwise it will cause memory leaks. In a high-level language like Java, we don't have to worry about memory recycling. That's because the jvm is already handling it for us.

picture

Different threads have different stack space and shared heap space. Each thread can indeed create a new memory space in the heap. This method is called Thread Local Storage (TLS), but the heap space created by a thread can also be accessed by other threads. For example, a thread uses Object* o = new Object() to create a new object. This object is in the heap, and the pointer o is in the stack. As long as this thread sends the value of the pointer o to other threads, then other threads Use another pointer p to receive, then other threads can still access the new object of this thread. This is the explanation of shared heap space.

It is generally not recommended to use new in a thread to open up a new memory space, because the heap is shared, so when one thread uses new, all other threads have to stop and wait, which incurs a huge synchronization cost. If there are m threads Each thread allocates n-sized memory to the heap, which requires m memory allocation operations, and all threads need to wait m times. Remember that every new operation is time-consuming. We can only allocate memory of m*n size to the heap once, and then each thread accesses the part it needs to access. This only requires one memory allocation, and there is no synchronization cost for subsequent operations.

Having said so much above, I just want to explain the meaning and role of the memory stack and memory heap, so the answer comes out, that is, we cannot "use only the heap or all only the stack", so that the calls of our programs and the storage of data will be problem appear.

Finally, let’s talk about the characteristics of the two.

(1) The stack has first-in-last-out, last-in-first-out characteristics, continuous storage, simple operation, easy use, and no need for management. Most chips provide chip-level hardware support for the stack. You only need to move the pointer to quickly allocate memory. and recycling. For example, local variables use stack memory to reduce unnecessary memory allocation management. The time complexity of stack creation and deletion is O(1), which is fast. However, it is not conducive to managing large memory. The size and life cycle of the data in the stack are determined, which lacks flexibility.

(2) The management mechanism of heap memory is relatively complex, and there is a corresponding allocation strategy to prevent the occurrence of a large number of small fragments and speed up searches. The heap is used to dynamically create and allocate memory. The time complexity of creating and deleting nodes is O(logn). The heap recycling mechanism is also much more complex. Depending on the size of the memory and the life cycle of the data, the corresponding recycling mechanism must be adopted, involving the heap management of the operating system. Because the management and application of heap memory are relatively complex and consume more system resources, heap memory is usually used with a longer life cycle and a wider range of global variables.

picture

Why does each thread have a separate stack?
There are four functions A, B, C, and D, with addresses 100, 200, 300, and 400 respectively; two threads execute them at the same time;

1) If there is only one stack

picture

When function A is executed in thread 1, it calls function B, pushes the address of the next instruction in function A onto the stack (104), and then executes function B;

During the execution of function B, it is found that the Yield() function (blue, the role of Yield() can be understood as switching threads) is called, so the address of the next instruction to be executed in B is first pushed onto the stack (204), and then executed Yield() switches to the thread at address 300, which is thread 2;

Next, function C is executed, method D is called in the same way, and 304 is pushed onto the stack;

Finally, function D is executed, 404 is pushed onto the stack, and Yield() in D will jump to address 204 to continue execution (switching to the address 204 corresponding to the next statement to be executed in thread 1);

Immediately after function B is executed, it will return. The return address is the value at the top of the stack (404). The return address here should be 104; therefore, problems will arise when multiple
threads share a stack!

2) One stack for each thread

picture

When you switch threads, you also need to switch the stack. Here you need a data structure TCB (Thread control block) to store the stack pointer; each thread has a TCB.

The Yield() function in thread 2 should be rewritten into the following format:

void Yield(){

TCB2.esp=esp;
esp=TCB1.esp;
jmp 204;
}
Execution process:

In function A, call B and push address 104 onto the stack (esp=1000);

Execute Yield() in function B, save the current stack pointer TCB1.esp =esp, switch the stack pointer esp=TCB2.esp at the same time, push address 204 onto the stack, and jump to function C (esp=1000);

Call function D in function C and push address 304 onto the stack (esp=2000);

Function D executes Yield(), saves the stack pointer, switches the stack pointer, pushes address 404 onto the stack, jumps to function B, and continues to execute the code at address 204;

After the execution is completed, execute '}', pop up the top address 204 of the thread 1 stack, and find that the instruction at address 204 is repeatedly executed here;
3) Finally

Yield() of Thread 2

void Yield(){ TCB2.esp=esp; esp=TCB1.esp; } In this way, when the fourth step in 2) is executed, jmp 204 jump is no longer used, but '}' is executed, and the stack in thread 1 is The top address is popped off the stack.



Example

The memory occupied by a program compiled by C/C++ is divided into the following parts:

1. Stack area (stack) - automatically allocated and released by the compiler, storing function parameter values, local variable values, etc. It operates like a stack in a data structure.

2. Heap area (heap) - Generally allocated and released by the programmer. If the programmer does not release it, it may be recycled by the OS when the program ends. Note that it is different from the heap in the data structure, and the allocation method is similar to a linked list.

3. Global area (static area) (static) - global variables and static variables are stored together. Initialized global variables and static variables are in the same area, and uninitialized global variables and uninitialized static variables are in the same area. Another adjacent area. - Released by the system after the program ends.

4. Literal constant area—constant strings are placed here. It is released by the system after the program ends.

5. Program code area—stores the binary code of the function body.

//main.cpp
int a = 0; global initialized area
char *p1; global uninitialized area
main()
{ int b; stack char s[] = “abc”; stack char *p2; stack char *p3 = “123456 "; 123456\0 is in the constant area, and p3 is on the stack. static int c =0; global (static) initialization area p1 = (char *)malloc(10); p2 = (char *)malloc(20); //The allocated areas of 10 and 20 bytes are in the heap area . strcpy(p1, "123456"); 123456\0 is placed in the constant area, and the compiler may optimize it into the same place as "123456" pointed to by p3. The stack is automatically allocated by the system. For example, declare a local variable int b in a function; the system automatically creates space for b on the stack.









Programmers need to apply for heap themselves and specify the size. In C, the malloc function is used, such as p1 = (char *)malloc(10); in C++, the new operator is used, such as p2 = (char *)malloc(10); but Note that p1 and p2 themselves are on the stack.

Guess you like

Origin blog.csdn.net/leeshineCSDN/article/details/133310724