C++ exception handling mechanism from shallow to deep, as well as the underlying analysis of the function call assembly process. The underlying simulation implementation of C++11 smart pointers

1. Exception

1.1. Exception programming model and basic use

  •  We have to use it to explain the above model   
double Div(int a, int b) {
	if (b == 0) throw "Zero Div";//抛出一个字符串常量
	cout << "要是异常抛出, 自我及其一下全部腰斩, 不会执行" << endl;
	return (double)a / (double)b;
}

int main() {
	try {
		cout << Div(4, 0) << endl;
	}
	catch (int errid) {
		//捕获错误码整形进行处理
		cout << "错误编号: " << errid << endl;
	}
	catch (const char* msg) {
		cout << "错误信息" << msg << endl;
	}
	cout << "异常处理结束了, 继续向后执行呀, 除非异常处理进行了中断" << endl;
	return 0;
}
  • Analysis: Since the exception is thrown, the post-sequence code will no longer be executed
  • After the exception handling is over, as long as the process is not terminated, continue to execute the next statement after the exception is handled.

1.2. Custom exception class

class MyException {
public:
	MyException(int errid, string errmsg) 
		: _errid(errid)
		, _errmsg(errmsg) 
	{}

	const string& what() const noexcept {
		return _errmsg;
	}

	int GetErrid() const noexcept {
		return _errid;
	}
private:
	int _errid;
	string _errmsg;
};

1.3. The search process of the exception handling function (find back along the function stack)

class MyException {
public:
	MyException(int errid, string errmsg) 
		: _errid(errid)
		, _errmsg(errmsg) 
	{}

	const string& what() const noexcept {
		return _errmsg;
	}

	int GetErrid() const noexcept {
		return _errid;
	}
private:
	int _errid;
	string _errmsg;
};


void h() {
	throw MyException(0, "沿着函数调用方向往回找异常处理函数");
}

void g() {
	try {
		h();
	}
	catch (int errid) {
		cout << "g()错误码是: " << errid << endl;
	}
}

void f() {
	try{
		g();
	}
	catch (const runtime_error& re) {
		cout << "f函数中处理函数: " << re.what() << endl;
	}

}

int main() {
	try {
		f();
	}
	catch (const MyException& e) {
		cout << "主函数中处理函数: " << e.what() << endl;
	}
	catch (...) {//	一般为了异常有个处理会在最后加上他
		cout << "主函数中处理函数: " << "捕获到未知异常" << endl;
	}
	return 0;
}
  • The result is of course that the corresponding processing function in the main function is found back along the function stack...

1.4. Exception re-throwing, multi-catch processing (more outer processing)

1.4.1 Exception handling to prevent memory leaks

class Test {
public:
	Test() {
		//default ctor 
	}	
	~Test() {
		cout << "dtor" << endl;
	}
};

int main() {
	try {
		//Test t;   栈区对象肯定没问题的
		Test* pt = new Test;//堆区呢?
		throw 1;
        delete pt;   //会怎样??? 不会执行, 内存泄漏
	}
	catch (int errid) {
		cout << "我会不会调用析构???" << endl;
		cout << errid << endl;
	}
	return 0;
}

  •  The result is naturally that the destructor is not automatically called, what's wrong???? It means that the memory leaks,  
  • Therefore, memory leaks are also a problem that requires special attention when writing exception handling.

1.4.2 Exception multi-catch processing, the inner layer does not handle processing and continues to throw outward,

double Div(int a, int b) {
	if (b == 0) {
		throw MyException(0, "Zero Div");
	}
	return (double)a / (double)b;
}

void func() {
 // 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
 // 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
 // 重新抛出去。
	int* arr = new int[4]{ 0 };
	try{
		int a, b;
		cout << "请输入被除数和除数: " << endl;
		cin >> a >> b;
		cout << Div(a, b) << endl;
	}
	catch (...) {	//我仅仅处理内存, 至于信息等等继续抛出去其他函数处理		
		cout << "delete[]" << endl;
		delete[] arr;
		throw;//继续往外抛出
	}
}
int main() {
	try {
		func();
	}
	catch (const MyException& e) {
		cout << "错误信息: " << e.what() << endl;
	}
	return 0;
}

1.5. Inheritance of exception classes (polymorphism, base class objects refer to subclass objects)

  •  Ask the first question?? Why do you want to inherit it, can you not write it yourself???

First of all, it is not impossible to write by yourself, but the scene is not suitable. Everyone writes an exception class, we are happy, but when we need to call the corresponding processing function, do we need to re-modify the class name in every place to call Corresponding processing function?    Where do you set polymorphism??

Is it possible to use the base class to refer to the subclass object, so when calling the interface, we only need to use the base class to call the corresponding processing function, and we only need to inherit the base class to rewrite the required processing function. ......

Review the definition of polymorphism: passing in different objects, calling the same function will have different effects, in fact, the subclass rewrites the base class virtual function 

So now, OK, in fact, companies generally have their own exception handling mechanisms, and their own exception handling classes have standards. When we use them, we can inherit and rewrite virtual functions according to the situation.

  • eg : simply write it by hand
