国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

深入淺出話異常

2019-11-17 05:26:02
字體:
供稿:網(wǎng)友
標(biāo)準(zhǔn)C的處理機(jī)制

  標(biāo)準(zhǔn)C提供了幾個(gè)異常治理機(jī)制,這些在標(biāo)準(zhǔn)C++也可用,雖然相關(guān)的頭文件名有了改變:舊的C標(biāo)準(zhǔn)頭文件名從<name.h>映射到新的C++標(biāo)準(zhǔn)頭文件名<cname>。(頭文件名的前綴C是為了記憶,指明它們是標(biāo)準(zhǔn)C的庫文件)

  雖然在C++的向后兼容里保留了C的頭文件,但我勸告你在任何可能的地方使用新的頭文件。對于許多實(shí)際使用中,最大的改變是在新的頭文件與namespace std內(nèi)進(jìn)行聲明。請看以下示例:

#include <stdio.h>//在標(biāo)準(zhǔn)C里被替換成#include <cstdio>

FILE *f = fopen("blarney.txt", "r");

std::FILE *f = std::fopen("blarney.txt", "r");//or the more C-esque

#include <cstdio>
using namespace std;

FILE *f = fopen("blarney.txt", "r");
  不幸的是,Microsoft's Visual C++不能在新的頭文件與namespace std同時(shí)具備的條件下進(jìn)行聲明,即使這種行為是標(biāo)準(zhǔn)C必需的。除非等到Visual C++支持這種行為,我將在本行使用舊的C風(fēng)格名字。

  (對于像Microsft的庫供給商來說,實(shí)現(xiàn)這些C庫頭文件的正確性需要維護(hù)與測試兩套不同的代碼,這是一項(xiàng)艱苦的任務(wù),且不會(huì)帶來任何價(jià)值)

  絕對終止

  這是一種徹底忽略異常的方法,大概這種簡單的響應(yīng)是一種安全的退出方法。在一些情形里,這是最正確的方法。

Before you scoff, consider that some exceptions betray a condition so severe that reasonable recovery is unlikely anyway. Perhaps the best example is malloc of a small object returning NULL. If the free store manager can't scrape together a few spare contiguous bytes, your   C庫頭文件<stdlib.h>提供了兩個(gè)不是相當(dāng)完美的程序函數(shù):abort與exit,這些函數(shù)實(shí)現(xiàn)了異常的生命期在第4、5階段。兩者不返回它的調(diào)用者,并且結(jié)束程序。

  雖然兩者在概念上是相關(guān)的,但使用它們的結(jié)果是不同的:

  abort: 粗魯?shù)亟Y(jié)束程序。這是默認(rèn)的,在運(yùn)行時(shí)診斷里調(diào)用abort來安全結(jié)束程序。這種結(jié)束方式可能會(huì)或可能不會(huì)刷新與關(guān)閉打開的文件或刪除臨時(shí)文件,這與你的設(shè)計(jì)有關(guān)。 exit:文明地結(jié)束程序。它附加了關(guān)閉打開的文件與返回狀態(tài)碼給執(zhí)行環(huán)境,exit還調(diào)用你用atexit注冊的回調(diào)函數(shù)。
你通常是在發(fā)生程序重大失敗的情況下調(diào)用abort,原因abort默認(rèn)行為是立即結(jié)束程序,你需要在調(diào)用abort之前保存你的數(shù)據(jù)。(在討論<signal.h>里會(huì)再提到)

  對于兩者的差異,exit執(zhí)行客戶用atexit注冊的清除代碼,它們的調(diào)用順序是按它們被注冊的相反順序來的。示例:

#include <stdio.h>
#include <stdlib.h>

static void atexit_handler_1(void)
{
printf("within 'atexit_handler_1'/n");
}

static void atexit_handler_2(void)
{
printf("within 'atexit_handler_2'/n");
}

int main(void)
{
atexit(atexit_handler_1);
atexit(atexit_handler_2);
exit(EXIT_SUCCESS);
printf("this line should never appear/n");
return 0;
}

/* When run yields
within 'atexit_handler_2'
within 'atexit_handler_1'

and returns a success code to calling environment.
*/
(注重:假如你的程序在main函數(shù)結(jié)束時(shí)沒有顯式調(diào)用exit,那么你用atexit注冊的處理函數(shù)也會(huì)被調(diào)用)。

