C language about memory understanding

1 Why do programs need memory to run?

1.1 The purpose of computer program operation
Why do computers need programming? Programming has been compiled for many years, and many programs have been written, why do we need to write additional programs? What on earth does the computer have this new program for?

The purpose of a program is to run, and the purpose of running a program is to obtain a certain result. Computers are used to calculate, and all computer programs are actually doing calculations. Computing is computing data. So a very important part of a computer program is data.

Computer program = code + data. After the computer program is run, a result is obtained, that is to say, code + data (after running) = result.

From a macro perspective, code is an action, which is the action of processing data; data is a number, which is something processed by code.

Then it can be concluded that the purpose of program operation is nothing more than two: result and process.

Using a function as an analogy: the formal parameter of a function is the data to be processed (the function also needs some temporary data, which are local variables), the function body is the code, the return value of the function is the result, and the execution process of the function body is the process.

    int add(int a, int b)
    {
    
    
        return a + b;
    }           // 这个函数的执行就是为了得到结果
    void add(int a, int b)
    {
    
    
        int c;
        c = a + b;
        printf("c = %d.\n", c);
    }           // 这个函数的执行重在过程(重在过程中的printf),返回值不需要
    int add(int a, int b)
    {
    
    
        int c;
        c = a + b;
        printf("c = %d.\n", c);
        return c;
    }           // 这个函数又重结果又重过程

Title 1.2 Computer program running process

The running process of a computer program is actually the process in which many functions in the program run one after another. A program is composed of many functions. The essence of a program is a function, and the essence of a function is the action of processing data.

1.3 Von Neumann structure and Harvard structure

Von Neumann structure is: data and code together.
The Harvard structure is: data and code exist separately.

What is Code: Functions.
What is data: global variables, local variables.

On the linux system running in S5PV210, when running the application program: at this time, all the code and data of the application program are in DRAM, so this structure is the Von Neumann structure; in the microcontroller, we burn the program code to In Flash (NorFlash), the program runs in place in Flash, and the data involved in the program (global variables, local variables) cannot be placed in Flash, but must be placed in RAM (SRAM). This is called the Harvard structure.

1.4 Dynamic memory DRAM and static memory SRAM

DRAM is dynamic memory and SRAM is static memory.

1.5 Summary: Why do we need memory?

Memory is used to store variable data, and data is represented as global variables, local variables, etc. in the program (in gcc, in fact, constants are also stored in memory) (in most single-chip microcomputers, constants are stored in flash, That is, in the code segment), it is very important for us to write programs, and it is even more essential to the operation of programs.

So memory is almost an essential requirement for programs. Simpler programs require less memory, while larger and more complex programs require more memory. Memory management is an important topic when we write programs. The key to many of the programming we have learned before is actually for memory, such as data structure (data structure is to study how data is organized, and data is stored in memory) and algorithm (algorithm is to use better and more An effective way to process data, since it is related to data, memory cannot be separated).

1.6 In-depth thinking: how to manage memory (when there is no OS, when there is an OS)

For computers, the greater the memory capacity, the greater the possibility, so everyone hopes that their computer memory will be larger. How to manage memory when we write programs has become a big problem. If it is not managed well, it may cause the program to run and consume too much memory, so that sooner or later the memory will be eaten up by your program, and the program will crash when no memory is available. So memory is a resource for programs, so managing memory is an important technology and topic for programs.

Let’s start with the perspective of the operating system: the operating system has all the hardware memory, because the memory is large, so the operating system divides the memory into pages (actually one block, usually 4KB), and then manages the memory in units of pages. Pages are managed in bytes in a more granular way. The principle of operating system memory management is very troublesome, very complicated, and very impersonal. Well, those of us who use the operating system don't really need to know these details. The operating system provides us with some interfaces for memory management, and we only need to use the API to manage memory. For example, use malloc free interfaces to manage memory in C language.

When there is no operating system: In the absence of an operating system (in fact, it is a bare-metal program), the program needs to directly operate the memory, and the programmer needs to calculate the use and arrangement of the memory by himself. If the programmer accidentally uses the wrong memory, the wrong result needs to be borne by himself.