class MyException {
public:
	MyException(int errid, string errmsg)
		: _errid(errid)
		, _errmsg(errmsg)
	{}

	virtual string what() const noexcept {
		return _errmsg;
	}

	virtual int GetErrid() const noexcept {
		return _errid;
	}
protected:
	int _errid;
	string _errmsg;
};
//继承的子类
class SqlException : public MyException {
public:
	SqlException(int errid = 0, const char* msg = "") 
		: MyException(errid, msg)
	{}
	virtual string what() const noexcept {
		string tmp("SqlException: ");
		tmp += _errmsg;
		return tmp;
	}
};
class CacheException : public MyException {
public:
	CacheException(int errid = 0, const char* msg = "")
		: MyException(errid, msg)
	{}
	virtual string what() const noexcept {
		string tmp("CacheException: ");
		tmp += _errmsg;
		return tmp;
	}

};
class HttpServerException : public MyException {
public:
	HttpServerException(int errid = 0, const char* msg = "")
		: MyException(errid, msg)
	{}
	virtual string what() const noexcept {
		string tmp("HttpServerException: ");
		tmp += _errmsg;
		return tmp;
	}
};

2. Introduce some assembly instructions and registers, analyze the assembly process of function calls

2.1. Basics of Register Assembly Instructions 

  • epi : instruction register, which stores the address of the next instruction
  • esp and ebp are both pointer registers
  • esp : stack top register, pointing to the top of the function stack stack, stack pointer
  • ebp : stack bottom register, pointing to the bottom of the stack, frame pointer    
  • push : data into the function stack, modify esp
  • pop : data out of the function stack, modify esp
  • sub : subtraction operation
  • add : addition operation
  • call : function call
  • jump : enter the calling function
  • ret : The return address after the function call ends, returning to the outer calling function
  • move : data transfer, stack top and bottom changes

 

2.2. Analysis of the assembly instructions of the function call stack (VS2019)

  • Parameter reverse push into the stack
  • call calls the function, and automatically pushes the ret address when entering (enters the called function)
  • The ret address that was pushed before ret at the end of the function call (return to the calling function)
  • To clean up parameters, see the processing mechanism, some are called functions to clean up by themselves, some are called functions to clean up

3. The realization of smart pointers from shallow to deep

3.1 Introduction to Smart Pointer RAII Technology

  • First of all, let's figure out the first thing, why we need smart pointers. Smart pointers are a class for resource recovery and usage management pointed to by pointers.
  • Memory leak: What is a memory leak? A memory leak means that we lose control of a piece of memory, but do not release it before we lose control..... The operating system allocates memory from the heap to our process, If we do not actively delete it, the operating system will not be able to allocate it to other processes while the process is running, and then the owner who originally allocated this memory does not delete it after using it, and there is no way to do this memory. Allocated, so it is equivalent to a memory leak
  • Hazards of memory leaks: Memory leaks occur in long-running programs, such as operating systems, background services, etc., and memory leaks will cause slower and slower responses and eventually freeze . 
  • RAII idea: RAII (Resource Acquisition Is Initialization) is a simple technology that uses the object life cycle to control program resources (such as memory, file handles, network connections, mutexes, etc.).
  • So smart pointers appeared. The life cycle of objects defined by smart pointers is limited. Smart pointers are defined as stack objects. When the function ends, the destructor will be automatically called, and resources will be released naturally.... (To avoid Deadlock unique_lock The smart pointer here is the idea)

3.2 The most basic framework model of smart pointers

It is OK to have construction and destructor and basic pointer operations. This is a general framework model, and it is not necessary to have all of them.

template<class T >
class SmartPtr {
public:
	SmartPtr(T* ptr)
		: _ptr(ptr) 
	{}

	~SmartPtr() {
		if (_ptr)
			delete _ptr;
	}

	T& operator() {
		return *_ptr;
	}

	T* operator() {
		return _ptr;
	}
private:
	T* _ptr;
};

3.3 Analysis of Four Kinds of Smart Pointer Characteristics

  • auto_ptr :   one of the four types of smart pointers, the problem lies in copy construction. After assignment, there will be a problem of pointer dangling. If you operate on a dangling pointer, an error will be reported
  • unique_ptr : In order to solve the problem of pointer dangling after auto copy and assignment, the copy construction and assignment overloading are directly prohibited.
  • shared_ptr : It still solves the problem of pointer dangling after auto copy and assignment, but it is not implemented by prohibiting copy and assignment overloading, but through a method called reference technology to avoid pointer dangling, regardless of assignment. , or copy, just operate the reference count + 1, so that after copying, the original pointer will not be dangling due to the transfer to the copy, but share the same address and the same memory resource with the copy.       
  • Note: The operation of shared resources must be protected to avoid the problem of reentrancy of functions. Mutual exclusion locks are used to ensure that only one thread writes to the shared critical resources at a time. Therefore, the + and - of the reference count are All operations require the use of lock protection
  • weak_ptr: In order to solve the problem of circular references, circular references, that is, there is a reference counting relationship between each other, and the real delete of mutual release is limited. . .

  •  The principle of shared_ptr solving circular reference: modify the _pre and _next pointers to weak_ptr smart pointers during reference counting
  • The principle is that when node1->_next = node2; and node2->_prev = node1; _next and _prev of weak_ptr will not increase the reference count of node1 and node2.