abort與exit都不將控制權(quán)返回給調(diào)用者,并且結(jié)束程序。 條件結(jié)束
abort與exit無條件終止你的程序。你也可以有條件地結(jié)束你的程序,這種機(jī)制是每一個(gè)程序員喜受的診斷工具:assert宏定義在<assert.h>,如下示例:

#if defined NDEBUG
#define assert(condition) ((void) 0)
#else
#define assert(condition) _assert((condition), #condition, __FILE__, __LINE__)
#endif
//*******************************************************//

(譯注:assert的實(shí)現(xiàn)并不唯一,比如:

Visual C++ 6.0的實(shí)現(xiàn)是:

#define assert(eXP) (void)((exp)(_assert(#exp, __FILE__, __LINE__), 0));

Borland C++ 5.5的實(shí)現(xiàn)是:

#define assert(exp) ((exp) ? (void)0 : _assert(#exp, __FILE__, __LINE__))
  至于函數(shù)_assert(在gcc的庫中_assert是一個(gè)宏)是各家的內(nèi)部實(shí)現(xiàn),不一定得非要_assert這個(gè)名字,其內(nèi)容一般是利用printf函數(shù)(在WIN平臺上往往是調(diào)用MessageBox)輸出出錯(cuò)信息(文件名及行號)并調(diào)用abort終止程序。

  //*******************************************************//

  在這個(gè)定義里,當(dāng)定義了預(yù)處理符號NDEBUG的時(shí)候,斷言是無效的,這意味著assert斷言宏只在你的Debug版本中有效。在Release版本里,assert斷言宏不進(jìn)行任何計(jì)算。由于這個(gè)而會(huì)引起一些側(cè)面效里,比如:


/* debug version */
#undef NDEBUG
#include <assert.h>
#include <stdio.h>

int main(void)
{
int i = 0;
assert(++i != 0);
printf("i is %d/n", i);
return 0;
}
/* 當(dāng)運(yùn)行后輸出:

i is 1
*/
那么現(xiàn)在改變代碼版本到release版本,定義NDEBUG:

/* release version */
#defing NDEBUG
#include <assert.h>
#include <stdio.h>

int main(void)
{
int i = 0;
assert(++i != 0);
printf("i is %d/n", i);
return 0;
}

/* 當(dāng)運(yùn)行后輸出:

i is 0
*/
  因此在assert中只能是比較而不能有實(shí)質(zhì)性的動(dòng)作,否則調(diào)試和發(fā)布版的結(jié)果可能會(huì)大相徑庭。

  因此,為了避免這種差異,確保在assert表達(dá)式不能包含有側(cè)面影響的代碼。

 ?。ㄗg注:切記assert中不能存在任何實(shí)質(zhì)性的動(dòng)作。Microsoft提供了ASSERT和VERIFY等等宏,其中VERIFY可以用在發(fā)布版內(nèi),不過這既非C/C++標(biāo)準(zhǔn),功能亦不夠強(qiáng)大。)

  只在Debug版本里,assert會(huì)調(diào)用_assert函數(shù)。以下是相似代碼:

void _assert(int test, char const *test_image,
char const *file, int line)
{
if (!test)
{
printf("Assertion failed: %s, file %s, line %d/n",
test_image, file, line);
abort();
}
}
  因此。在斷言失敗將產(chǎn)生出具體的診斷信息,包含源程序文件名與行號,之后調(diào)用abort,我給這種機(jī)制的示例是相當(dāng)?shù)拇植?;你的庫?shí)現(xiàn)者改

  assert典型是用在調(diào)試邏輯錯(cuò)誤,它永遠(yuǎn)不會(huì)存在于發(fā)行程序里。

/* 'f' never called by other programs */
static void f(int *p)
{
assert(p != NULL);
/* ... */
}
  我說明一下邏輯錯(cuò)誤與其它運(yùn)行里的錯(cuò)誤關(guān)于使用assert的區(qū)別:

/* ...get file 'name' from user... */
FILE *file = fopen(name, mode);
assert(file != NULL); /* 相當(dāng)可疑的用法???questionable use */
  這種錯(cuò)誤出現(xiàn)在異常表達(dá)式里,但它不是BUG,它是運(yùn)行時(shí)異常,assert可能會(huì)不正確地響應(yīng),你應(yīng)該使用其它機(jī)制,我在下面介紹。

  非局部Goto

  對比于abort與exit,goto 讓你有更多地治理異常的方法,不幸的是gotos是局部的(Local):goto到的標(biāo)識點(diǎn)只能在它們函數(shù)的內(nèi)部,因此不能在程序的任意地方控制它。 為了克服這種限制,標(biāo)準(zhǔn)C庫提供了setjmp與longjmp函數(shù),它可能goto到任何地方。頭文件 <setjmp.h>定義了這些函數(shù),包括間接的jmp_buf,這種機(jī)制簡單直接:

  setjmp(j)設(shè)置goto指針,jmp_buf用當(dāng)前程序上下文信息來初始對象j。這種上下文信息典型包括程序位置指針、堆棧與框架指針,還有其寄存器與內(nèi)存值。當(dāng)初始化上下文信息后,setjmp返回0.

  稍后調(diào)用longjmp(j, r)的結(jié)果goto到對象j指定的地方(之前調(diào)用setjmp進(jìn)行初始化j),當(dāng)調(diào)用的目標(biāo)非局部goto,setjmp返回r,假如r是0返回1.(記住:setjmp在這個(gè)上下文中不能返回0)

By having two classes of return values, setjmp lets you determine how it's being used. When setting j, setjmp works as you normally expect; but as the target of a long jump, setjmp "wakes up" from outside its normal context.
  假如使用longjmp來引發(fā)終止異常,setjmpgoto到正確的異常處理方法里。

#include <setjmp.h>
#include <stdio.h>

jmp_buf j;
void raise_exception(void)
{
printf("exception raised/n");
longjmp(j, 1); /* jump to exception handler */
printf("this line should never appear/n");
}

int main(void)
{
if (setjmp(j) == 0)
{
printf("'setjmp' is initializing 'j'/n");
raise_exception();//Restore context
printf("this line should never appear/n");
}
else
{
printf("'setjmp' was just jumped into/n");
/* this code is the exception handler */
}
return 0;
}

/* 運(yùn)行結(jié)果:

'setjmp' is initializing 'j'
exception raised
'setjmp' was just jumped into
*/

  注重:用jmp_buf來恢復(fù)其它上下文是無效的,請看以下示例: jmp_buf j;


void f(void)
{
setjmp(j);
}

int main(void)
{
f();
longjmp(j, 1); /* 邏輯錯(cuò)誤 */
return 0;
}
  你必須在當(dāng)前調(diào)用上下文中只認(rèn)為setjmp是非局部goto。

  Signals

  標(biāo)準(zhǔn)C庫也標(biāo)準(zhǔn)化了event治理包(雖然較原始)。這個(gè)治理包定義了設(shè)置event與signal,連同標(biāo)準(zhǔn)的引發(fā)與處理方法。那些signals可在異常表達(dá)式或不同的擴(kuò)展event里引發(fā)它們。這也是要討論的目的。我只集中在異常signal. 

  對于使用這些治理包,應(yīng)該包含標(biāo)準(zhǔn)頭文件<signal.h>,這個(gè)頭文件定義了raise與signal函數(shù),sig_atomic_t類型與開始執(zhí)行signal event的宏SIG。在標(biāo)準(zhǔn)要求里有6個(gè)signal宏,但你的庫實(shí)現(xiàn)者可以增加其它。但設(shè)置signal的函數(shù)定義固定在<signal.h>里,你不能擴(kuò)展你自已的signal的設(shè)置函數(shù)。調(diào)用raise來引發(fā)signal,并進(jìn)入到相應(yīng)的處理過程。運(yùn)行時(shí)系統(tǒng)提供了默認(rèn)的處理方法,但你可以安裝你自已的signal行為。處理方法通過sig_atomic_t來與外部程序進(jìn)行通信.對于類型名字的建議,分配給每一對象是原子方式,或中斷安全(interrupt-safe)。

  當(dāng)你注冊signal處理方法的時(shí)候,一般你要提供處理函數(shù)地址。每一個(gè)函數(shù)必需接受int值,且返回void。在這種方法,signal處理方法象setjmp;只有異常上下文能接收單個(gè)整數(shù):

void handler(int signal_value);

