[Elementary C++] Memory management && First introduction to templates

1. C/C++ memory distribution

The memory distribution of C/C++ is mainly divided intostack area, heap area, data segment and code segment, as well as memory mapping segment.

The stack is also called a stack – non-static local variables/function parameters/return values, etc. The stack grows downward.
The memory mapped segment is an efficient I/O mapping method used to load a shared dynamic memory library. Users can use the system interface to create shared shared memory for inter-process communication.
The heap is used for dynamic memory allocation when the program is running, and the heap can grow.
Data segment – ​​stores global data and static data
Code segment – ​​executable code/read-only constants.

Look at the following code:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
    
    
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = {
    
     1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}

There are several questions:

1. Where is char2 2. Where is *char2
3. Where is pChar3 4. Where is *pChar3
5. Where is ptr1 6. Where is *ptr1


7.sizeof(num1) =
8.sizeof(char2) = ? 9.strlen(char2) = ?
10.sizeof(pChar3) = ? 11.strlen(pChar3) = ?
12.sizeof(ptr1) = ?

1.2 char2 is the name of the array defined in the Test function and is a local variable. is in the stack area;* char2 dereferences the array and gets the address of the array as a string,But it copies the contents of the string to the address of the array., so * char2 is in the stack area .
3.4 pChar3 is a pointer defined in the Test function, a local variable, in the stack area< a i=6>; * pChar3 dereferences the pointer and gets the string, string in the code segment.
5.6 ptr1 is an integer pointer defined in the Test function, which is a local variable. is in the stack area ptr1 is an integer pointer, so the answer is 4/812. strlen calculates the size of the string, not Including the trailing slash 0, so the answer is 411. sizeof only focuses on the type, pChar3 is a pointer, so the answer is 4/810. strlen calculates the size of the string, excluding the trailing slash 0, so the answer is 49. sizeof calculates the size of the string. Note that the trailing slash 0 is included, so the answer is 58. sizeof calculates the size of the entire array in bytes. There are 10 elements in the array, and one element is 4 bytes, so The answer is 407.. is in the heap area; * The address obtained by dereferencing ptr1 is the space opened by the malloc function,





A picture represents:
Insert image description here

2. C/C++ dynamic memory management method

The dynamic allocation methods in C language include malloc, calloc and realloc. The malloc function is commonly used, and the way to release space is free. However, these two methods have some limitations, so C++ has a new dynamic allocation management method, which uses the new and delete operators for dynamic memory management.

2.1 Usage of new and delete

Let’s make a comparison first:

int* a = (int*)malloc(sizeof(int));
///
int* a = new int;

new does not require us to write sizeof ourselves to calculate the byte size, does it look more concise?

delete a;
///
free(a);

Delete is similar to free, please note that it must be empty after releasing the space.

1️⃣new initialization element
Add a bracket at the end, the value in the bracket is the value to be initialized

int* a = new int(4);//初始化为4

Insert image description here

2️⃣new creates the number of elements
Add a square bracket at the end, the square bracket is the number of elements

int* a = new int[5];

Insert image description here

3️⃣new creates the number of elements and initializes them
On the basis of 2️⃣, add a curly brace, and the curly braces are the initialized values. . If the initialized number is less than the number of elements, add 0

int* a = new int[5]{
    
     1,2,3 };

Insert image description here
4️⃣delete single element time

delete a;

5️⃣delete multiple elements

delete[] a;

Note:
Apply for a single element space and release new and delete of a single object. Apply for multiple element space and release new[] and delete[] of multiple objects. To be used together

2.2 Comparison of new and malloc, delete and free

What they have in common:
all apply for space from the heap and require the user to release it manually.

Uneven score:
1️⃣malloc only opens up the space size and cannot initialize it; new can both open up the space size and initialize the space.The previous code is just for demonstration.

2️⃣Custom type initialization problem
When the object is a custom type, malloc is not convenient for custom type initialization, because malloc will only open up Space, free will only release space. new will open space first, and then call the constructor; delete will first call the destructor, and then release the space.

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a = 0)" << endl;
	}
	~A()
	{
    
    
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	A* aa = new A[3]{
    
     11,22,33 };
	delete aa;
	return 0;
}

Insert image description here

At the same time, new can complete the initialization when opening the space.

Insert image description here

Supplement:
The return value of malloc is void*, which must be forced when used. New does not need to, because new is followed by the type of space;
When malloc fails to apply for space, it returns NULL, so it must be null when used, and new does not need to be used.But new needs to catch exceptions;
malloc and free are functions, new and delete are operators

2.3 Analysis of more complex scenarios

