What happens when you throw an exception in main()

From the last video

Logical analysis of throw Exceptions

When an exception is thrown, it is propagated up the function call stack. During this time, if the exception is caught, the program runs normally. What happens if the exception is still not caught in main(), that is, if it is thrown in main()? (The program crashes, but the results may vary slightly depending on the compiler)

Throw an exception in the main() function


Running the above code on different compilers results in different results;

Run under g++ as follows:

main() begin…
Test()
terminate called after throwing an instance of ‘int’
Aborted (core dumped)
Running under VS2013, the result is as follows:

main() begin…
Test()
The exception debugging dialog box is displayed
As a result, a global terminate() function is called after an exception is thrown in main(), which is handled differently by different compilers.

  
C++ supports custom terminating functions. If you set the custom terminating function by calling set_terminate(), the default terminal() function will be invalid;

(1) Features of the custom end function: the same as the default terminal() end function prototype, there is no parameter and no return value;

On the use
Custom end functionNote:

    
1) You cannot throw an exception again in this function. This is the last chance to handle an exception.

2) The current program must be terminated in some way, such as exit(1), abort();

Exit () : ends the current program and ensures that all global and static local objects are properly destructed;

Abort () : Abort a program without calling any object’s destructor;

(2)
Set_terminate () functionVoid (*)(); void(*)(); 2) The return value is the custom terminate() entry address;

Custom end function test case



2. What happens when an exception is thrown in a destructor

In general, the destructor destroys the used resource. If an exception is thrown during resource destruction, the used resource cannot be completely destroyed. What happens if you dig deeper into this explanation?

If an exception is thrown in the main() function and not caught, the exception will trigger the system’s default terminating function, terminal(). Because different compilers have different internal implementations of the terminal() function,

(1) If the terminal() function ends the program with exit(1), then the destructor may be called, and an exception will be thrown in the destructor, which will lead to a second call to the terminal() function, with disastrous consequences (similar to the secondary release of heap space). However, Powerful Windows and Linux systems solve this problem for us, but some embedded operating systems can cause problems.

(2) if terminal() terminates the program abort(), the situation in (1) does not occur, which is why the g++ compiler does this.

Note: The terminal() terminating function is the last function to process the exception, so it cannot throw an exception again. This rule is violated in (1).

Throwing an exception in the terminal() terminator causes the terminal() terminator to be called a second time.

Conclusion: When an exception is thrown ina destructor, it is dangerous to end terminate() with exit(). You may call terminate() twice, or even loop indefinitely.

Throw an exception case test in the destructor


Running the above code on different compilers results in different results;

Run under g++ as follows:

main() begin…
Test()
Void mterminate() // Call the custom termination function mterminate() for the first time in main()
~Test()// exit(1) At the end of the program, the destructor is called. Abort () is called when the destructor throws an exception again
Aborted (core dumped) // Note: Some older versions of the compiler may call the custom terminate function mterminate(). Void mterminate()
Running under VS2013, the result is as follows:

main() begin…
Test()
void mterminate()
~Test()// exit(1) At the end of the program, the destructor is called, in the destructor again throws an exception, the exception debugging dialog box will pop up
Note: Some older versions of the compiler may call the custom termination function mterminate(), which displays void mterminate().
Conclusion: The new version of the compiler optimizes the behavior of throwing exceptions in destructors, directly terminating programs with exceptions.

3. Abnormal specification of functions

There are many ways to determine whether a function will throw an exception, such as looking at the implementation of the function (unfortunately, third-party libraries do not provide the implementation), or looking at the technical documentation (it may not be the same as the version of the function currently in use), but all of these methods are flawed. There’s actually a much simpler and more efficient way to do this
To determine whether a function will throw an exception, we use the exception declaration directly. This is called the exception specification of the function.

  
The exception declaration is written after the argument list as a modifier to the function declaration; 

[url=]

[/url]


1
/ *
Any exceptions may be thrown
* /
2
void

func1();

3
4
/ *
Exception types that can only be thrown: char and int
* /
5
void

func2()

throw

(

char

.

int

);

6
7
/ *
No exceptions are thrown
* /
8
void

func3()

throw

(a);

[url=]

[/url]



Significance of exception specification:

(1) Prompt function callers to be prepared for exception handling; (if you want to know what type of exception the called function will throw, just open the header file and see how the function is declared;)

(2) Prompt the maintainer of the function not to throw other exceptions;

(3) Exception specification is part of function interface; (used to illustrate how this function is used correctly;)

Abnormal test cases outside of the abnormal specification


Running the above code on different compilers results in different results;

Run under g++ as follows:

func()
terminate called after throwing an instance of ‘char’
Aborted (core dumped)
Running under VS2013, the result is as follows:

func()
Catch (char)// The exception was caught, indicating that the exception specification is not limited
Through a re-examination of the above code results, we found that
In g++, when the exception is not in the function exception specification, a global function unexpected() is called, from which the default global terminating function terminate() is called;

  
However, in vs2013, exceptions are not limited by function exception specifications.

Conclusion:
The g++ compiler follows the c++ specification, while the vs2013 compiler is not limited to this constraint.

Tip: Function exception specifications are handled differently by different compilers, so it’s important to test the compiler you’re using as you develop your project.

C++ supports custom exception functions
; By calling set_unexpected()To set the custom exception function, at this time the system defaultGlobal function unexpected()Will the failure;

(1) Custom
Abnormal functionFeatures: with default
Global function unexpected()As with the prototype, there is no parameter and no return value;