void f(void)
{
signal(SIGFPE, handler); /* 注冊處理過程*/
/* ... */
raise(SIGFPE); /* 通過 'SIGFPE'來調(diào)用處理過程 */
}
  有兩種安裝指定處理方法可供選擇:

  signal(SIGxxx, SIG_DFL),//使用系統(tǒng)默認(rèn)的處理方法.

  signal(SIGxxx, SIG_IGN), //告訴系統(tǒng)忽略signal。

  在所有情形里,signal返回指向先前的處理過程的指針或SIG_ERR(意味著注冊失敗)

  當(dāng)處理方法被調(diào)用的時(shí)候,這意味signal開始異常處理。而且你可以在處理方法里自由調(diào)用abort,exit或longjmp來效地結(jié)束異常。一些有趣的地方:實(shí)際上,abort自已在內(nèi)部也調(diào)用raise(SIGABRT),默認(rèn)的SIGABRT異常處理方法顯示診斷信息與結(jié)束程序。但你可以安裝你自已的SIGABRT異常處理方法來改變這種行為:

  但你不能改變abort的終止程序的行為,以下是abort的相似代碼:

void abort(void)
{
raise(SIGABRT);
exit(EXIT_FAILURE);
}

  這兒,假如你SIGABRT異常處理方法返回后,abort也結(jié)束程序。

  在標(biāo)準(zhǔn)C庫里,在signal異常處理方法行為也是有限制的。請看標(biāo)準(zhǔn)7.7.1.1的細(xì)節(jié)。

 ?。ㄗg者注:以下是標(biāo)準(zhǔn)C的草案文件:http://anubis.dkuug.dk/JTC1/SC22/WG14/www/docs里的n843.pdf)

  公共變量

 ?。約etjmp.h>與<signal.h>正常用于檢測到異常后進(jìn)行通知處理過程:當(dāng)?shù)玫疆惓J录耐ㄖ臅r(shí)候,異常處理過程將被喚醒。假如你更喜歡檢查錯(cuò)誤碼的方法,那么標(biāo)準(zhǔn)庫提供了這種行為,包含在頭文件<errno.h>里。這個(gè)頭文件定義了errno,再加上errno一些常用到的值。標(biāo)準(zhǔn)庫要求三個(gè)這樣的值:EDOM, ERANGE,EILSEQ ,它們分別是domain,range與multibyte-sequence error,但編譯器提供商可能增加其它。

  errno,包含了設(shè)置與獲取的庫代碼:庫代碼產(chǎn)生異常對象(單個(gè)整數(shù)),拷貝異常對象的值給予errno,然后在用戶模式中檢測異常。

  主要使用errno的庫函數(shù)集中在<math.h>與<stdio.h>。在程序開始時(shí)errno被設(shè)置為0,而且沒有任何庫代碼會(huì)自動(dòng)再一次設(shè)置errno為0(也就是說當(dāng)你處理了錯(cuò)誤之后,一定要將errno設(shè)置為0才能再調(diào)用標(biāo)準(zhǔn)庫代碼)。因此,對于檢測錯(cuò)誤,你必須設(shè)置0,然后繼續(xù)調(diào)用標(biāo)準(zhǔn)庫程序。以下是示例:

#include <errno.h>
#include <math.h>
#include <stdio.h>

int main(void)
{
double x, y, result;
/* ... somehow set 'x' and 'y' ... */
errno = 0;
result = pow(x, y);
if (errno == EDOM)
printf("domain error on x/y pair/n");
else if (errno == ERANGE)
printf("range error on result/n");
else
printf("x to the y = %d/n", (int) result);
return 0;
}

說明:errno不需要引用到對象:

int *_errno_function()
{
static int real_errno = 0;
return &real_errno;//不需要這樣做
}

#define errno (*_errno_function())

