![嵌入式編程錯(cuò)誤處理機(jī)制設(shè)計(jì)_第1頁(yè)](http://file4.renrendoc.com/view/c5bfe726bb38f4bb359509bda3e6d095/c5bfe726bb38f4bb359509bda3e6d0951.gif)
![嵌入式編程錯(cuò)誤處理機(jī)制設(shè)計(jì)_第2頁(yè)](http://file4.renrendoc.com/view/c5bfe726bb38f4bb359509bda3e6d095/c5bfe726bb38f4bb359509bda3e6d0952.gif)
![嵌入式編程錯(cuò)誤處理機(jī)制設(shè)計(jì)_第3頁(yè)](http://file4.renrendoc.com/view/c5bfe726bb38f4bb359509bda3e6d095/c5bfe726bb38f4bb359509bda3e6d0953.gif)
![嵌入式編程錯(cuò)誤處理機(jī)制設(shè)計(jì)_第4頁(yè)](http://file4.renrendoc.com/view/c5bfe726bb38f4bb359509bda3e6d095/c5bfe726bb38f4bb359509bda3e6d0954.gif)
![嵌入式編程錯(cuò)誤處理機(jī)制設(shè)計(jì)_第5頁(yè)](http://file4.renrendoc.com/view/c5bfe726bb38f4bb359509bda3e6d095/c5bfe726bb38f4bb359509bda3e6d0955.gif)
版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第第頁(yè)嵌入式編程錯(cuò)誤處理機(jī)制設(shè)計(jì)
正文
前言
本文主要總結(jié)(嵌入式系統(tǒng))(C語(yǔ)言)(編程)中,主要的錯(cuò)誤處理方式。文中涉及的代碼運(yùn)行環(huán)境如下:
一、錯(cuò)誤概念
1.1錯(cuò)誤分類
從嚴(yán)重性而言,程序錯(cuò)誤可分為致命性和非致命性兩類。對(duì)于致命性錯(cuò)誤,無(wú)法執(zhí)行恢復(fù)動(dòng)作,最多只能在用戶屏幕上打印出錯(cuò)消息或?qū)⑵鋵懭肴罩疚募?,然后終止程序;而對(duì)于非致命性錯(cuò)誤,多數(shù)本質(zhì)上是暫時(shí)的(如資源短缺),一般恢復(fù)動(dòng)作是延遲一些時(shí)間后再次嘗試。
從交互性而言,程序錯(cuò)誤可分為用戶錯(cuò)誤和內(nèi)部錯(cuò)誤兩類。用戶錯(cuò)誤呈現(xiàn)給用戶,通常指明用戶操作上的錯(cuò)誤;而程序內(nèi)部錯(cuò)誤呈現(xiàn)給(程序員)(可能攜帶用戶不可接觸的數(shù)據(jù)細(xì)節(jié)),用于查錯(cuò)和排障。
應(yīng)用程序開(kāi)發(fā)者可決定恢復(fù)哪些錯(cuò)誤以及如何恢復(fù)。例如,若磁盤已滿,可考慮刪除非必需或已過(guò)期的數(shù)據(jù);若(網(wǎng)絡(luò))連接失敗,可考慮短時(shí)間延遲后重建連接。選擇合理的錯(cuò)誤恢復(fù)策略,可避免應(yīng)用程序的異常終止,從而改善其健壯性。
1.2處理步驟
錯(cuò)誤處理即處理程序運(yùn)行時(shí)出現(xiàn)的任何意外或異常情況。典型的錯(cuò)誤處理包含五個(gè)步驟:
程序執(zhí)行時(shí)發(fā)生軟件錯(cuò)誤。該錯(cuò)誤可能產(chǎn)生于被底層驅(qū)動(dòng)或內(nèi)核映射為軟件錯(cuò)誤的(硬件)響應(yīng)事件(如除零)。
以一個(gè)錯(cuò)誤指示符(如整數(shù)或結(jié)構(gòu)體)記錄錯(cuò)誤的原因及相關(guān)信息。
程序(檢測(cè))該錯(cuò)誤(讀取錯(cuò)誤指示符,或由其主動(dòng)上報(bào));
程序決定如何處理錯(cuò)誤(忽略、部分處理或完全處理);
恢復(fù)或終止程序的執(zhí)行。
上述步驟用C語(yǔ)言代碼表述如下:
int
func(){
int
bIsErrOccur
=
0;
//do
something
that
might
invoke
erro(rs)
if(bIsErrOccur)
//Stage
1:
error
occurred
return
-1;
//Stage
2:
genera(te)
error
indicator
//...
return
0;}int
main(void){
if(func()
!=
0)
//Stage
3:
detect
error
{
//Stage
4:
handle
error
}
//Stage
5:
recover
or
abort
return
0;}
調(diào)用者可能希望函數(shù)返回成功時(shí)表示完全成功,失敗時(shí)程序恢復(fù)到調(diào)用前的狀態(tài)(但被調(diào)函數(shù)很難保證這點(diǎn))。
二、錯(cuò)誤傳遞
2.1返回值和回傳參數(shù)
C語(yǔ)言通常使用返回值來(lái)標(biāo)志函數(shù)是否執(zhí)行成功,調(diào)用者通過(guò)if等語(yǔ)句檢查該返回值以判斷函數(shù)執(zhí)行情況。常見(jiàn)的幾種調(diào)用形式如下:
if((p
=
malloc(100))
==
NULL)
//...if((c
=
getchar())
==
EOF)
//...if(((ti)cks
=
clock())
Unix系統(tǒng)調(diào)用級(jí)函數(shù)(和一些老的Posix函數(shù))的返回值有時(shí)既包括錯(cuò)誤代碼也包括有用結(jié)果。因此,上述調(diào)用形式可在同一條語(yǔ)句中接收返回值并檢查錯(cuò)誤(當(dāng)執(zhí)行成功時(shí)返回合法的數(shù)據(jù)值)。
返回值方式的好處是簡(jiǎn)便和高效,但仍存在較多問(wèn)題:
代碼可讀性降低
沒(méi)有返回值的函數(shù)是不可靠的。但若每個(gè)函數(shù)都具有返回值,為保持程序健壯性,就必須對(duì)每個(gè)函數(shù)進(jìn)行正確性驗(yàn)證,即調(diào)用時(shí)檢查其返回值。這樣,代碼中很大一部分可能花費(fèi)在錯(cuò)誤處理上,且排錯(cuò)代碼和正常流程代碼攪在一起,比較混亂。
質(zhì)量降級(jí)
條件語(yǔ)句相比其他類型的語(yǔ)句潛藏更多的錯(cuò)誤。不必要的條件語(yǔ)句會(huì)增加排障和白盒測(cè)試的工作量。
信息有限
通過(guò)返回值只能返回一個(gè)值,因此一般只能簡(jiǎn)單地標(biāo)志成功或失敗,而無(wú)法作為獲知具體錯(cuò)誤信息的手段。通過(guò)按位編碼可變通地返回多個(gè)值,但并不常用。字符串處理函數(shù)可參考IntToAscii()來(lái)返回具體的錯(cuò)誤原因,并支持鏈?zhǔn)奖磉_(dá):
char
*IntToAscii(int
dwVal,
char
*pszRes,
int
dwRadix){
if(NULL
==
pszRes)
return
"Arg2Null";
if((dwRadix
36))
return
"Arg3OutOfRange";
//...
return
pszRes;}
定義沖突
不同函數(shù)在成功和失敗時(shí)返回值的取值規(guī)則可能不同。例如,Unix系統(tǒng)調(diào)用級(jí)函數(shù)返回0代表成功,-1代表失??;新的Posix函數(shù)返回0代表成功,非0代表失?。粯?biāo)準(zhǔn)C庫(kù)中isxxx函數(shù)返回1表示成功,0表示失敗。
無(wú)約束性
調(diào)用者可以忽略和丟棄返回值。未檢查和處理返回值時(shí),程序仍然能夠運(yùn)行,但結(jié)果不可預(yù)知。
新的Posix函數(shù)返回值只攜帶狀態(tài)和異常信息,并通過(guò)參數(shù)列表中的指針回傳有用的結(jié)果?;貍鲄?shù)綁定到相應(yīng)的實(shí)參上,因此調(diào)用者不可能完全忽略它們。通過(guò)回傳參數(shù)(如結(jié)構(gòu)體指針)可返回多個(gè)值,也可攜帶更多的信息。
綜合返回值和回傳參數(shù)的優(yōu)點(diǎn),可對(duì)Get類函數(shù)采用返回值(含有用結(jié)果)方式,而對(duì)Set類函數(shù)采用返回值+回傳參數(shù)方式。對(duì)于純粹的返回值,可按需提供如下解析接口:
typedef
enum{
S_OK,
//成功
S_ERROR,
//失敗(原因未明確),通用狀態(tài)
S_NULL_POINTER,
//入?yún)⒅羔槥镹ULL
S_ILLEGAL_PA(RAM),
//參數(shù)值非法,通用
S_OUT_OF_RANGE,
//參數(shù)值越限
S_MAX_STATUS
//不可作為返回值狀態(tài),僅作枚舉最值使用}FUNC_STATUS;#define
RC_NAME(eRetCode)
((eRetCode)
==
S_OK
?
"Success"
:
((eRetCode)
==
S_ERROR
?
"Failure"
:
((eRetCode)
==
S_NULL_POINTER
?
"NullPointer"
:
((eRetCode)
==
S_ILLEGAL_PARAM
?
"IllegalParas"
:
((eRetCode)
==
S_OUT_OF_RANGE
?
"OutOfRange"
:
"Unknown")))))
當(dāng)返回值錯(cuò)誤碼來(lái)自下游模塊時(shí),可能與本模塊錯(cuò)誤碼沖突。此時(shí),建議不要將下游錯(cuò)誤碼直接向上傳遞,以免引起混亂。若允許向終端或文件輸出錯(cuò)誤信息,則可詳細(xì)記錄出錯(cuò)現(xiàn)場(chǎng)(如函數(shù)名、錯(cuò)誤描述、參數(shù)取值等),并轉(zhuǎn)換為本模塊定義的錯(cuò)誤碼再向上傳遞。
2.2全局狀態(tài)標(biāo)志(errno)
Unix系統(tǒng)調(diào)用或某些C標(biāo)準(zhǔn)庫(kù)函數(shù)出錯(cuò)時(shí),通常返回一個(gè)負(fù)值,并設(shè)置全局整型變量errno為一個(gè)含有錯(cuò)誤信息的值。例如,open函數(shù)出錯(cuò)時(shí)返回-1,并設(shè)置errno為EACESS(權(quán)限不足)等值。
C標(biāo)準(zhǔn)庫(kù)頭文件中定義errno及其可能的非零常量取值(以字符'E'開(kāi)頭)。在ANSIC中已定義一些基本的errno常量,(操作系統(tǒng))也會(huì)擴(kuò)展一部分(但其對(duì)錯(cuò)誤描述仍顯匱乏)。
(Linux)系統(tǒng)中,出錯(cuò)常量在errno(3)手冊(cè)頁(yè)中列出,可通過(guò)man3errno命令查看。除EAG(AI)N和EWOULDBLOCK取值相同外,POSIX.1指定的所有出錯(cuò)編號(hào)取值均不同。
Posix和ISOC將errno定義為一個(gè)可修改的整型左值(lvalue),可以是包含出錯(cuò)編號(hào)的一個(gè)整數(shù),或是一個(gè)返回出錯(cuò)編號(hào)指針的函數(shù)。以前使用的定義為:
extern
int
errno;
但在多線程環(huán)境中,多個(gè)線程共享進(jìn)程地址空間,每個(gè)線程都有屬于自己的局部errno(thre(ad)-local)以避免一個(gè)線程干擾另一個(gè)線程。例如,Linux支持多線程存取errno,將其定義為:
extern
int
*__errno_location(void);#define
errno
(*__errno_location())
函數(shù)__errno_location在不同的庫(kù)版本下有不同的定義,在單線程版本中,直接返回全局變量errno的地址;而在多線程版本中,不同線程調(diào)用__errno_location返回的地址則各不相同。
C運(yùn)行庫(kù)中主要在math.h(數(shù)學(xué)運(yùn)算)和stdio.h(I/O操作)頭文件聲明的函數(shù)中使用errno。
使用errno時(shí)應(yīng)注意以下幾點(diǎn):
函數(shù)返回成功時(shí),允許其修改errno。
例如,調(diào)用fopen函數(shù)新建文件時(shí),內(nèi)部可能會(huì)調(diào)用其他庫(kù)函數(shù)檢測(cè)是否存在同名文件。而用于檢測(cè)文件的庫(kù)函數(shù)在文件不存在時(shí),可能會(huì)失敗并設(shè)置errno。這樣,fopen函數(shù)每次新建一個(gè)事先并不存在的文件時(shí),即使沒(méi)有任何程序錯(cuò)誤發(fā)生(fopen本身成功返回),errno也仍然可能被設(shè)置。
因此,調(diào)用庫(kù)函數(shù)時(shí)應(yīng)先檢測(cè)作為錯(cuò)誤指示的返回值。僅當(dāng)函數(shù)返回值指明出錯(cuò)時(shí),才檢查errno值:
//調(diào)用庫(kù)函數(shù)if(返回錯(cuò)誤值)
//檢查errno
庫(kù)函數(shù)返回失敗時(shí),不一定會(huì)設(shè)置errno,取決于具體的庫(kù)函數(shù)。
errno在程序開(kāi)始時(shí)設(shè)置為0,任何庫(kù)函數(shù)都不會(huì)將errno再次清零。
因此,在調(diào)用可能設(shè)置errno的運(yùn)行庫(kù)函數(shù)之前,最好先將errno設(shè)置為0。調(diào)用失敗后再檢查errno的值。
使用errno前,應(yīng)避免調(diào)用其他可能設(shè)置errno的庫(kù)函數(shù)。如:
if
(somecall()
==
-1){
printf("somecall()
failed");
if(errno
==
...)
{
...
}}
somecall()函數(shù)出錯(cuò)返回時(shí)設(shè)置errno。但當(dāng)檢查errno時(shí),其值可能已被printf()函數(shù)改變。若要正確使用somecall()函數(shù)設(shè)置的errno,須在調(diào)用printf()函數(shù)前保存其值:
if
(somecall()
==
-1){
int
dwErrSaved
=
errno;
printf("somecall()
failed");
if(dwErrSaved
==
...)
{
...
}}
類似地,當(dāng)在(信號(hào))處理程序中調(diào)用可重入函數(shù)時(shí),應(yīng)在其前保存其后恢復(fù)errno值。
使用現(xiàn)代版本的C庫(kù)時(shí),應(yīng)包含使用頭文件;在非常老的Unix系統(tǒng)中,可能沒(méi)有該頭文件,此時(shí)可手工聲明errno(如externinterrno)。
C標(biāo)準(zhǔn)定義strerror和perror兩個(gè)函數(shù),以幫助打印錯(cuò)誤信息。
#include
char
*strerror(int
errnum);
該函數(shù)將errnum(即errno值)映射為一個(gè)出錯(cuò)信息字符串,并返回指向該字符串的指針??蓪⒊鲥e(cuò)字符串和其它信息組合輸出到用戶界面,或保存到日志文件中,如通過(guò)fprintf(fp,"somecallfailed(%s)",strerror(errno))將錯(cuò)誤消息打印到fp指向的文件中。
perror函數(shù)將當(dāng)前errno對(duì)應(yīng)的錯(cuò)誤消息的字符串輸出到標(biāo)準(zhǔn)錯(cuò)誤(即stderr或2)上。
#include
void
perror(const
char
*msg);
該函數(shù)首先輸出由msg指向的字符串(用戶自己定義的信息),后面緊跟一個(gè)冒號(hào)和空格,然后是當(dāng)前errno值對(duì)應(yīng)的錯(cuò)誤類型描述,最后是一個(gè)換行符。未使用重定向時(shí),該函數(shù)輸出到控制臺(tái)上;若將標(biāo)準(zhǔn)錯(cuò)誤輸出重定向到/dev/null,則看不到任何輸出。
注意,perror()函數(shù)中errno對(duì)應(yīng)的錯(cuò)誤消息集合與strerror()相同。但后者可提供更多定位信息和輸出方式。
兩個(gè)函數(shù)的用法示例如下:
int
main(int
argc,
char**
argv){
errno
=
0;
FILE
*pFile
=
fopen(argv[1],
"r");
if(NULL
==
pFile)
{
printf("(Can)not
open
file
'%s'(%s)!",
argv[1],
strerror(errno));
perror("Open
file
failed");
}
else
{
printf("Open
file
'%s'(%s)!",
argv[1],
strerror(errno));
perror("Open
file");
fclose(pFile);
}
return
0;}
執(zhí)行結(jié)果為:
[wangxiaoyuan_@localhost
test1]$
./GlbErr
/sdb1/wangxiaoyuan/linux_test/test1/test.cOpen
file
'/sdb1/wangxiaoyuan/linux_test/test1/test.c'(Success)!Open
file:
Success[wangxiaoyuan_@localhost
test1]$
./GlbErr
NonexistentFile.hCannot
open
file
'NonexistentFile.h'(No
such
file
or
directory)!Open
file
failed:
No
such
file
or
directory[wangxiaoyuan_@localhost
test1]$
./GlbErr
NonexistentFile.h
>
testOpen
file
failed:
No
such
file
or
directory[wangxiaoyuan_@localhost
test1]$
./GlbErr
NonexistentFile.h
2>
testCannot
open
file
'NonexistentFile.h'(No
such
file
or
directory)!
也可仿照errno的定義和處理,定制自己的錯(cuò)誤代碼:
int
*_fpErrNo(void){
static
int
dwLocalErrNo
=
0;
return
}#define
ErrNo
(*_fpErrNo())#define
EOUTOFRANGE
1//define
other
error
mac(ros)...int
Callee(void){
ErrNo
=
1;
return
-1;}int
main(void){
ErrNo
=
0;
if((-1
==
Callee())
return
0;}
借助全局狀態(tài)標(biāo)志,可充分利用函數(shù)的接口(返回值和參數(shù)表)。但與返回值一樣,它隱含地要求調(diào)用者在調(diào)用函數(shù)后檢查該標(biāo)志,而這種約束同樣脆弱。
此外,全局狀態(tài)標(biāo)志存在重用和覆蓋的風(fēng)險(xiǎn)。而函數(shù)返回值是無(wú)名的臨時(shí)變量,由函數(shù)產(chǎn)生且只能被調(diào)用者訪問(wèn)。調(diào)用完成后即可檢查或拷貝返回值,然后原始的返回對(duì)象將消失而不能被重用。又因?yàn)闊o(wú)名,返回值不能被覆蓋。
2.3局部跳轉(zhuǎn)(goto)
使用goto語(yǔ)句可直接跳轉(zhuǎn)到函數(shù)內(nèi)的錯(cuò)誤處理代碼處。以除零錯(cuò)誤為例:
double
Division(double
fDividend,
double
fDivisor){
return
fDividend/fDivisor;}int
main(void){
int
dwFlag
=
0;
if(1
==
dwFlag)
{
RaiseException:
printf("The
divisor
cannot
be
0!");
exit(1);
}
dwFlag
=
1;
double
fDividend
=
0.0,
fDivisor
=
0.0;
printf("Enter
the
dividend:
");
scanf("%lf",
printf("Enter
the
divisor
:
");
scanf("%lf",
if(0
==
fDivisor)
//不太嚴(yán)謹(jǐn)?shù)母↑c(diǎn)數(shù)判0比較
goto
RaiseException;
printf("The
quotient
is
%.2lf",
Division(fDividend,
fDivisor));
return
0;}
執(zhí)行結(jié)果如下:
[wangxiaoyuan_@localhost
test1]$
./testEnter
the
dividend:
10Enter
the
divisor
:
0The
divisor
cannot
be
0![wangxiaoyuan_@localhost
test1]$
./testEnter
the
dividend:
10Enter
the
divisor
:
2The
quotient
is
5.00
雖然goto語(yǔ)句會(huì)破壞代碼結(jié)構(gòu)性,但卻非常適用于集中錯(cuò)誤處理。偽代碼示例如下:
Calle(rF)unc(){
if((ret
=
CalleeFunc1())
2.4非局部跳轉(zhuǎn)(setjmp/longjmp)
局部goto語(yǔ)句只能跳到所在函數(shù)內(nèi)部的標(biāo)號(hào)上。若要跨越函數(shù)跳轉(zhuǎn),需要借助標(biāo)準(zhǔn)C庫(kù)提供非局部跳轉(zhuǎn)函數(shù)setjmp()和longjmp()。它們分別承擔(dān)非局部標(biāo)號(hào)和goto的作用,非常適用于處理發(fā)生在深層嵌套函數(shù)調(diào)用中的出錯(cuò)情況。“非局部跳轉(zhuǎn)”是在棧上跳過(guò)若干調(diào)用幀,返回到當(dāng)前函數(shù)調(diào)用路徑上的某個(gè)函數(shù)內(nèi)。
#include
int
setjmp(jmp_buf
env);void
longjmp(jmp_buf
env,int
val);
函數(shù)setjmp()將程序運(yùn)行時(shí)的當(dāng)前系統(tǒng)堆棧環(huán)境保存在緩沖區(qū)env結(jié)構(gòu)中。初次調(diào)用該函數(shù)時(shí)返回值為0。longjmp()函數(shù)根據(jù)setjmp()所保存的env結(jié)構(gòu)恢復(fù)先前的堆棧環(huán)境,即“跳回”先前調(diào)用setjmp時(shí)的程序執(zhí)行點(diǎn)。此時(shí),setjmp()函數(shù)返回longjmp()函數(shù)所設(shè)置的參數(shù)val值,程序?qū)⒗^續(xù)執(zhí)行setjmp調(diào)用后的下一條語(yǔ)句(仿佛從未離開(kāi)setjmp)。參數(shù)val為非0值,若設(shè)置為0,則setjmp()函數(shù)返回1。
可見(jiàn),setjmp()有兩類返回值,用于區(qū)分是首次直接調(diào)用(返回0)和還是由其他地方跳轉(zhuǎn)而來(lái)(返回非0值)。對(duì)于一個(gè)setjmp可有多個(gè)longjmp,因此可由不同的非0返回值區(qū)分這些longjmp。
舉個(gè)簡(jiǎn)單例子說(shuō)明setjmp/longjmp的非局部跳轉(zhuǎn):
jmp_buf
gJmpBuf;void
Func1(){
printf("Enter
Func1");
if(0)longjmp(gJmpBuf,
1);}void
Func2(){
printf("Enter
Func2");
if(0)longjmp(gJmpBuf,
2);}void
Func3(){
printf("Enter
Func3");
if(1)longjmp(gJmpBuf,
3);}int
main(void){
int
dwJmpRet
=
setjmp(gJmpBuf);
printf("dwJmpRet
=
%d",
dwJmpRet);
if(0
==
dwJmpRet)
{
Func1();
Func2();
Func3();
}
else
{
switch(dwJmpRet)
{
case
1:
printf("Jump
back
from
Func1");
break;
case
2:
printf("Jump
back
from
Func2");
break;
case
3:
printf("Jump
back
from
Func3");
break;
default:
printf("Unknown
Func!");
break;
}
}
return
0;}
執(zhí)行結(jié)果為:
dwJmpRet
=
0Enter
Func1Enter
Func2Enter
Func3dwJmpRet
=
3Jump
back
from
Func3
當(dāng)setjmp/longjmp嵌在單個(gè)函數(shù)中使用時(shí),可(模擬)PASCAL語(yǔ)言中嵌套函數(shù)定義(即函數(shù)內(nèi)中定義一個(gè)局部函數(shù))。當(dāng)setjmp/longjmp跨越函數(shù)使用時(shí),可模擬面向?qū)ο笳Z(yǔ)言中的異常(exception)機(jī)制。
模擬異常機(jī)制時(shí),首先通過(guò)setjmp()函數(shù)設(shè)置一個(gè)跳轉(zhuǎn)點(diǎn)并保存返回現(xiàn)場(chǎng),然后使用try塊包含那些可能出現(xiàn)錯(cuò)誤的代碼??稍趖ry塊代碼中或其調(diào)用的函數(shù)內(nèi),通過(guò)longjmp()函數(shù)拋出(throw)異常。拋出異常后,將跳回setjmp()函數(shù)所設(shè)置的跳轉(zhuǎn)點(diǎn)并執(zhí)行catch塊所包含的異常處理程序。
以除零錯(cuò)誤為例:
jmp_buf
gJmpBuf;void
RaiseException(void){
printf("Exception
is
raised:
");
longjmp(gJmpBuf,
1);
//throw,跳轉(zhuǎn)至異常處理代碼
printf("This
line
should
never
get
printed!");}double
Division(double
fDividend,
double
fDivisor){
return
fDividend/fDivisor;}int
main(void){
double
fDividend
=
0.0,
fDivisor
=
0.0;
printf("Enter
the
dividend:
");
scanf("%lf",
printf("Enter
the
divisor
:
");
if(0
==
setjmp(gJmpBuf))
//try塊
{
scanf("%lf",
if(0
==
fDivisor)
//也可將該判斷及RaiseException置于Division內(nèi)
RaiseException();
printf("The
quotient
is
%.2lf",
Division(fDividend,
fDivisor));
}
else
//catch塊(異常處理代碼)
{
printf("The
divisor
cannot
be
0!");
}
return
0;}
執(zhí)行結(jié)果為:
Enter
the
dividend:
10Enter
the
divisor
:
0Exception
is
raised:
The
divisor
cannot
be
0!
通過(guò)組合使用setjmp/longjmp函數(shù),可對(duì)復(fù)雜程序中可能出現(xiàn)的異常進(jìn)行集中處理。根據(jù)longjmp()函數(shù)所傳遞的返回值來(lái)區(qū)分處理各種不同的異常。
使用setjmp/longjmp函數(shù)時(shí)應(yīng)注意以下幾點(diǎn):
必須先調(diào)用setjmp()函數(shù)后調(diào)用longjmp()函數(shù),以恢復(fù)到先前被保存的程序執(zhí)行點(diǎn)。若調(diào)用順序相反,將導(dǎo)致程序的執(zhí)行流變得不可預(yù)測(cè),很容易導(dǎo)致程序崩潰。
longjmp()函數(shù)必須在setjmp()函數(shù)的作用域之內(nèi)。在調(diào)用setjmp()函數(shù)時(shí),它保存的程序執(zhí)行點(diǎn)環(huán)境只在當(dāng)前主調(diào)函數(shù)作用域以內(nèi)(或以后)有效。若主調(diào)函數(shù)返回或退出到上層(或更上層)的函數(shù)環(huán)境中,則setjmp()函數(shù)所保存的程序環(huán)境也隨之失效(函數(shù)返回時(shí)堆棧內(nèi)存失效)。這就要求setjmp()不可該封裝在一個(gè)函數(shù)中,若要封裝則必須使用宏(詳見(jiàn)《C語(yǔ)言接口與實(shí)現(xiàn)》“第4章異常與斷言”)。
通常將jmp_buf變量定義為全局變量,以便跨函數(shù)調(diào)用longjmp。
通常,存放在存儲(chǔ)器中的變量將具有l(wèi)ongjmp時(shí)的值,而在(CPU)和浮點(diǎn)(寄存器)中的變量則恢復(fù)為調(diào)用setjmp時(shí)的值。因此,若在調(diào)用setjmp和longjmp之間修改自動(dòng)變量或寄存器變量的值,當(dāng)setjmp從longjmp調(diào)用返回時(shí),變量將維持修改后的值。若要編寫使用非局部跳轉(zhuǎn)的可移植程序,必須使用volatile屬性。
使用異常機(jī)制不必每次調(diào)用都檢查一次返回值,但因?yàn)槌绦蛑腥魏挝恢枚伎赡軖伋霎惓?,必須時(shí)刻考慮是否捕捉異常。在大型程序中,判斷是否捕捉異常會(huì)是很大的思維負(fù)擔(dān),影響開(kāi)發(fā)效率。相比之下,通過(guò)返回值指示錯(cuò)誤有利于調(diào)用者在最近出錯(cuò)的地方進(jìn)行檢查。此外,返回值模式中程序的運(yùn)行順序一目了然,對(duì)維護(hù)者可讀性更高。因此,應(yīng)用程序中不建議使用setjmp/longjmp“異常處理”機(jī)制(除非庫(kù)或框架)。
2.5信號(hào)(signal/raise)
在某些情況下,主機(jī)環(huán)境或操作系統(tǒng)可能發(fā)出信號(hào)(signal)事件,指示特定的編程錯(cuò)誤或嚴(yán)重事件(如除0或中斷等)。這些信號(hào)本意并非用于錯(cuò)誤捕獲,而是指示與正常程序流不協(xié)調(diào)的外部事件。
為處理信號(hào),需要使用以下信號(hào)相關(guān)函數(shù):
#include
typedef
void
(*fpSigFunc)(int);fpSigFunc
signal(int
signo,
fpSigFunc
fpHandler);int
raise(int
signo);
其中,參數(shù)signo是Unix系統(tǒng)定義的信號(hào)編號(hào)(正整數(shù)),不允許用戶自定義信號(hào)。參數(shù)fpHandler是常量SIG_DFL、常量SIG_IGN或當(dāng)接收到此信號(hào)后要調(diào)用的信號(hào)處理函數(shù)(signalhandler)的地址。若指定SIG_DFL,則接收到此信號(hào)后調(diào)用系統(tǒng)的缺省處理函數(shù);若指定SIG_IGN,則向內(nèi)核表明忽略此信號(hào)(SIGKILL和SIGSTOP不可忽略)。某些異常信號(hào)(如除數(shù)為零)不太可能恢復(fù),此時(shí)信號(hào)處理函數(shù)可在程序終止前正確地清理某些資源。信號(hào)處理函數(shù)所收到的異常信息僅是一個(gè)整數(shù)(待處理的信號(hào)事件),這點(diǎn)與setjmp()函數(shù)類似。
signal()函數(shù)執(zhí)行成功時(shí)返回前次掛接的處理函數(shù)地址,失敗時(shí)則返回SIG_ERR。信號(hào)通過(guò)調(diào)用raise()函數(shù)產(chǎn)生并被處理函數(shù)捕獲。
以除零錯(cuò)誤為例:
void
fphandler(int
dwSigNo){
printf("Exception
is
raised,
dwSigNo=%d!",
dwSigNo);}int
main(void){
if(SIG_ERR
==
signal(SIGFPE,
fphandler))
{
fprintf(stderr,
"Fail
to
set
SIGFPE
handler!");
exit(EXIT_FAILURE);
}
double
fDividend
=
10.0,
fDivisor
=
0.0;
if(0
==
fDivisor)
{
raise(SIGFPE);
exit(EXIT_FAILURE);
}
printf("The
quotient
is
%.2lf",
fDividend/fDivisor);
return
0;}
執(zhí)行結(jié)果為"Exceptionisraised,dwSigNo=8!"(0.0不等同于0,因此系統(tǒng)未檢測(cè)到浮點(diǎn)異常)。
若將被除數(shù)(Dividend)和除數(shù)(Divisor)改為整型變量:
int
main(void){
if(SIG_ERR
==
signal(SIGFPE,
fphandler))
{
fprintf(stderr,
"Fail
to
set
SIGFPE
handler!");
exit(EXIT_FAILURE);
}
int
dwDividend
=
10,
dwDivisor
=
0;
double
fQuotient
=
dwDividend/dwDivisor;
printf("The
quotient
is
%.2lf",
fQuotient);
return
0;}
則執(zhí)行后循環(huán)輸出"Exceptionisraised,dwSigNo=8!"。這是因?yàn)檫M(jìn)程捕捉到信號(hào)并對(duì)其進(jìn)行處理時(shí),進(jìn)程正在執(zhí)行的指令序列被信號(hào)處理程序臨時(shí)中斷,它首先執(zhí)行該信號(hào)處理程序中的指令。若從信號(hào)處理程序返回(未調(diào)用exit或longjmp),則繼續(xù)執(zhí)行在捕捉到信號(hào)時(shí)進(jìn)程正在執(zhí)行的正常指令序列。因此,每次系統(tǒng)調(diào)用信號(hào)處理函數(shù)后,異??刂屏鬟€會(huì)返回除0指令繼續(xù)執(zhí)行。而除0異常不可恢復(fù),導(dǎo)致反復(fù)輸出異常。
規(guī)避方法有兩種:
將SIGFPE信號(hào)變成系統(tǒng)默認(rèn)處理,即signal(SIGFPE,SIG_DFL)。
此時(shí)執(zhí)行輸出為"Floatingpointexception"。
利用setjmp/longjmp跳過(guò)引發(fā)異常的指令:
jmp_buf
gJmpBuf;void
fphandler(int
dwSigNo){
printf("Exception
is
raised,
dwSigNo=%d!",
dwSigNo);
longjmp(gJmpBuf,
1);}int
main(void){
if(SIG_ERR
==
signal(SIGFPE,
SIG_DFL))
{
fprintf(stderr,
"Fail
to
set
SIGFPE
handler!");
exit(EXIT_FAILURE);
}
int
dwDividend
=
10,
dwDivisor
=
0;
if(0
==
setjmp(gJmpBuf))
{
double
fQuotient
=
dwDividend/dwDivisor;
printf("The
quotient
is
%.2lf",
fQuotient);
}
else
{
printf("The
divisor
cannot
be
0!");
}
return
0;}
注意,在信號(hào)處理程序中還可使用sigsetjmp/siglongjmp函數(shù)進(jìn)行非局部跳轉(zhuǎn)。相比setjmp函數(shù),sigsetjmp函數(shù)增加一個(gè)信號(hào)屏蔽字參數(shù)。
三
錯(cuò)誤處理
3.1終止(abort/exit)
致命性錯(cuò)誤無(wú)法恢復(fù),只能終止程序。例如,當(dāng)空閑堆管理程序無(wú)法提供可用的連續(xù)空間時(shí)(調(diào)用malloc返回NULL),用戶程序的健壯性將嚴(yán)重受損。若恢復(fù)的可能性渺茫,則最好終止或重啟程序。
標(biāo)準(zhǔn)C庫(kù)提供exit()和abort()函數(shù),分別用于程序正常終止和異常終止。兩者都不會(huì)返回到調(diào)用者中,且都導(dǎo)致程序被強(qiáng)行結(jié)束。
exit()及其相似函數(shù)原型聲明如下:
#include
void
exit(int
status);void
_Exit(int
status);#include
void
_exit(int
status);
其中,exit和_Exit由ISOC說(shuō)明,而_exit由Posix.1說(shuō)明。因此使用不同的頭文件。
ISOC定義_Exit旨在為進(jìn)程提供一種無(wú)需運(yùn)行終止處理程序(exithandler)或信號(hào)處理程序(signalhandler)而終止的方法,是否沖洗標(biāo)準(zhǔn)I/O流則取決于實(shí)現(xiàn)。Unix系統(tǒng)中_Exit和_exit同義,兩者均直接進(jìn)入內(nèi)核,而不沖洗標(biāo)準(zhǔn)I/O流。_exit函數(shù)由exit調(diào)用,處理Unix特定的細(xì)節(jié)。
exit()函數(shù)首先調(diào)用執(zhí)行各終止處理程序,然后按需多次調(diào)用fclose函數(shù)關(guān)閉所有已打開(kāi)的標(biāo)準(zhǔn)I/O流(將所有緩沖的輸出數(shù)據(jù)沖洗寫到文件上),然后調(diào)用_exit函數(shù)進(jìn)入內(nèi)核。
標(biāo)準(zhǔn)函數(shù)庫(kù)中有一種“緩沖I/O(bufferedI/O)”機(jī)制。該機(jī)制對(duì)于每個(gè)打開(kāi)的文件,在內(nèi)存中維護(hù)一片緩沖區(qū)。每次讀文件時(shí)會(huì)連續(xù)讀出若干條記錄,下次讀文件時(shí)就可直接從內(nèi)存緩沖區(qū)中讀?。幻看螌懳募r(shí)也僅僅寫入內(nèi)存緩沖區(qū),等滿足一定條件(如緩沖區(qū)填滿,或遇到換行符等特定字符)時(shí)再將緩沖區(qū)內(nèi)容一次性寫入文件。
通過(guò)盡可能減少read和write調(diào)用的次數(shù),該機(jī)制可顯著提高文件讀寫速度,但也給編程帶來(lái)某些麻煩。例如,向文件內(nèi)寫入一些數(shù)據(jù)時(shí),若未滿足特定條件,數(shù)據(jù)會(huì)暫存在緩沖區(qū)內(nèi)。(開(kāi)發(fā)者)并不知曉這點(diǎn),而調(diào)用_exit()函數(shù)直接關(guān)閉進(jìn)程,導(dǎo)致緩沖區(qū)數(shù)據(jù)丟失。因此,若要保證數(shù)據(jù)完整性,必須調(diào)用exit()函數(shù),或在調(diào)用_exit()函數(shù)前先通過(guò)fflush()函數(shù)將緩沖區(qū)內(nèi)容寫入指定的文件。
例如,調(diào)用printf函數(shù)(遇到換行符''時(shí)自動(dòng)讀出緩沖區(qū)中內(nèi)容)函數(shù)后再調(diào)用exit:
int
main(void){
printf("Using
exit...");
printf("This
is
the
content
in
buffer");
exit(0);
printf("This
line
will
never
be
reached");}
執(zhí)行輸出為:
Using
exit...This
is
the
content
in
buffer(結(jié)尾無(wú)換行符)
調(diào)用printf函數(shù)后再調(diào)用_exit:
int
main(void){
printf("Using
_exit...");
printf("This
is
the
content
in
buffer");
fprintf(stdout,
"Standard
output
stream");
fprintf(stderr,
"Standard
error
stream");
//fflush(stdout);
_exit(0);}
執(zhí)行輸出為:
Using
_exit...Standard
error
stream(結(jié)尾無(wú)換行符)
若取消fflush句解釋,則執(zhí)行輸出為:
Using
_exit...Standard
error
streamThis
is
the
content
in
bufferStandard
output
stream(結(jié)尾無(wú)換行符)
通常,標(biāo)準(zhǔn)錯(cuò)誤是不帶緩沖的,打開(kāi)至終端設(shè)備的流(如標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出)是行緩沖的(遇換行符則執(zhí)行I/O操作);其他所有流則是全緩沖的(填滿標(biāo)準(zhǔn)I/O緩沖區(qū)后才執(zhí)行I/O操作)。
三個(gè)exit函數(shù)都帶有一個(gè)整型參數(shù)status,稱之為終止?fàn)顟B(tài)(或退出狀態(tài))。該參數(shù)取值通常為兩個(gè)宏,即EXIT_SUCCESS(0)和EXIT_FAILURE(1)。大多數(shù)Unixshell都可檢查進(jìn)程的終止?fàn)顟B(tài)。若(a)調(diào)用這些函數(shù)時(shí)不帶終止?fàn)顟B(tài),或(b)main函數(shù)執(zhí)行了無(wú)返回值的return語(yǔ)句,或(c)main函數(shù)未聲明返回類型為整型,則該進(jìn)程的終止?fàn)顟B(tài)未定義。但若main函數(shù)的返回類型為整型,且執(zhí)行到最后一條語(yǔ)句時(shí)返回(隱式返回),則該進(jìn)程的終止?fàn)顟B(tài)為0。
exit系列函數(shù)是最簡(jiǎn)單直接的錯(cuò)誤處理方式,但程序出錯(cuò)終止時(shí)無(wú)法捕獲異常信息。ISOC規(guī)定一個(gè)進(jìn)程可以注冊(cè)32個(gè)終止處理函數(shù)。這些函數(shù)可編寫為自定義的清理代碼,將由exit()函數(shù)自動(dòng)調(diào)用,并可使用atexit()函數(shù)進(jìn)行注冊(cè)。
#include
int
atexit(void
(*func)(void));
該函數(shù)的參數(shù)是一個(gè)無(wú)參數(shù)無(wú)返回值的終止處理函數(shù)。exit()函數(shù)按注冊(cè)的相反順序調(diào)用這些函數(shù)。同一函數(shù)若注冊(cè)多次,則被調(diào)用多次。即使不調(diào)用exit函數(shù),程序退出時(shí)也會(huì)執(zhí)行atexit注冊(cè)的函數(shù)。
通過(guò)結(jié)合exit()和atexit()函數(shù),可在程序出錯(cuò)終止時(shí)拋出異常信息。以除零錯(cuò)誤為例:
double
Division(double
fDividend,
double
fDivisor){
return
fDividend/fDivisor;}void
RaiseException1(void){
printf("Exception
is
raised:
");}void
RaiseException2(void){
printf("The
divisor
cannot
be
0!");}int
main(void){
double
fDividend
=
0.0,
fDivisor
=
0.0;
printf("Enter
the
dividend:
");
scanf("%lf",
printf("Enter
the
divisor
:
");
scanf("%lf",
if(0
==
fDivisor)
{
atexit(RaiseException2);
atexit(RaiseException1);
exit(EXIT_FAILURE);
}
printf("The
quotient
is
%.2lf",
Division(fDividend,
fDivisor));
return
0;}
執(zhí)行結(jié)果為:
Enter
the
dividend:
10Enter
the
divisor
:
0Exception
is
raised:
The
divisor
cannot
be
0!
注意,通過(guò)atexit()注冊(cè)的終止處理函數(shù)必須顯式(使用return語(yǔ)句)或隱式地正常返回,而不能通過(guò)調(diào)用exit()或longjmp()等其他方式終止,否則將導(dǎo)致未定義的行為。例如,在GCC4.1.2編譯環(huán)境下,調(diào)用exit()終止時(shí)仍等效于正常返回;而VC6.0編譯環(huán)境下,調(diào)用exit()的處理函數(shù)將阻止其他已注冊(cè)的處理函數(shù)被調(diào)用,并且可能導(dǎo)致程序異常終止甚至崩潰。
嵌套調(diào)用exit()函數(shù)將導(dǎo)致未定義的行為,因此在終止處理函數(shù)或信號(hào)處理函數(shù)中盡量不要調(diào)用exit()。
abort()函數(shù)原型聲明如下:
#include
void
abort(void);
該函數(shù)將SIGABRT信號(hào)發(fā)送給調(diào)用進(jìn)程(進(jìn)程不應(yīng)忽略此信號(hào))。
ISOC規(guī)定,調(diào)用abort將向主機(jī)環(huán)境遞送一個(gè)未成功終止的通知,其方法是調(diào)用raise(SIGABRT)函數(shù)。因此,abort()函數(shù)理論上的實(shí)現(xiàn)為:
void
abort(void){
raise(SIGABRT);
exit(EXIT_FAILURE);}
可見(jiàn),即使捕捉到SIGABRT信號(hào)且相應(yīng)信號(hào)處理程序返回,abort()函數(shù)仍然終止程序。Posix.1也說(shuō)明abort()函數(shù)并不理會(huì)進(jìn)程對(duì)此信號(hào)的阻塞和忽略。
進(jìn)程捕捉到SIGABRT信號(hào)后,可在其終止之前執(zhí)行所需的清理操作(如調(diào)用exit)。若進(jìn)程不在信號(hào)處理程序中終止自己,Posix.1聲明當(dāng)信號(hào)處理程序返回時(shí),abort()函數(shù)終止該進(jìn)程。
ISOC規(guī)定,abort()函數(shù)是否沖洗輸出流、關(guān)閉已打開(kāi)文件及刪除臨時(shí)文件由實(shí)現(xiàn)決定。Posix.1則要求若abort()函數(shù)終止進(jìn)程,則它對(duì)所有打開(kāi)標(biāo)準(zhǔn)I/O流的效果應(yīng)當(dāng)與進(jìn)程終止前對(duì)每個(gè)流調(diào)用fclose相同。為提高可移植性,若希望沖洗標(biāo)準(zhǔn)I/O流,則應(yīng)在調(diào)用abort()之前執(zhí)行這種操作。
3.2斷言(assert)
abort()和exit()函數(shù)無(wú)條件終止程序。也可使用斷言(assert)有條件地終止程序。
assert是診斷調(diào)試程序時(shí)經(jīng)常使用的宏,定義在內(nèi)。該宏的典型實(shí)現(xiàn)如下:
#ifdef
NDEBUG
#define
assert(expr)
((void)
0)#else
extern
void
__assert((const
char
*,
const
char
*,
int,
const
char
*));
#define
assert(expr)
((void)
((expr)
||
(__assert(#expr,
__FILE__,
__LINE__,
__FUNCTION__),
0)))#endif
可見(jiàn),assert宏僅在調(diào)試版本(未定義NDEBUG)中有效,且調(diào)用__assert()函數(shù)。該函數(shù)將輸出發(fā)生錯(cuò)誤的文件名、代碼行、函數(shù)名以及條件表達(dá)式:
void
__assert(const
char
*assertion,
const
char
*
filename,
int
linenumber,
register
const
char
*
function){
fprintf(stderr,
"
[%s(%d)%s]
Assertion
'%s'
failed.",
filename,
linenumber,
((function
==
NULL)
?
"UnknownFunc"
:
function),
assertion);
abort();}
因此,assert宏實(shí)際上是一個(gè)帶有錯(cuò)誤說(shuō)明信息的abort(),并做了前提條件檢查。若檢查失敗(斷言表達(dá)式為邏輯假),則報(bào)告錯(cuò)誤并終止程序;否則繼續(xù)執(zhí)行后面的語(yǔ)句。
使用者也可按需定制assert宏。例如,另一實(shí)現(xiàn)版本為:
#undef
assert#ifdef
NDEBUG
#define
assert(expr)
((void)
0)#else
#define
assert(expr)
((void)
((expr)
||
(fprintf(stderr,
"[%s(%d)]
Assertion
'%s'
failed.",
__FILE__,
__LINE__,
#expr),
abort(),
0)))#endif
注意,expr1||expr2表達(dá)式作為單獨(dú)語(yǔ)句出現(xiàn)時(shí),等效于條件語(yǔ)句if(!(expr1))expr2。這樣,assert宏就可擴(kuò)展為一個(gè)表達(dá)式,而不是一條語(yǔ)句。逗號(hào)表達(dá)式expr2返回最后一個(gè)表達(dá)式的值(即0),以符合||操作符的要求。
使用斷言時(shí)應(yīng)注意以下幾點(diǎn):
斷言用于檢測(cè)理論上絕不應(yīng)該出現(xiàn)的情況,如入?yún)⒅羔槥榭?、除?shù)為0等。
對(duì)比以下兩種情況:
char
*Strcpy(char
*pszDst,
co
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 人教版數(shù)學(xué)七年級(jí)上冊(cè)4.3.2《 角的比較與運(yùn)算》聽(tīng)評(píng)課記錄
- 魯教版地理七年級(jí)下冊(cè)8.1《自然特征與農(nóng)業(yè)》聽(tīng)課評(píng)課記錄
- 小學(xué)二年級(jí)上冊(cè)乘法口算題
- 蘇教版三年級(jí)數(shù)學(xué)上冊(cè)口算練習(xí)試題全套
- 集團(tuán)公司戰(zhàn)略合作框架協(xié)議書(shū)范本
- 藥店?duì)I業(yè)員聘用合同范本
- 2025年度虛擬現(xiàn)實(shí)游戲配音音效音樂(lè)委托協(xié)議
- 2025年度二零二五年度健身工作室門面店轉(zhuǎn)讓合同
- 大連市物業(yè)管理委托合同
- 2025年度咖啡連鎖品牌檔口轉(zhuǎn)讓及運(yùn)營(yíng)管理合同
- 陰道鏡幻燈課件
- 現(xiàn)代漢語(yǔ)詞匯學(xué)精選課件
- PCB行業(yè)安全生產(chǎn)常見(jiàn)隱患及防范措施課件
- 上海音樂(lè)學(xué)院 樂(lè)理試題
- SAP中國(guó)客戶名單
- DB32∕T 186-2015 建筑消防設(shè)施檢測(cè)技術(shù)規(guī)程
- 2022年福建泉州中考英語(yǔ)真題【含答案】
- 汽車座椅骨架的焊接夾具畢業(yè)設(shè)計(jì)說(shuō)明書(shū)(共23頁(yè))
- 露天礦山職業(yè)危害預(yù)先危險(xiǎn)分析表
- 淺談固定資產(chǎn)的審計(jì)
- WZCK-20系列微機(jī)直流監(jiān)控裝置使用說(shuō)明書(shū)(v1.02)
評(píng)論
0/150
提交評(píng)論