(2) Precautions about using custom exception functions:

    
An exception can be thrown in a function (resuming program execution when the exception conforms to the exception specification of the triggering function; Otherwise, call global terminate() to terminate the program);

(3)
set_unexpected()
functionVoid (*)(); void(*)(); 2) Return value is custom
unexpected()Function entry address;

A test case for customizing unexpected()


Running the above code on different compilers results in different results;

Run under g++ as follows:

func()
void m_unexpected()
Catch (int)// The exception thrown by throw 1 in the custom exception function m_unexpected() is caught because it conforms to the function exception specification
Running under VS2013, the result is as follows:

func()
Catch (char)// VS2013 does not follow the C ++ specification and is not restricted by the exception specification. It directly catches the throw ‘C’ in the function exception specification
  
Conclusion :(g++) unexpected() is the last chance to handle exceptions correctly. If not caught, terminate() is called and the current program ends up with an exception;

(VS2013) There is no limit to the function exception specification, all functions can throw any exception.

4. Analysis of dynamic memory application results

In C language, the malloc function is used to apply for dynamic memory. If it succeeds, the corresponding memory head address is returned. On failure, NULL is returned.

In the c++ specification, when the new and new[] operators are overloaded to dynamically apply for a large enough memory space,

(1) If successful, call the constructor to create the object in the obtained space, and return the object address;

(2) If it fails (out of memory space), the result will be different depending on the compiler;

1) Return NULL; (the behavior of earlier compilers is not part of the c++ specification)

2) Raise STD ::bad_alloc (Later compilers throw exceptions, and some earlier compilers still return NULL)

Note: different compilers are also uncertain about how to throw an exception. The c++ specification throws STD ::bad_alloc in the new_handler() function, which is automatically called when a memory request fails.

When we run out of memory, we call the global new_hander() function, which gives us a chance to clean up enough memory. So, we can customize the new_hander() function and set the custom new_hander() function with the global function set_new_hander(). (as proved by experiments, some compilers do not define the global new_hander() function, such as vs2013, g++, see case 1)

Special note: the return value of set_new_hander() is the default global new_hander() entry address.

Set_terminate () returns the address of the custom terminate() entry;

The return value of the set_unexpected() function is custom
unexpected()The entry address of the function.

Case 1: Prove whether the compiler defines the global new_handler() function


Running the above code on different compilers results in different results;

Running under vs2013 and g++ gives the following result:

Func = 0 // => vs2013 and g++ does not define the global new_handler() function
Run under BCC with the following result:

func = 00401468
Catch (const bad_alloc&)// Defines the global new_handler() function in the BCC and throws the STD ::bad_alloc exception in that function


Case 2: How different compilers behave when memory requests fail


Running the above code on different compilers results in different results;

Run under g++ as follows:

operator new: 4
Test()// NULL is returned because heap space failed, then create object on the failed heap space, when m_value = 0; When (equivalent to assigning a value to an invalid address), the compiler reported a segment error
Segmentation fault (core dumped)
Running under VS2013, the result is as follows:

operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000
Run under BCC with the following result:

operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000
operator delete[]: 00000000
Summary: in g++ compiler, the memory space fails and the constructor continues to be called to create the object, which generates a segment error. In vs2013 and BCC compilers, NULL is returned for memory space allocation failure.

  
The new and delete operators, or new[] and delete[] operators, must be overloaded in order for different compilers to behave the same way when applying for memory. Instead of throwing STD ::bad_alloc, the STD ::bad_alloc exception is returned when a memory application fails. This must be done by throwing () to modify the memory requester function.

Case 3 :(optimization) how different compilers behave when memory requests fail


Through the test, g++, vs2013, BCC three compilers run the same results, the output results are as follows:

operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000

5. New use of the new keyword
(1) nothrow keyword

Use of the nothrow keyword


Running the above code on different compilers results in different results;

Run under g++, BCC, and the result is as follows:

0 // The nothrow keyword is used. If a dynamic memory application fails, NULL is returned


——————–


Catch (const bad_alloc&)// Throw STD ::bad_alloc exception on dynamic memory request failure without nothrow keyword
Failed to compile under VS2013:

The memory request is too large, that is, the total size of the array must not exceed 0x7FFFFFFF bytes.
Conclusion: The nothrow keyword is useful: do not throw an exception regardless of the result of a dynamic memory request, however this may vary from compiler to compiler.

(2) Use new to create an object at the specified address

Create an object at the specified address with new


Run under g++, vs2013, BCC and the result is as follows:

1:2
3:4
Conclusion of dynamic memory application:

(1) Different compilers have different implementation details on dynamic memory allocation;

(2) The compiler may redefine the implementation of new and throw a bad_alloc exception in the implementation; (vs2013, g++)

(3) The default implementation of the compiler may not set the global new_handler() function; (vs2013, g++)

(4) Specific details of new need to be considered for codes with high requirements for portability;

We can further verify this conclusion by using vs2013 as an example. In the compiler installation package, find the two files new. CPP and new2. CPP. C: Program Files (x86)\Microsoft Visual Studio 12.0\VC\ CRT \ SRC (x86)\Microsoft Visual Studio 12.0\VC\ CRT \ SRC)

Docs.microsoft.com/zh-cn/cpp/c…

;

  

  

  
So in VS, when a dynamic memory request fails, STD :: BAD_alloc is thrown instead of returning NULL;

Source code analysis new.cpp


Source code analysis new2.cpp