From a language perspective: Different languages ​​provide different interfaces for operating memory.
For example, assembly: there is no memory management at all, and the memory management depends entirely on the programmer. It is very troublesome to directly use the memory address (such as 0xd0020010) when operating memory in assembly; for example, C language: the compiler in C language helps us manage the direct memory address
. We all access the memory through the variable names provided by the compiler. If you need a large memory under the operating system, you can access the system memory through the API (malloc free). If a large block of memory is needed in a bare-metal program, you need to define an array by yourself to solve it.
For example, C++ language: C++ language further encapsulates the use of memory. We can use new to create objects (in fact, allocate memory for objects), and then use delete to delete objects (in fact, release memory). Therefore, the memory management of the C++ language is more advanced and easier than that of C. But the management of memory in C++ is still done by the programmer himself. If the programmer creates a new object, but forgets to delete it when it is used up, the memory occupied by the object cannot be released, which is a memory leak.
Languages ​​such as Java/C#: These languages ​​​​do not directly operate memory, but operate memory through a virtual machine. In this way, the virtual machine acts as an agent for our programmers to help us deal with the release of memory. If my program applies for memory and forgets to release it after use, the virtual machine will release the memory for me. It sounds like C#java and other languages ​​have advantages over C/C++, but in fact, his virtual machine needs to pay a certain price to reclaim memory, so there is no good or bad language, only adaptation or inadaptability. When our program is very concerned about performance (such as the operating system kernel), we will use C/C++ language; when we are very concerned about the speed of developing programs, we will use Java/C# and other languages.

2-bit, byte, halfword, word concept and memory bit width

2.1 What is memory? (two angles of hardware and logic)

From a hardware point of view: memory is actually an accessory of the computer (commonly called a memory stick). According to different hardware implementation principles, memory can also be divided into SRAM and DRAM (there are many generations of DRAM, such as the earliest SDRAM, and later DDR1, DDR2..., LPDDR).

From a logical point of view: memory is such a thing that it can be accessed randomly (random access means that as long as an address is given, the memory address can be accessed), and it can be read and written (of course, it can also be limited to read-only logically) Or just write); memory is naturally used to store variables in programming (it is because of memory that C language can define variables, and a variable in the language actually corresponds to a unit in the memory).

2.2 Logical abstraction diagram of memory (programming model of memory)

From a logical point of view, memory is actually composed of an infinite number of memory cells. Each cell has a fixed address called a memory address. This memory address is uniquely corresponding to this memory cell and is permanently bound.

The analogy of memory with a building is the most appropriate. The logical memory is like an infinite building, and the cells of the memory are like small rooms in the building. The address of each memory cell is like the room number of each small room. The content stored in the memory is like the people living in the room.

Logically speaking, memory can be infinite (because mathematically, the number can always increase, without end). However, the actual memory size is limited in reality, such as a 32-bit system (32-bit system refers to a 32-bit data line, but the general address line is also 32-bit, and the 32-bit address line determines that the memory address can only have 32 bits. Bit binary, so the logical size is 2 to the 32nd power) The memory limit is 4G. In fact, the available memory in a 32-bit system is less than or equal to 4G (for example, I installed 32-bit windows on a 32-bit CPU, but the actual computer only has 512M memory).

2.3 Bits and bytes

There are 4 units of memory unit size: bit (1bit), byte (8bit), halfword (usually 16bit) and word (usually 32bit).

In all computers and all machines (whether it is a 32-bit system or a 16-bit system or a later 64-bit system), a bit is always 1 bit, and a byte is always 8 bits.

2.4 Words and half words

There have been three types of 16-bit systems, 32-bit systems, and 64-bit systems in history, and there are many operating systems such as windows, linux, and iOS, so many concepts have been chaotically defined in history.

It is recommended that you do not distinguish the concepts of words, half-words, and double-words in detail, as long as you know how many bits of these units depend on the platform. In actual work, first figure out the definition of this platform on each platform (how many bits are a word, a half word is always half of a word, and a double word is always twice the size of a word).

The concept of word is generally not used in programming, so we distinguish this concept mainly because these concepts are used in some documents. If you do not make a distinction, it may cause your misunderstanding of the program.

On the software and hardware platform of linux+ARM (s5pv210), words are 32 bits.

2.5 Memory bit width (both hardware and logic perspectives)