int main(void)
{
errno = 0;
/* ... */
if (errno == EDOM)
/* ... */
}
  返回值與參數(shù)

  errno-像異常對象但沒有限制:

  所有相關(guān)部分必須集中在一起,答應(yīng)設(shè)置與檢測相同對象.

  偶而可以改變對象.

  假如在調(diào)用其它程序之前你沒有重置對象或檢測它們,那么你將錯(cuò)過異常.

  宏與內(nèi)部對象名會(huì)隱藏異常對象。

  靜態(tài)對象天生不具線程全安。

  在總結(jié)里,每一個(gè)對象都是脆弱的:你太輕易濫用它們,在你的編譯器沒有警告信息里,你的程序可能出現(xiàn)不可猜測的行為。

  去掉這些缺陷,你需要的對象應(yīng)該是:

  由兩部分組成:一部分產(chǎn)生異常,另一部分檢測異常。

  取得正確的值.

  不要隱藏它們.

  是線程安全.

  函數(shù)的返回值應(yīng)該符合這些標(biāo)準(zhǔn),因?yàn)樗鼈兪钦{(diào)用函數(shù)里創(chuàng)建的未命名的臨時(shí)對象,且只能被調(diào)用者理解。當(dāng)一個(gè)調(diào)用完成,調(diào)用者可能檢測或拷貝返回對象的值;之后,返回的原始對象消失了,因此不能在使用這個(gè)對象了。由于對象是未命名的對象,它是不能被隱藏的。

  (在C++里,我假定在函數(shù)調(diào)用表達(dá)式只返回左值,意味著調(diào)用者不能返回引用,我的這種限制只在我討論的C兼容技術(shù)這部分里,而且C沒有引用(C標(biāo)準(zhǔn)-C98也加入支持引用),所以我的這個(gè)假設(shè)是合理的)


int f()
{
int error;
/* ... */
if (error) /* Stage 1: error occurred */
return -1; /* Stage 2: generate exception object */
/* ... */
}

int main(void)
{
if (f() != 0) /* Stage 3: detect exception */
{
/* Stage 4: handle exception */
}
/* Stage 5: recover */
}
  返回值是標(biāo)準(zhǔn)C庫用來傳播異常的較好的方法,請思考以下示例:

if ((p = malloc(n)) == NULL)
/* ... */

if ((c = getchar()) == EOF)
/* ... */

if ((ticks = clock()) < 0)
/* ... */
  說明:這種在一個(gè)語句里進(jìn)行捕捉返回值與測試異常的方法是較典型的慣用法。它有兩個(gè)不同的含義:合法的數(shù)據(jù)值與異常值。代碼必須解釋這兩種計(jì)算路徑在哪兒知道它是正確的。

  函數(shù)返回值的方法被運(yùn)用于許多公共語言,Microsoft運(yùn)用在它的COM模型。COM方法通過返回HRESULT來通報(bào)異常對象,Microsoft對這個(gè)值使用32位無符號整數(shù)。不像當(dāng)才的例子只是討論。COM的返回值只返回狀態(tài)與異常信息,其它信息通過指針指向參數(shù)。

  外部指針與C++引用參數(shù)是變種的函數(shù)返回值,但它們有以下幾點(diǎn)不同:

  你可以忽略或丟棄返回值。可是,外部參數(shù)綁定到相應(yīng)的信息,你不能完全忽略它們,與返回值比較,函數(shù)與調(diào)用者把參數(shù)緊緊耦合著。

  任何數(shù)值都可以經(jīng)過外部參數(shù)返回,雖然函數(shù)返值只能發(fā)送一個(gè)值,但外部參數(shù)可以提供多個(gè)邏輯返回值。

  返回值是臨時(shí)對象:在調(diào)用函數(shù)之前它們是不存在的,它們在調(diào)用者結(jié)束后消失。異常對象的生命期比被調(diào)用函數(shù)更長。

  結(jié)尾

  本期圍繞著介紹標(biāo)準(zhǔn)C支持的一般異常的處理方法。在第二期,我將介紹Microsoft擴(kuò)展了這些標(biāo)準(zhǔn)C的方法:專用的異常處理宏與結(jié)構(gòu)化異常處理(SEH)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 双城市| 肇庆市| 石狮市| 赣榆县| 天祝| 玉环县| 武夷山市| 伊金霍洛旗| 内丘县| 丹凤县| 宁乡县| 清水河县| 措勤县| 慈溪市| 五台县| 泰和县| 丽水市| 鲁甸县| 兴宁市| 旌德县| 潼南县| 邹平县| 宜丰县| 锡林郭勒盟| 卢湾区| 恩施市| 凤山县| 裕民县| 建湖县| 莆田市| 哈尔滨市| 封丘县| 正宁县| 鄄城县| 佛冈县| 尉犁县| 宁夏| 中卫市| 合作市| 双江| 北宁市|