Look at the following code:

class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack(int capacity = 4)" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
    
    
	Stack* st = new Stack;
	delete st;
	return 0;
}

This code has a new and delete in the main function, a new in the constructor, and a delete in the destructor, so what is the relationship between them?
Picture Impression analysis:
Insert image description here
The custom type first opens up space for objects (the number of objects will be analyzed later), and then calls the constructor. In the constructor, space is opened up for the _a array, because _a is a built-in type. , so the processing here is the same as malloc. To delete an object, first call the destructor, clean up _a, and then release the space. At this time, the object is cleaned up.

Note that some people mistakenly write delete st as free st. free only releases space and does not call the destructor. It releases the object's space, but the space pointed to by _a in the object's space is not released. Otherwise, it becomes a wild pointer, so you cannot mix free and delete.

3. operator new and operator delete functions

new and delete are operators used by users to apply for and release dynamic memory. Operator new and operator delete are global functions provided by the system. New calls the operator new global function at the bottom to apply for space, and delete uses the operator delete global function at the bottom to release space. .

Let’s first look at a piece of assembly code:
Insert image description here

operator new actually applies for space through malloc. If malloc successfully applies for space, it will return directly. Otherwise, it will implement the space shortage response measures provided by the user. If the user provides this measure, it will continue. Apply, otherwise an exception will be thrown. operator delete finally releases space through free. So, operator new and operator delete can be said to be encapsulations of malloc and free

4. Implementation principles of new and delete

1️⃣For built-in types, new is basically similar to malloc, delete and free. There is one difference. When there is only a single object, the two matched ones are new/delete; when there are multiple objects, the two matched ones are new[] and delete[]. Like an array, it is also a continuous space. It should be noted that new will throw an exception when it fails to apply for space, and malloc will return NULL.

2️⃣For custom types, when there is only a single object, it is the example of the previous code, and this will not be discussed. Let’s take a look at the situation with multiple objects:

class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack(int capacity = 4)" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
    
    
	Stack* st = new Stack[10];
	delete[] st;
	return 0;
}

Insert image description here

Principle of new []:
1. Call the operator new[] function, and actually call the operator new function in operator new[] to complete the application for 10 object spaces 2. Call operator delete[] to release space, and actually call operator delete in operator delete[] to release space 1. On the released object space Execute the destructor 10 times to complete the cleanup of resources in 10 objectsThe principle of delete[]:
2. Execute the constructor 10 times on the requested space


Insert image description here
A stack object has 3 built-in types (int* int int), a total of 12 bytes, so 10 stack objects have a total of 120 bytes, but is this true?
Insert image description here
Open the memory to view:
Insert image description here
The 4 bytes in front of the address pointed to by st store the number of objects
Insert image description here

What happens if you remove the square brackets after delete:

	Stack* st = new Stack[10];
	delete st;

Insert image description here

The program crashed, what's the reason? Let’s first look at the following code:

The following is a class A. The private member variable has only one integer type. First comment out the destructor and see what happens:

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a = 0)" << endl;
	}
	/*~A()
	{
		cout << "~A()" << endl;
	}*/
private:
	int _a;
};
int main()
{
    
    
	A* aa = new A[10];
	delete aa;/// 把方括号去掉
	return 0;
}

Insert image description here
The program runs normally. What will happen if the destructor is not commented:
Insert image description here
The program crashes.

Why does it make a difference if this class A annotation does not annotate the destructor?

There is no annotated destructor, this destructor will be called, the destructor will be called, aa points to the position in the picture below
Insert image description here
The front is red The area is used to store the number of objects. We know that delete[] matches new[], and delete matches new. Because there are no square brackets after delete, there is no need for the first 4 bytes to store the object's individuality when using new to open space. number (there is nothing easy to record the number of 1 object), so the aa pointer points as shown in the figure below.
Insert image description here

In this case, the space released is as shown below:
Insert image description here
The space released is the blue area, but the previous red area must also be released, and you cannot only release part of it, so, causing the operation to crash. The location of the released space is incorrect

has annotated destructor, although the compiler can automatically call the default generated destructor, but this depends on the compiler. Because there is only a built-in type (an integer) in class A and there is no application space, it does not need to be processed . If no processing is done, the destructor will not be called. Without a destructor, there will be no previous location to store the number of objects. The released location will not only release part of it.

Summary: new[] and delete[] and new and delete must be used in a matching manner and cannot be mixed.

5. First acquaintance with templates

5.1 Generic programming

Concept: Generic programming is a general code without specific types that can be reused in different types. Templates are the basis of generic programming. Templates are divided into function templates and class templates.