From a hardware point of view: the implementation of hardware memory itself has a width, that is to say, some memory sticks are 8-bit, while some are 16-bit. Then it needs to be emphasized that the memory chips can be connected in parallel, even 8-bit memory chips can be made into 16-bit or 32-bit hardware memory through parallel connection.

From a logical point of view: the memory bit width is logically arbitrary, and even logically there is a memory with a memory bit width of 24 bits (but in fact, this kind of hardware is unavailable and has no practical significance). From a logical point of view, no matter what the memory bit width is, I can just operate it directly, and it will not affect my operation. But because your operation is not pure logic but requires hardware to execute, so you can't do whatever you want, so many of our actual operations are limited by the characteristics of the hardware. For example, 24-bit memory is logically no different from 32-bit memory, but the actual hardware is 32-bit, so you must work according to the characteristics and limitations of 32-bit hardware.

3 Memory addressing and addressing, memory alignment

3.1 Memory Addressing Method

Logically, the memory is a grid. These grids can be used to hold things (the things inside are the numbers stored in the memory). Each grid has a number. This number is the memory address. This memory address (a number) There is a one-to-one correspondence with the space of this grid (essentially a space) and is permanently bound. This is how memory is addressed.

When the program is running, the CPU in the computer actually only knows the memory address, and does not care about where the space represented by this address is and how to distribute these physical issues. Because the hardware design guarantees that the grid can be found according to this address, the two concepts of the memory unit: address and space are two aspects of the memory unit.

3.2 Key: memory addressing is in bytes

I just give a number (for example, 7), and then say that this number is a memory address, and then ask you how much space does this memory address correspond to? This size is fixed, which is one byte (8bit).
If the memory is likened to a building, each room in this building is a memory grid, and the size of this grid is fixed at 8 bits, as if all the rooms in this building are of the same type.

3.3 Relationship between memory and data types

The basic data types in C language are: char short int long float double.

int integer (integer type, this integer is reflected in the fact that it is the same as the data bit width of the CPU itself) For example, for a 32-bit CPU, the integer is 32 bits, and the int is 32 bits.

The relationship between data types and memory is that
data types are used to define variables, and these variables need to be stored and operated in memory. So the data type must match the memory to get the best performance, otherwise it may not work or be inefficient.

It is best to use int to define variables in a 32-bit system, because it is efficient. The reason is that the 32-bit system itself is also 32-bit with memory, etc. Such a hardware configuration is naturally suitable for defining 32-bit int type variables, which is the most efficient. It is also possible to define 8-bit char type variables or 16-bit short type variables, but in fact the access efficiency is not high.

In many 32-bit environments, we actually define bool type variables (actually only 1 bit is enough) to implement bool with int. That is to say, when we define a bool b1;, the compiler actually allocates 32 bits of memory for us to store this bool variable b1. The compiler actually wastes 31 bits of memory by doing this, but the benefit is high efficiency.

Question: In actual programming, should memory saving be the most important or operating efficiency be the most important? The answer is uncertain, depending on the specific situation. Many years ago, memory was very expensive and there was very little memory on the machine. At that time, the main purpose of writing code was to save memory. Now with the development of semiconductor technology, memory has become very cheap. Today's machines are all high-end, and they don't care about saving a little memory. Efficiency and user experience have become the key. So most of the programs written now focus on efficiency.

3.4 Memory Alignment

We int a in C; to define an int type variable, we must allocate 4 bytes in memory to store this a. There are two different memory allocation ideas and strategies:
the first: 0 1 2 3 aligned access
the second: 1 2 3 4 or 2 3 4 5 or 3 4 5 6 unaligned access

Aligned memory access is not a logical problem, but a hardware problem. From a hardware point of view, the four units of 32-bit memory, 0 1 2 3, are logically related. The combination of these 4 bytes is suitable for an int hardware, and the efficiency is high.

Aligned access works well with the hardware, so it is very efficient; unaligned access is not efficient because it does not match the hardware itself. (Because of compatibility issues, general hardware also provides unaligned access, but the efficiency is much lower.)

4 How does C language operate memory

