1. Memory management
We need to know-the variable is actually an abstract name of the memory address. In a statically compiled program, all variable names will be converted to memory addresses during compilation. The machine does not know the name we took, only the address.
One of the important factors that need to be considered in the program design when the memory is used is not only because the system memory is limited (especially in the embedded system), but also the memory allocation will directly affect the efficiency of the program. Therefore, we must have a systematic understanding of memory management in C language.
In C language, 4 memory areas are defined: code area; global variable and static variable area; local variable area is stack area; dynamic storage area is heap area; details are as follows:
1>Stack area (stack)-automatically allocated and released by the compiler, storing function parameter values, local variable values, etc. Its operation is similar to the stack in the data structure.
2>heap — generally allocated and released by the programmer, if the programmer does not release it, the OS may reclaim it 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)—The storage of global variables and static variables are put together, initialized global variables and static variables are in one area, uninitialized global variables and uninitialized static variables are adjacent Another area. -Released by the system after the program ends.
4>Constant area-constant string is placed here. Released by the system after the program ends.
5>Program code area—store the binary code of the function body. Let's look at the picture:
figure 1
First of all, we need to know that the source code is compiled into a program, and the program is placed on the hard disk, not in the memory! Only when executed will it be called into memory! Let's take a look at the program structure, ELF is the main executable file format of Linux. The ELF file consists of 4 parts, namely ELF header, Program header table, Section and Section header table. details as follows:
1>Program header describes the location and size of a segment in the file, as well as the location and size where it is placed in memory. The information to be loaded;
2>Sections stores the information of the object file, from the connection point of view: including instructions, data, symbol table, relocation information, etc. In the figure, we can see that Sections include:
text text storage instruction;
rodata data node readonly;
data The data node can be read and written;
3>Section header table (section header table) contains information describing file sections. Each section has an entry in this table; each entry gives the section name, size, and so on. Equivalent to index!
And how is the program distributed into the memory? Let's look at the picture above:
1 The text and initialized data and uninitialized data are what we call the data segment, and the text is the code segment;
2> Above the text section is the constant area, above the constant area is the global variable and static variable area, both occupy the part of initialized data and uninitialized data;
3>On top is the heap, the dynamic storage area, here is the upward growth;
4> Above the heap is the stack, which stores local variables, that is, after the execution of the code block where the local variables are located, this memory will be released, and the stack area here will grow downward;
5>Command line parameters are 001 and the like. The previous articles on environment variables have already been mentioned, and those who are interested can check it out.
We know that memory is divided into dynamic memory and static memory, let's talk about static memory first.
1.1 Static memory
The storage model determines the memory allocation method and access characteristics of a variable. There are three main dimensions to determine in the C language: storage period, scope, and link.
1. Storage period Storage period: the retention time of the variable in the memory (life cycle) The storage period is divided into two situations. The key is to see whether the variable will be automatically recovered by the system during the execution of the program.
1) Static storage period Static will not be automatically recovered once it is allocated during program execution. Generally speaking, any variable that is not defined in a function-level code block. Regardless of whether it is in the code block, as long as the variable is modified with the static keyword.
2) Automatic storage period Automatic variables other than static storage are in automatic storage period, or as long as they are non-static variables defined in the code block, the system will automatically unconfigure and release the memory;
2. Scope Scope: the visibility (access or reference) of a variable in its own file that defines the variable. In C language, there are 3 scopes: 1) Code block scope The variable defined in the code block Both have the scope of the code. From the place where this variable is defined, to the end of this code block, the variable is visible;
2) Function prototype scope All variables appearing in the function prototype have the function prototype scope. The function prototype scope extends from the variable definition to the end of the prototype declaration.
3) File scope A variable defined outside of all functions has file scope, and a variable with file scope is visible from its definition to the end of the file containing the definition;
3. Link link: the visibility (access or reference) of a variable in all files that make up a program; there are three different links in C language: 1) External link if a variable is in all files that make up a program Any location can be accessed, it is said that the variable supports external links;
2) Internal linking If a variable can only be accessed anywhere in the file defining itself, it is said that the variable supports internal linking.
3) Null link If a variable is only privately owned by the current code block that defines itself and cannot be accessed by other parts of the program, it is considered that the variable supports null link
Let's look at a code example:
#include
int a = 0;// Global initialization area
char *p1; //Global uninitialized area
int main()
{
int b; //b is in the stack area
char s[] = "abc"; //stack
char *p2; //p2 is in the stack area
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 10 and 20 bytes allocated are in the heap area.
strcpy(p1, "123456"); //123456\0 is placed in the constant area, the compiler may optimize it and "123456" pointed to by p3 into one place.
}
·
1.2 Dynamic memory
When the program runs to a variable that needs to be dynamically allocated, it must apply to the system for a piece of storage space of the required size in the heap for storing the variable. When the variable is not in use, that is, at the end of its life, the storage space occupied by it should be displayed and released so that the system can redistribute the space and reuse wired resources. The following describes the functions of dynamic memory application and release.
1.2.1 malloc function
Malloc function prototype:
size is the number of bytes of memory that needs to be dynamically allocated. If the application is successful, the function returns the starting address of the allocated memory, if the application fails, it returns NULL. Let's look at the following example:
When using this function, pay attention to the following points: 1) Only care about the size of the requested memory; 2) What is requested is a contiguous memory. Remember to write error judgment; 3) Display initialization. That is, we don't know what is in this memory, we need to clear it;
1.2.2 free function
The amount of memory allocated on the heap needs to be displayed and released with the free function. The function prototype is as follows:
When using free(), there are also the following points to note: 1) The starting address of the memory must be provided; when calling this function, the starting address of the memory must be provided, not part of the address, and it is not allowed to release a part of the memory .
2) Malloc and free are used in pairs; the compiler is not responsible for the release of dynamic memory, and the programmer needs to display the release. Therefore, malloc and free are paired to avoid memory leaks.
p = NULL is necessary, because although this memory is released, p still points to this memory to avoid misoperation of p next time;
3) Repeated release is not allowed. After this memory is released, it may have been allocated. This area is occupied by others. If it is released again, data will be lost;
1.2.3 Other related functions
The calloc function needs to consider the type of storage location to allocate memory. The realloc function can adjust the size of a dynamically allocated memory
1.3 heap and stack comparison
1) How to apply
stack: automatically allocated by the system. For example, declare a local variable int b in the function; the system automatically opens up space for b in the stack: the programmer needs to apply for it and specify the size, in the malloc function in c, such as p1 = (char *)malloc(10) ;
2) The response of the system after the application. Stack: As long as the remaining space of the stack is greater than the requested space, the system will provide memory for the program, otherwise an exception will be reported to indicate stack overflow.
Heap: First of all, you should know that the operating system has a linked list that records free memory addresses. When the system receives an application for the program, it will traverse the linked list to find the first heap node whose space is larger than the requested space, and then start the node from Delete the free node list and allocate the space of the node to the program. In addition, for most systems, the size of this allocation will be recorded at the first address in this memory space. In this way, the delete statement in the code In order to release the memory space correctly. In addition, because the size of the found heap node may not be exactly equal to the size of the application, the system will automatically put the extra part into the free list.
3) Application size limit Stack: The stack is a data structure extended to lower addresses, and is a contiguous memory area. This sentence means that the address at the top of the stack and the maximum capacity of the stack are pre-defined by the system. The size of the stack is 2M (some say it is 1M, in short, it is a constant determined at compile time). If the requested space exceeds When there is remaining space in the stack, overflow will be prompted. Therefore, the space available from the stack is smaller. Heap: Heap is a data structure that extends to high addresses and is a discontinuous memory area. This is because the system uses a linked list to store free memory addresses, which are naturally discontinuous, and the traversal direction of the linked list is from low addresses to high addresses. The size of the heap is limited by the effective virtual memory in the computer system. It can be seen that the space obtained by the heap is more flexible and larger.
4) Comparison of application efficiency The stack is automatically allocated by the system, which is faster. But the programmer cannot control it. The heap is the memory allocated by new, which is generally slow and prone to memory fragmentation, but it is the most convenient to use.
5) The storage content stack in the heap and the stack: When a function is called, the first to be pushed onto the stack is the address of the next instruction after the main function (the next executable statement of the function call statement), and then the functions of each Parameters. In most C compilers, the parameters are pushed onto the stack from right to left, and then the local variables in the function. Note that static variables are not stacked. When this function call is over, the local variables are popped out of the stack first, then the parameters, and finally the pointer on the top of the stack points to the initial address, which is the next instruction in the main function, and the program continues to run from this point.
Heap: Generally, one byte is used to store the size of the heap at the head of the heap. The specific content in the heap is arranged by the programmer.
6) Comparison of access efficiency
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa is assigned at runtime; bbbbbbbbbbb is determined at compile time; however, in subsequent accesses, the array on the stack is faster than the string pointed to by the pointer (for example, the heap). such as:
Corresponding assembly code
The first type reads the elements in the string directly into the register cl when reading, while the second type reads the pointer value into edx first, and then reads the characters according to edx, which is obviously slow.
7) Final summary The difference between heap and stack can be seen with the following analogy:
The stack is just like when we go to a restaurant to eat, we just order (issue an application), pay, and eat (use), and we leave when we are full, and we don’t have to worry about the preparations such as cutting vegetables and washing dishes, washing dishes, washing pots, etc. The advantage of work is that it is quick, but the degree of freedom is small.
Dumping is like making your own favorite dishes, which is more troublesome, but it is more in line with your own taste and has a lot of freedom.
2. Memory alignment
2.1 Detailed explanation of #pragma pack(n) alignment usage
1. What is alignment, and why it is necessary to align the memory space in modern computers are divided according to bytes. In theory, it seems that access to any type of variable can start from any address, but the actual situation is when accessing specific variables It is often accessed at a specific memory address, which requires various types of data to be arranged in space according to certain rules, rather than sequentially arranged one after another. This is alignment.
The role and reason of alignment: The processing of storage space by various hardware platforms is very different. Some platforms can only access certain specific types of data from certain specific addresses. Other platforms may not have this situation, but the most common is that if the data storage is not aligned according to the requirements of the platform, it will cause a loss in access efficiency. For example, some platforms start with an even address every time. If an int type (assuming a 32-bit system) is stored at the beginning of an even address, then a read cycle can be read, and if it is stored at an odd address, In some places, it may take 2 read cycles and piece together the high and low bytes of the two read results to get the int data. Obviously, the reading efficiency has dropped a lot. This is also a game between space and time.
2. Realization of alignment Generally, when we write programs, we don't need to consider alignment issues. The compiler will choose the alignment strategy of the target platform for us. Of course, we can also notify the compiler to pass precompiled instructions to change the alignment method for the specified data. However, because we generally don't need to care about this problem, because the editor aligns the data storage, and we don't understand it, we often get confused about some problems. The most common is the sizeof result of the struct data structure, which is unexpected. For this, we need to understand the alignment algorithm.
Function: Specify the packing alignment of structure, union and class members; Syntax:
#pragma pack( [show] | [push | pop] [, identifier], n)
Explanation: 1>pack provides control of the data declaration level, which has no effect on the definition; 2>If no parameter is specified when calling pack, n will be set to the default value; 3>Once the alignment of the data type is changed, the direct effect is to occupy memory Reduce, but performance will decline;
3. Syntax analysis 1>show: optional parameter; displays the number of bytes of the current packing aligment, displayed in the form of warning message;
2>push: optional parameter; push the currently specified packing alignment value to the stack, where the stack is the internal compiler stack, and set the current packing alignment to n; if n is not specified, the current packing alignment value Push stack
3>pop: Optional parameter; delete the top record from the internal compiler stack; if n is not specified, the current stack top record is the new packing alignment value; if n is specified, n will become the new packing aligment Numerical value; if the identifier is specified, the records in the internal compiler stack will be popped until the identifier is found, then the identifier will be popped out, and the packing alignment value will be set to the record at the top of the current stack; if the specified identifier does not exist in the internal compiler stack, the pop operation is ignored;
4>identifier: optional parameter; when used with push, give a name to the record currently pushed onto the stack; when used with pop, pop all records from the internal compiler stack until the identifier is popped, If the identifier is not found, ignore the pop operation; 5>n: optional parameter; specify the value of packing, in bytes; the default value is 8, and the legal values ​​are 1, 2, 4, 8, 16 .
4. Important rules
1>The members of the complex type are stored in the memory in the order in which they are declared, and the address of the first member is the same as the address of the entire type;
2>Each member is aligned separately, that is, each member is aligned in its own way and the length is minimized; the rule is that each member is aligned according to its type (usually the size of this type) and the smaller of the specified alignment parameters One aligned
3>Data members of structure, union or class, the first is placed at offset 0; the alignment of each data member in the future, according to the value specified by #pragma pack and the length of the data member itself, whichever is smaller That is to say, when the value specified by #pragma pack equals or exceeds the length of all data members, the size of this specified value will have no effect;
4> The overall alignment of complex types (such as structures) is based on the smaller value between the largest data member in the structure and the #pragma pack specified value; this way, when the member is a complex type, the length can be minimized;
5>The calculation of the overall length of the structure must be an integer multiple of all the alignment parameters used, not enough to fill the empty bytes; that is, an integer multiple of the largest value of all the alignment parameters used, because the alignment parameters are all 2. n-th power; in this way, each item can be aligned on the boundary when processing the array;
5. Alignment algorithm Due to the differences of various platforms and compilers, I now use my gcc version 3.2.2 compiler (32-bit x86 platform) as an example to discuss how the compiler aligns the members of the struct data structure of.
Under the same alignment, the order of data definition inside the structure is different, and the structure as a whole occupies different memory space, as follows:
Let the structure be defined as follows:
Structure A contains a 4-byte int, a 1-byte char, and a 2-byte short data. So the space used by A should be 7 bytes. But because the compiler needs to align the data members in space. So use sizeof(strcut A) with a value of 8. Now adjust the order of the member variables of the structure.
At this time, it is also a variable with a total of 7 bytes, but the value of sizeof(struct B) is 12. Below we use the precompiled directive #progma pack (value) to tell the compiler to use the alignment value we specify instead of the default.
The value of sizeof(struct C) is 8. Modify the alignment value to 1:
The sizeof(struct D) value is 7.
For char type data, its self-alignment value is 1, for short type it is 2, and for int, float, double type, its self-alignment value is 4, unit byte.
6. Four conceptual values ​​1> the alignment value of the data type itself: it is the alignment value of the basic data type explained above.
2>Specified alignment value: #progma pack (value) specified alignment value value.
3> The self-alignment value of the structure or class: the value with the largest self-alignment value among its data members.
4> Effective alignment values ​​of data members, structures and classes: the smaller one of its own alignment value and the specified alignment value. With these values, we can easily discuss the alignment of the members of the specific data structure and its own. The effective alignment value N is the final value used to determine the data storage address mode, the most important. Effective alignment to N means "alignment on N", that is to say, the "storage start address %N=0" of the data. The data variables in the data structure are arranged in a defined order. The starting address of the first data variable is the starting address of the data structure. The member variables of the structure should be aligned and discharged, and the structure itself should be rounded according to its own effective alignment value (that is, the total length occupied by the structure member variables needs to be an integer multiple of the effective alignment value of the structure, which is understood with the following example). In this way, you cannot understand the values ​​of the above examples.
Example analysis: Analysis example B;
Assume that B starts from address space 0x0000. In this example, the specified alignment value is not defined. In the author's environment, the value is 4 by default.
The self-alignment value of the first member variable b is 1, which is smaller than the specified or default specified alignment value of 4, so its effective alignment value is 1, so its storage address 0x0000 conforms to 0x0000%1=0.
The second member variable a has its own alignment value of 4, so the effective alignment value is also 4, so it can only be stored in the four consecutive byte spaces from 0x0004 to 0x0007, conforming to 0x0004%4=0 , And close to the first variable.
The third variable c has its own alignment value of 2, so the effective alignment value is also 2. It can be stored in the two byte spaces from 0x0008 to 0x0009, conforming to 0x0008%2=0. Therefore, the contents of B from 0x0000 to 0x0009 are stored. Look at the self-alignment value of data structure B as the maximum alignment value in its variable (here b), so it is 4, so the effective alignment value of the structure is also 4. According to the requirement of structure rounding, 0x0009 to 0x0000=10 bytes, (10+2)%4=0. So 0x0000A to 0x000B are also occupied by structure B. Therefore, B has a total of 12 bytes from 0x0000 to 0x000B, sizeof(struct B)=12;
Similarly, analyze the above example C:
The self-alignment value of the first variable b is 1, and the specified alignment value is 2, so its effective alignment value is 1. Assuming that C starts from 0x0000, then b is stored in 0x0000, which conforms to 0x0000%1=0;
The second variable, the self-alignment value is 4, and the specified alignment value is 2, so the effective alignment value is 2, so the sequence is stored in the four consecutive bytes of 0x0002, 0x0003, 0x0004, and 0x0005, conforming to 0x0002%2=0.
The self-alignment value of the third variable c is 2, so the effective alignment value is 2, and the sequence is stored in 0x0006 and 0x0007, which conforms to 0x0006%2=0. Therefore, a total of eight bytes from 0x0000 to 0x00007 are stored in C variables.
And C's self-alignment value is 4, so the effective alignment value of C is 2. And 8%2=0, C only occupies eight bytes from 0x0000 to 0x0007. So sizeof(struct C)=8.
9.2.2 The impact of byte alignment on the program
Let us first look at a few examples (32bit, x86 environment, gcc compiler): Suppose the structure is defined as follows:
Now it is known that the length of various data types on 32-bit machines is as follows: char:1 (same as signed and unsigned) short: 2 (same as signed and unsigned) int: 4 (same as signed and unsigned) long: 4 (same as signed and unsigned) The sign and unsigned are the same) float:4 double:8 So what is the size of the above two structures? The result is: sizeof(strcut A) value is 8 sizeof(struct B) value is 12
Structure A contains an int with a length of 4 bytes, a char with a length of 1 byte and a short data with a length of 2 bytes. The same is true for B; logically, the size of A and B should both be 7 bytes. The above result appears because the compiler needs to align the data members in space. The above is the result of alignment according to the default setting of the compiler, so can we change the default alignment setting of the compiler, of course. For example:
The value of sizeof(struct C) is 8. Modify the alignment value to 1:
The sizeof(struct D) value is 7. We will explain the role of #pragma pack() later.
2.3 Modify the default alignment value of the compiler
1>In the VC IDE, you can modify it like this: [Projec++t]|[Settings], modify in the Struct Member Alignment of the Code Generation option of the category of the c/c++ tab, the default is 8 bytes. 2>During encoding, you can dynamically modify it like this: #pragma pack. Note: it is pragma instead of progma.
If you want to consider saving space when programming, then we only need to assume that the first address of the structure is 0, and then the variables can be arranged according to the above principles. The basic principle is to change the variables in the structure from small to large according to the type size. Declare, try to reduce the intermediate filling space. Another is to exchange space for time efficiency, we show filling space for alignment, for example: There is a way to use space for time to explicitly insert reserved members:
The reserved member has no meaning to our program. It only serves to fill up the space to achieve byte alignment. Of course, even if this member is not added, the compiler will automatically fill in the alignment for us. Reminder effect.
The hidden dangers of 2.4 byte alignment
Many of the hidden dangers of alignment in the code are implicit. For example, when forced type conversion. E.g:
The last two lines of code, to access the unsignedshort variable from the odd boundary, obviously do not meet the alignment requirements.
On x86, similar operations will only affect efficiency, but on MIPS or sparc, it may be an error, because they require byte alignment.
If there is an alignment or assignment problem, first check 1). The big little end of the compiler is set 2). See whether the system itself supports unaligned access 3). If it supports, see if the alignment is set or not, if not, see the need for access Add some special decorations to mark its special access operations. Alignment processing under ARM from DUI0067D_ADS1_2_CompLib type qulifiers is partly taken from the use of the alignment part of the ARM compiler document:
1.__align(num) This is used to modify the byte boundary of the highest-level object. When using LDRD or STRD in assembly, you need to use this command __align(8) for modification restrictions. To ensure that the data objects are aligned accordingly. The command to modify the object is limited to 8 bytes at most, which allows 2-byte objects to be aligned with 4 bytes, but cannot allow 4-byte objects to be aligned with 2 bytes. __align is a storage class modification. It only modifies the highest-level type objects and cannot be used for structure or function objects.
2.__packed __packed is one-byte alignment l Can not align packed objects l Read and write access to all objects are non-aligned access l Float and structure union containing float and objects without __packed will not be byte aligned l __packed has no effect on local integer variables
l Forcing the conversion from unpacked objects to packed objects is undefined, and the integer pointer can be legally defined
Meaning is packed. __packed int* p; //__packed int has no meaning
2.5 Aligned or unaligned read and write access causes problems
__packed struct STRUCT_TEST
{char a;int b;char c;
};
//Define the following structure. At this time, the starting address of b must be unaligned. Accessing b in the stack may be problematic, because the data on the stack must be aligned access [from CL]
//Define the following variables as global static not on the stack
static char* p;static struct STRUCT_TEST a;void Main()
{
__packed int* q; //At this time, it is defined as __packed to modify the access below the current q pointing to the unaligned data address.
p = (char*)&a;
q = (int*)(p+1);
*q = 0x87654321; /*
The assembly instruction to get the assignment is very clear
ldr r5,0x20001590; = #0x12345678
[0xe1a00005] mov r0,r5
[0xeb0000b0] bl __rt_uwrite4 //Call a 4byte write operation function here
[0xe5c10000] strb r0,[r1,#0] //The function performs strb operations 4 times and then returns to ensure correct data access
[0xe1a02420] mov r2,r0,lsr #8
[0xe5c12001] strb r2,[r1,#1]
[0xe1a02820] mov r2,r0,lsr #16
[0xe5c12002] strb r2,[r1,#2]
[0xe1a02c20] mov r2,r0,lsr #24
[0xe5c12003] strb r2,[r1,#3]
[0xe1a0f00e] mov pc,r14
*/ /*
If q is not modified by __packed, the assembled instructions will directly cause access failure at odd addresses.
[0xe59f2018] ldr r2,0x20001594; = #0x87654321
[0xe5812000] str r2,[r1,#0]
*/
//This can clearly see how unaligned access produces errors
//And how to eliminate problems caused by unaligned access
//It can also be seen that the instruction difference between unaligned access and aligned access causes efficiency problems
}
2.92mm RF connectors
Xi'an KNT Scien-tech Co., Ltd , https://www.honorconnector.com