5.2 Function template

5.2.1 Concept

A function template is a general-purpose function that uses the types of actual parameters to derive functions of the corresponding type to implement function calls.

5.2.2 Writing method

Keyword templete
Format: templete < typename T1, typename T2... >, T is type
typename can also be class

A template for an exchange function:

template <class T>
void Swap(T& left, T& right)
{
    
    
	T tmp = left;
	left = right;
	right = tmp;
}

To exchange two numbers:

int main()
{
    
    
	int a = 1;
	int b = 3;
	cout << a << endl << b << endl;
	Swap(a, b);//a和b传进去,编译器推出类型,T就变成了int类型
	cout << a << endl << b << endl;
	return 0;
}

operation result:
Insert image description here

5.2.3 Use function templates when using different types

1️⃣ calls the function twice. The types of the function parameters are different. One is int and the other is double. Both calls use function templates, but these two Not calling the same function.

View through assembly:
Insert image description here
2️⃣Multiple template parameter printing
If you want to print different types at the same time, you can use multiple template parameters

template <class T1, class T2>
void Print(T1& a, T2& b)
{
    
    
	cout << a << endl << b << endl;
}
int main()
{
    
    
	int a = 33;
	double b = 1.22;
	Print(a, b);
	return 0;
}

The types of a and b are directly determined. Anyway, whatever is passed in, it will print.
Insert image description here

3️⃣Additive function

Code:

template <class T>
T Add(const T& a, const T& b)
{
    
    
	return a + b;
}
int main()
{
    
    
	int a = 2;
	int b = 6;
	cout << Add(a, b) << endl;

	return 0;
}

Running result:
Insert image description here
If the type of b is changed to double:
Insert image description here
The compiler reports an error, indicating that the two types cannot be different, otherwise Not sure which type to use.

5.2.4 Function Template Instantiation

1️⃣Implicit instantiation
Automatically deducing the corresponding function template parameter type through the type compiler of the actual parameters is called implicit instantiation.

Addition function:

template <class T>
T Add(const T& a, const T& b)
{
    
    
	return a + b;
}
int main()
{
    
    
	int a = 9;
	int b = 2;
	int c = Add(a, b);
	cout << c << endl;
	return 0;
}

Insert image description here

T is rolled out to type int.

If the type of one of the parameters is different, the compiler will report an error. There are two ways to solve it.

Casting:

template <class T>
T Add(const T& a, const T& b)
{
    
    
	return a + b;
}
int main()
{
    
    
	int a = 9;
	double b = 2.1;
	
	cout << Add(a, (int)b) << endl;
	return 0;
}

Insert image description here
The other isdisplay instantiation

2️⃣Show instantiation

template <class T>
T Add(const T& a, const T& b)
{
    
    
	return a + b;
}
int main()
{
    
    
	int a = 9;
	double b = 2.1;
	
	cout << Add<int>(a, b) << endl;
	return 0;
}

Specify the actual type of the template parameter in the <> after the function name
Insert image description here
Under normal circumstances, it is enough to use the function template for implicit instantiation, but in some cases it is necessary to use explicit Instantiation

template <class T>
T* func()
{
    
    
	return new T[3];
}
int main()
{
    
    
	func<int>();
	return 0;
}

Without passing parameters, T does not know what type it should become, so instantiation must be displayed to determine the type of T.

5.2.5 Function template matching calling principle

When there are both ordinary functions and function templates in the code, which one will be used when calling the function?

//函数模板
template <class T>
T Add(const T& a, const T& b)
{
    
    
	return a + b;
}
//普通函数
int Add(int a, int b)
{
    
    
	return a + b;
}
int main()
{
    
    
	int a = 1;
	int b = 3;
	cout << Add(a, b) << endl;

	return 0;
}

Turn on debugging:
Insert image description here
When variables a and b are defined, they are both of int type. The parameter types of ordinary functions are also int, so use the ready-made ones.

Now change the types of a and b.

	double a = 1.1;
	double b = 3.3;

Debugging:
Insert image description here
If there is no ready-made ordinary function, use the appropriate one. The function template parameter type is double

5.3 Class templates

Class templates are similar to function templates, but there are some points to note:Must show instantiation

template <class T>
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack(int capacity = 4)" << endl;
		_a = new T[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		delete[] _a;
		_top = 0;
		_capacity = 0;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};
int main()
{
    
    
	Stack<int> st1;
	Stack<double> st2;
	return 0;
}

Only in this way can the type of member variables in the class be determined.
Insert image description here

Guess you like

Origin blog.csdn.net/2301_77459845/article/details/134374490