struct ListNode
{
	int _data;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};

3.4 Three smart pointer simulation implementation codes

auto_ptr 

namespace tyj {
	template<class T>
	class auto_ptr {
	public:
		auto_ptr(T* ptr) 
			: _ptr(ptr) 
		{}

		auto_ptr(auto_ptr<T>& ap) {
			_ptr = ap._ptr;//转移资源
			ap._ptr = nullptr;//原指针悬空
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap) {
			if (this != &ap) {
				if (_ptr)
					delete _ptr;//清理现有资源
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
		}
		~auto_ptr() {
			if (_ptr)
				delete _ptr;
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}
	private:
		T* _ptr;
	};

}

int main() {
	int* pint = new int[4]{ 0 };
	tyj::auto_ptr<int> smartp(pint);
	*smartp = 1;
	cout << *smartp << endl;
	tyj::auto_ptr<int> smartp2(smartp);
	//*smartp = 1;
	//cout << *smartp << endl;
	//smartp 不可以再进行写入了, 已经悬空了
	return 0;
}

unique_ptr : directly prohibit copy construction and assignment overloading

namespace tyj {
	template<class T>
	class unique_ptr {
	public:
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		~unique_ptr() {
			if (_ptr)
				delete _ptr;
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}
	private:
		T* _ptr;
		unique_ptr(unique_ptr<T>& up) = delete;	//禁止掉构造函数
		unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;//禁止掉复制重载
	};
}

shared_ptr : The way of using reference counting: Because reference counting is a critical resource, its operation must be protected by a mutex and perform atomic operations. . . (protect critical resources)

namespace tyj {
	template <class T>
	class shared_ptr {
	public:
		shared_ptr(T* ptr = nullptr) 
			: _ptr(ptr)
			, _pmtx(new mutex)
			, _pRefCount(new int(1))
		{}
		~shared_ptr() {
			Release();		//释放资源
		}
		//增加引用计数, 赋值拷贝
		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			, _pmtx(sp._pmtx) {
			AddRefCount();//增加引用计数
		}
		shared_ptr<T>& operator=(shared_ptr<T>& sp) {
			if (this != &sp) {
				Release();//先释放可能持有的资源
				_ptr = sp._ptr;
				_pmtx = sp._pmtx;
				_pRefCount = sp._pRefCount;
				AddRefCount();//增加引用计数
			}
			return *this;
		}

		int UseCount() { 
			return *_pRefCount; 
		}

		T* Get() {
			return _ptr;
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}

	private:
		void AddRefCount() {//增加引用计数
			_pmtx->lock();
			++(*_pRefCount);
			_pmtx->unlock();
		}

		void Release() {//释放资源, 减去一次引用计数
			bool deleteFlag = 0;//判断是否需要释放资源, 真正的delete
			_pmtx->lock();
			if (--(*_pRefCount) == 0) {
				delete _ptr;
				delete _pRefCount;
				deleteFlag = 1;
			}
			_pmtx->unlock();

			if (deleteFlag) {
				delete _pmtx;//最后释放锁
			}
		}

	private:
		T* _ptr;//指向管理资源的指针
		int* _pRefCount;//引用计数指针
		mutex* _pmtx;//互斥锁
	};
}

4. Summary

  • This paper first introduces the basic model of anomaly, and then proposes the focus of anomaly learning
  • 1. try {protect code}catch(capture type) {exception handling logic block}
  • 2. You can customize the exception class, you can inherit the standard class and then rewrite the handler function, and the exception information function
  • 3. The exception handling function is searched back along the function stack, and is searched back in the opposite direction of the function call. If the processing function cannot be found in the called function, it will return to the calling function to find the processing function.
  • 4. The essential reason for the inheritance of exception classes: In order to use the base class to receive all derived class objects, and then call the same function interface, to achieve different call results and different processing mechanisms ....    Unify the user's calling interface and class ( The user only needs to use the base class object reference to receive, and it is OK to drop the function, as for the specific object passed in, it does not need to be concerned)
  • 5. Learning of registers and assembly instructions, esp : top of stack register ebp : bottom of stack register epi : instruction register, etc.
  • 6. Four kinds of smart pointers, there are reasons for the iterative evolution of smart pointers:
  • The problem of auto_ptr is that after copying or assignment, the body pointer will be dangling. In order to solve the problem of pointer dangling, unique_ptr directly prohibits assignment overloading and copy construction. The way shared_ptr solves dangling is to add reference counting, weak_ptr is for the cycle of shared_ptr The problem of reference, mutual reference, mutual restriction, and inability to completely release resources arises. Compared with the underlying principle of shared_ptr, it is realized by ++ which does not count references when cyclically referencing each other. 

Guess you like

Origin blog.csdn.net/weixin_53695360/article/details/123125277