4.1 Encapsulation of memory address by C language (use variable name to access memory, meaning of data type, meaning of function name

For example, in C language, int a; a = 5; a += 4; // a == 9;
combined with memory to analyze the essence of C language statements:
int a; // The compiler has applied for an int type for us Memory grid (the length is 4 bytes, the address is determined, but only the compiler knows it, we don't know it, and we don't need to know it.), and bind the symbol a to this grid.

a = 5; // When the compiler finds that we want to assign a value to a, it will throw the value 5 into the memory grid bound to the symbol a.

a += 4; // The compiler finds that we need to add a value to a, a += 4 is equivalent to a = a + 4; the compiler will first read out the original value of a, then add 4 to this value, and then Write the sum after addition into a.

The essential meaning of the data type in C language is: to represent the length and analysis method of a memory grid.

The data type determines the meaning of the length: we have a memory address (0x30000000), originally this address only represents the length of 1 byte, but in fact we can give it a type (int), so that it has a length (4), In this way, the number representing the memory address (0x30000000) can represent the continuous n (4) bytes of memory grid starting from this number (0x30000000) (0x30000000 + 0x30000001 + 0x30000002 + 0x30000003).

The data type determines the meaning of the analysis method: for example, I have a memory address (0x30000000), we can specify the analysis method of the binary number in the memory cell by giving the memory address different types. For example, I (int) 0x30000000 means (0x30000000 + 0x30000001 + 0x30000002 + 0x30000003) these 4 bytes together store an int type data; then I (float) 0x30000000 means (0x30000000 + 0x30000000 + 0x30000001 + 0x30000003) These 4 bytes together store a float data;

I talked about a very important concept before: the addressing unit of a memory cell is a byte.
(int *)0;
(float *)0;
(short)0;
(char)0;

int a; // int a; the compiler will automatically assign a memory address to a, for example, 0x12345678
(int )&a; // equivalent to (int )0x12345678
(float *)&a;

In the C language, a function is an encapsulation of a piece of code. The essence of the function name is the first address of this piece of code. So the essence of the function name is also a memory address.

4.2 Using pointers to access memory indirectly

Regarding the type (whether it is an ordinary variable type int float, etc., or a pointer type int * float *, etc.), just remember:
the type is only a length specification for the memory represented by the following number or symbol (representing the memory address) and The analysis method is specified only.

The pointer in the C language is called a pointer variable, and the pointer variable is actually a common variable without any difference. For example, there is actually no difference between int a and int p. Both a and p represent a memory address (for example, 0x20000000), but the length and analysis method of this memory address (0x20000000) are different. a is an int type, so the length of a is 4 bytes, and the analysis method is in accordance with the regulations of int; p is an int type, so the length is 4 bytes, and the analysis method is in accordance with the regulations of int * (the consecutive 4 bytes starting with 0x20000000 An address is stored in the byte, and the memory unit represented by this address stores an int type number).

4.3 Using arrays to manage memory

There is actually no essential difference between array management memory and variables, but the method of parsing symbols is different. (There is actually no essential difference between ordinary variables, arrays, and pointer variables. They are all parsing memory addresses, but the parsing methods are different).

int a; // The compiler allocates 4 bytes to a, and binds the first address to the symbol a.
int b[10]; // The compiler allocates 40 bytes of length to b, and binds the address of the first element with the symbol b.

The first element (a[0]) in the array is called the first element; each element type is int, so the length is 4, and the address of the first byte is called the first address; the first element a[0] The first address of is called the first address of the first element.

5 Structure of memory management

5.1 The significance of the knowledge of data structure

Data structure is the study of how data is organized (arranged in memory) and how it is processed.

5.2 The simplest data structure: array

Why have an array? Because there are many variables of the same type and related meanings in the program that need to be managed, at this time, if the program is made with separate variables, it looks messy, and it will be easier to manage it with arrays.
For example int ages[20];

5.3 Advantages and disadvantages of arrays

Advantages: The array is relatively simple, with subscripts for access, and random access.
Defects: 1. All element types in the array must be the same; 2. The size of the array must be given when defining, and once determined, it cannot be changed.

##5.4 The grand debut of the structure
The structure was invented to solve the first defect of the array: all elements in the array must be of the same type.

We want to manage the age of 3 students (int type), what should we do?
The first solution: use the array int ages[3];
the second solution: use the structure
struct ages
{ int age1; int age2; int age3; }; struct ages age;




Analysis summary: In this example, arrays are better than structs. But it cannot be concluded that arrays are better than structures. When the types of elements in the package are different, only structures can be used instead of arrays.

struct people
{ int age; // the age of the person char name[20]; // the name of the person int height; // the height of the person }; because the types of the elements of people are not exactly the same, so you must use a structure, there is no way Use an array.




5.5 Digression: Embedding pointers in structures to achieve object-oriented

Process-oriented and object-oriented.
In general: C language is process-oriented, but the Linux system written in C language is object-oriented.
Non-object-oriented languages ​​are not necessarily incapable of implementing object-oriented code. It's just that it is simpler, more intuitive, and more brainless to implement object-oriented language in an object-oriented language.

It is easier to implement object-oriented languages ​​​​such as C++ and Java, because the language itself does a lot of things for us; but it is very troublesome to implement object-oriented languages ​​​​with C, and it does not seem to be easy to understand, which is why most people learn The reason why I have passed the C language but can't understand the linux kernel code.

struct s
{ int age; // Ordinary variable void (*pFunc)(void); // Function pointer, pointing to functions such as void func(void) }; Object-oriented can be realized by using such a structure. In this way, the structure containing function pointers is similar to the class in object-oriented, the variables in the structure are similar to the member variables in the class, and the function pointers in the structure are similar to the member methods in the class.




6 The stack of memory management (stack)

6.1 What is a stack

The stack is a data structure, and the stack is used in the C language to store local variables. The stack was invented to manage memory.

6.2 Features of stack-managed memory (small memory, automation)

FILO first in last out stack
FIFO first in first out queue

The characteristic of the stack is that the entrance is the exit, there is only one mouth, and the other mouth is blocked. So those who go in first must come out last.

The characteristic of the queue is that there are both entrances and exits, and it must enter through the entrance and exit through the exit, so the ones that go in first must come out first, otherwise the ones behind will be blocked.

6.3 Application Example of Stack: Local Variables

Local variables in C language are implemented using stacks.

When we define a local variable (int a) in C, the compiler will allocate a section of space (4 bytes) in the stack for this local variable (the top pointer on the stack will move to give the space for the local variable a during allocation) The meaning is to associate the memory address of the 4-byte stack memory with the local variable name a we defined), and the operation corresponding to the stack is to push the stack.

Note: The movement of the stack pointer and memory allocation are automatic here (the stack is done by itself, we don't need to write code to operate).

Then when our function exits, the local variables will perish. The operation corresponding to the stack is popping the stack (popping the stack). When the stack is popped, the pointer at the top of the stack moves to release the 4 bytes of space associated with a in the stack space. This action is also automatic, and no human intervention is required to write code.

Advantages of the stack: the stack manages memory, the advantage is convenience, the allocation and final recovery do not need to worry about the programmer, and the C language completes it automatically.

Analyze a detail: In C language, if a local variable is not initialized when defining it, the value is random, why?
To define a local variable is to provide the program with a memory space and bind the local variable name by moving the stack pointer on the stack. Because this memory space is on the stack, and the stack memory is used repeatedly (dirty, not cleared when it was used up last time), so if the definition of local variables implemented using the stack is not explicitly initialized, the value is dirty of. What if you initialize explicitly?
The C language implements the initialization of local variables through a small means.
int a = 15; // initialize the local variable definition,
the C language compiler will automatically convert this line into:
int a; // local variable definition
a = 15; // ordinary assignment statement

6.4 Stack constraints (predetermined stack size is not flexible, afraid of overflow)

First of all, the stack has a size. So the stack memory size is not easy to set. If it is too small, it is afraid of overflowing, and if it is too large, it is afraid of wasting memory (this disadvantage is a bit like an array).

Secondly, stack overflow is very harmful and must be avoided. Therefore, when we define local variables in C language, we cannot define too many or too large (for example, int a[10000] when local variables cannot be defined; when using recursion to solve problems, we must pay attention to recursive convergence) Reference: https://blog
. csdn.net/qq_20233867/article/details/78983624

Guess you like

Origin blog.csdn.net/weixin_48321071/article/details/124610891