您好,登錄后才能下訂單哦!
異常是指程序在運行過程中產生可預料的執行分支。如除0操作,數組訪問越界、要打開的文件不存在。
Bug是指程序中的錯誤,是不被預期的運行方式。如野指針、堆空間使用結束未釋放。
C語言中處理異常的方式一般是使用if....else...分支語句。
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
cout << "a is devieded by zero" <<endl;
}
return ret;
}
C語言通過setjmp和longjmp對異常處理進行優化。int setjmp(jmp_buf env);
將上下文保存到jmp_buf結構體中void longjmp(jmp_buf env, int value);
從jmp_buf結構體中恢復setjmp保存的上下文,最終從setjmp函數調用點返回,返回值為value。
#include <iostream>
#include <csetjmp>
using namespace std;
static jmp_buf env;
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
longjmp(env, 1);
}
return ret;
}
int main(int argc, char *argv[])
{
if( setjmp(env) == 0 )
{
double r = divide(1, 1);
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
C++語言中內置了異常處理的語法,try.....catch......。
try語句塊用來處理正常代碼邏輯,catch語句塊用來處理異常處理情況,throw拋出異常。在try語句塊拋出的異常在相應的catch語句塊捕獲處理。
同一個try語句塊可以對應多個catch語句塊,catch語句塊可以定義具體處理的異常類型,不同的類型的異常由不同的catch語句塊處理,try語句塊可以拋出任何類型的異常,catch(...)用于處理所有類型的異常,任何異常都只能被捕獲一次。
throw拋出的異常必須被catch處理,如果當前函數能夠處理異常,繼續執行;如果當前函數不能處理異常,函數停止執行并返回。未被處理的異常會順著函數調用棧向上傳遞,直到被處理為止,否則程序將停止執行。
異常處理的匹配:
A、異常拋出后從上到下嚴格匹配每個catch語句塊處理的類型,不能進行任何類型轉換。
B、catch(...)語句塊只能放到catch語句塊分支的最后位置。
異常處理的使用實例:
try
{
throw 'c';
}
catch(char c)
{
cout << "catch(char c)" << endl;
}
catch(short c)
{
cout << "catch(short c)" << endl;
}
catch(double c)
{
cout << "catch(double c)" << endl;
}
catch(...)
{
cout << "catch(...)" << endl;
}
catch語句塊捕獲的異常重新解釋后可以拋出異常,拋出的異常在外層的try...catch中捕獲。
try
{
try
{
throw 'c';
}
catch(int i)
{
cout << "Inner: catch(int i)" << endl;
throw i;
}
catch(...)
{
cout << "Inner: catch(...)" << endl;
throw;
}
}
catch(...)
{
cout << "Outer: catch(...)" << endl;
}
通常在catch語句塊中捕獲的異常重新解釋后可以再次拋出異常,工程實踐中通常用于統一異常類型,如通過捕獲第三方庫函數中拋出的異常,重新解釋后拋出統一的異常處理信息。
異常的類型可以是自定義類型,自定義類型的異常匹配依舊是自上而下嚴格匹配,但由于賦值兼容性原則在異常匹配中適用,所以匹配子類異常的catch語句塊放在catch分支的上部,匹配父類異常的catch語句塊放在catch分支的下部。
#include <iostream>
using namespace std;
class Parent
{
public:
Parent(int i):code(i)
{
}
private:
int code;
};
class Child : public Parent
{
public:
Child(int i):Parent(i),code(i)
{
}
private:
int code;
};
int main(int argc, char *argv[])
{
try
{
Child child(1);
throw child;
}
catch(const Child& e)
{
cout << "catch(const Child& e)" << endl;
}
catch(const Parent& e)
{
cout << "catch(const Parent& e)" << endl;
}
return 0;
}
STL提供了實用的異常處理類,STL中的異常都是從exception類繼承而來,exception類只要有兩個分支,logic_error和runtime_error。logic_error用于處理程序中可避免邏輯錯誤,runtime_error用于處理程序中無法處理的惡性錯誤。
#include <iostream>
#include <stdexcept>
using namespace std;
int main(int argc, char *argv[])
{
int array[5] = {0};
for(int i = 0; i < 5; i++)
{
array[i] = i;
}
try
{
for(int i = 0; i < 10; i++)
{
if(i >= 5)
{
throw out_of_range("out of range");
}
else
{
cout << array[i] <<endl;
}
}
}
catch(const out_of_range& e)
{
cout << e.what() << endl;
}
return 0;
}
try...catch語句用于分隔正常功能代碼與異常處理代碼。try...catch語句也可以將函數體分隔為兩部分。
函數聲明和定義時可以直接指定可能拋出的異常類型,異常聲明作為函數的一部分可以提高代碼可讀性。
函數異常聲明是一種與編譯器之間的契約,函數聲明異常后就只能拋出聲明的異常。如果拋出其它異常將會導致程序運行終止。也可以通過函數異常聲明定義無異常函數。
#include <iostream>
using namespace std;
//聲明拋出的異常類型為int
void func(int i, int j)throw(int)
{
if(0 < j && j < 10)
{
}
else
{
throw 0;
}
}
void test(int i)try
{
func(i,i);
}
catch(int i)
{
cout << "catch(int i): " << i << endl;
}
catch(...)
{
cout << "Exception:" << endl;
}
int main(int argc, char *argv[])
{
test(10);
test(1);
return 0;
}
上述代碼中,func函數聲明了拋出的異常類型為int,因此func函數只能拋出int類型異常,如果拋出其它類型異常將導致程序運行終止。即使test函數可以對拋出的其它類型異常進行捕獲,程序也會運行終止。
如果函數內部可能會拋出多種類型的異常,需要在函數聲明異常時指定聲明的異常類型,代碼如下:
#include <iostream>
#include <string>
using namespace std;
//聲明拋出的異常類型為int,char,string
void func(int i, int j)throw(int,char,string)
{
if(0 < j && j < 10)
{
throw j;
}
if(10 < j && j < 100)
{
throw 'A';
}
else
{
throw string("string exception.");
}
}
void test(int i)try
{
func(i,i);
}
catch(int i)
{
cout << "catch(int i): " << i << endl;
}
catch(char c)
{
cout << "Exception:" << c << endl;
}
catch(string s)
{
cout << s << endl;
}
catch(...)
{
cout << "Exception:" << endl;
}
int main(int argc, char *argv[])
{
test(115);//string exception.
test(1);//catch(int i): 1
test(20);//Exception:A
return 0;
}
上述代碼中,func函數可以拋出多種類型的異常,test函數會捕獲func函數拋出的多種異常類型。
如果異常沒有被處理,terminate函數會被自動調用。terminate函數是整個程序釋放系統資源的最后機會。默認情況下,terminate函數調用abort庫函數終止程序。abort函數使得程序執行異常而立即退出。
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};
int main(int argc, char *argv[])
{
static Test test;
throw 1;
return 0;
}
上述代碼運行結果如下:
C++支持使用自定義的terminate函數實現替換默認的terminate函數實現。
自定義terminate函數的實現規則如下:
A、自定義一個無返回值、無參數的函數
B、不能拋出任何異常
C、必須以某種方式結束當前程序
通過調用set_terminate函數可以設置自定義的terminate結束函數,其用法如下:
A、參數類型為void (*)()
B、返回值為默認的terminate函數入口地址
#include <iostream>
#include <cstdlib>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};
void terminate_test()
{
cout << "void terminate_test()" << endl;
exit(1);
}
int main(int argc, char *argv[])
{
set_terminate(terminate_test);
static Test test;
throw 1;
return 0;
}
// output:
// Test()
// void terminate_test()
// ~Test()
上述代碼在最終terminate_test結束函數中調用了exit(1),exit函數會確保程序中全局、靜態數據區的對象被正確銷毀。如果使用abort函數替換exit函數,程序運行結果如下:
析構函數中拋出異常可能會導致最終結束函數terminate函數會被重復調用。
C++語言提供用于聲明函數拋出異常的語法聲明。異常聲明作為函數聲明的修飾符,位于函數參數表的后面。函數異常聲明的示例如下:
//可能拋出任何異常
void func1();
//只能拋出的異常類型:char,int
void func2() throw(char, int);
//不拋出任何異常
void func3() throw();
函數異常聲明的意義如下:
A、提示函數調用者必須做好異常處理的準備
B、提示函數的維護者不要拋出其它異常
C、函數異常規格說明是函數接口的一部分
如果函數拋出的異常類型不在函數異常聲明中,全局unexpected()函數會被調用。默認的unexpected()函數會調用全局的terminate函數,可以自定義函數替換默認的unexpected()函數實現。
自定義的unexpected()函數的實現規則如下:
A、自定義一個無返回值、無參數的函數
B、能夠再次拋出異常,當異常符合觸發函數的異常規格說明時,恢復程序執行。否則,調用全局terminate函數結束程序。
通過調用set_unexpected函數可以設置自定義的unexpected()函數,用法如下:
A、參數類型為void (*)()
B、返回值為默認的unexpected()函數入口地址。
#include <iostream>
#include <cstdlib>
using namespace std;
void func() throw(int)
{
cout << "void func()throw(int)" << endl;
throw 'A';
}
void unexpected_test()
{
cout << "void unexpected_test()" << endl;
throw 1;
}
int main(int argc, char *argv[])
{
set_unexpected(unexpected_test);
try
{
func();
}
catch(int)
{
cout << "catch(int)" << endl;
}
catch(char)
{
cout << "catch(char)" << endl;
}
return 0;
}
// output:
// void func()throw(int)
// void unexpected_test()
// catch(int)
C++編譯器不一定對C++語言中函數異常規格說明進行支持。VC++編譯器不支持,G++編譯器支持。
C語言中,malloc函數申請內存失敗時返回NULL值。
C++語言中,對于早期的C++編譯器,new關鍵字申請內存失敗時,返回NULL值;對于現代C++編譯器,new關鍵字申請內存失敗時,拋出std::bad_alloc異常。
C++語言規范中,new關鍵字的標準行為如下:
A、new在內存分配時,如果空間不足,會調用全局的new_handler函數,new_handler函數中拋出std::bad_alloc異常;如果成功,會在分配的空間調用構造函數創建對象,并返回對象的地址。
B、可以自定義new_handler函數,處理默認new內存分配失敗的情況。
#include <iostream>
#include <cstdlib>
using namespace std;
void new_handler_test()
{
cout << "void new_handler_test()" << endl;
cout << "No enough memory" << endl;
exit(1);
}
int main(int argc, char *argv[])
{
set_new_handler(new_handler_test);
int* p = new(std::nothrow) int[10000000000];
return 0;
}
// output:
// void new_handler_test()
// No enough memory
上述代碼中,自定義new_handler函數,拋出異常時會調用。
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;
void new_handler_test()
{
cout << "void new_handler_test()" << endl;
cout << "No enough memory" << endl;
exit(1);
}
int main(int argc, char *argv[])
{
new_handler func = set_new_handler(new_handler_test);
cout << "func = " << func << endl;
if(func)
{
try
{
func();
}
catch(const bad_alloc& e)
{
cout << e.what() << endl;
}
}
return 0;
}
// func = 0
上述代碼是在G++編譯器、VC++編譯器下編譯執行后打印的結果,表明G++編譯器、VC++編譯器沒有設置默認的new_handler函數。如果C++編譯器(如BCC編譯器)設置有默認的new_handler函數,func函數執行時將會拋出bad_alloc異常,被捕獲后打印出bad_alloc異常的相關信息。
不同的C++編譯器,new關鍵字申請動態內存失敗時表現不同。
工程實踐中,為了在不同C++編譯器間統一new關鍵字的行為,提高代碼的可移植性,解決方案如下:
A、重新定義全局的new/delete實現,不拋出任何異常;自定義new_handler函數,不拋出任何異常(不推薦)。
B、在類內重載new/delete操作符,不拋出任何異常。
C、單次動態內存分配時使用nothrow參數,指明new不拋出異常。
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;
class Test
{
int m_data;
public:
Test()
{
cout << "Test()" << endl;
m_data = 0;//異常
}
~Test()
{
cout << "~Test()" << endl;
}
void* operator new (unsigned int size)
{
cout << "operator new: " << size << endl;
// return malloc(size);
return NULL;
}
void operator delete (void* p)
{
cout << "operator delete: " << p << endl;
free(p);
}
void* operator new[] (unsigned int size)
{
cout << "operator new[]: " << size << endl;
// return malloc(size);
return NULL;
}
void operator delete[] (void* p)
{
cout << "operator delete[]: " << p << endl;
free(p);
}
};
int main(int argc, char *argv[])
{
Test* p = new Test();
cout << p << endl;
delete p;
return 0;
}
// output:
// operator new: 4
// Test()
// 異常
上述代碼在執行new操作符函數后會調用Test構造函數,并在初始化m_data成員變量時拋出異常。為了確保不同C++編譯器在調用new關鍵字時具有相同的行為,需要在new失敗時不拋出異常,因此需要在new操作符增加函數的異常聲明。
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;
class Test
{
int m_data;
public:
Test()
{
cout << "Test()" << endl;
m_data = 0;//異常
}
~Test()
{
cout << "~Test()" << endl;
}
void* operator new (unsigned int size) throw()
{
cout << "operator new: " << size << endl;
// return malloc(size);
return NULL;
}
void operator delete (void* p)
{
cout << "operator delete: " << p << endl;
free(p);
}
void* operator new[] (unsigned int size) throw()
{
cout << "operator new[]: " << size << endl;
// return malloc(size);
return NULL;
}
void operator delete[] (void* p)
{
cout << "operator delete[]: " << p << endl;
free(p);
}
};
int main(int argc, char *argv[])
{
Test* p = new Test();
cout << p << endl;
delete p;
p = new Test[5];
cout << p << endl;
delete [] p;
return 0;
}
// output:
// operator new: 4
// 0
// operator new[]: 24
// 0
上述代碼對Test類的new和delete關鍵字進行了重載,統一了new失敗時的行為。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
//不拋出異常
int* p = new(nothrow) int[1000000000];
cout << "p = " << p << endl;
delete [] p;
int array[2] = {0};
struct Test
{
int x;
int y;
};
//在棧空間創建對象
Test* pTest = new(array) Test();
pTest->x = 100;
pTest->y = 200;
cout << array[0] << endl;
cout << array[1] << endl;
//顯示析構
pTest->~Test();
return 0;
}
// output:
// p = 0
// 100
// 200
上述代碼中使用nothrow關鍵字對象new進行限制,確保new創建對象失敗時不會拋出異常。new關鍵字也可以指定創建對象的地址空間,比如棧空間。
不是所有的C++編譯器都遵循C++標準規范,C++編譯器可能重新定義new關鍵字的實現,并在實現中拋出bad_alloc異常。VC++編譯器對new關鍵字進行了重定義,new關鍵字在new.cpp文件中進行了實現。
#ifdef _SYSCRT
#include <cruntime.h>
#include <crtdbg.h>
#include <malloc.h>
#include <new.h>
#include <stdlib.h>
#include <winheap.h>
#include <rtcsup.h>
#include <internal.h>
void * operator new( size_t cb )
{
void *res;
for (;;) {
// allocate memory block
res = _heap_alloc(cb);
// if successful allocation, return pointer to memory
if (res)
break;
// call installed new handler
if (!_callnewh(cb))
break;
// new handler was successful -- try to allocate again
}
RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));
return res;
}
#else /* _SYSCRT */
#include <cstdlib>
#include <new>
_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
* Copyright (c) 1992-2002 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V3.13:0009 */
#endif /* _SYSCRT */
上述代碼顯示,在new失敗時默認拋出bad_alloc異常。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。