[英]What is the correct way to implement error propagation in C?
我來自具有異常(特別是C#)的高級語言,並且通常只捕獲我的邏輯知道如何處理的異常-否則,它們會傳播並導致程序崩潰,但至少它提供了有關發生的情況的清晰信息,在哪里。 我想在C中實現類似的行為。我想在main
的小部分中,從調用的函數中獲取錯誤代碼,以識別發生了什么,並將錯誤消息打印到stderr
並退出並返回相同的錯誤我得到的代碼。
第一個問題-人們是否使用C語言做到這一點,就架構而言,這是否是一個好的目標?
第二個問題是關於錯誤返回碼和錯誤號。 默認情況下,我應該在代碼中使用什么? 似乎這兩個約定在C標准庫中可以互換使用,這非常令人困惑。
第三個問題是關於錯誤代碼本身。 在C#和Java中,除了特定的注釋外,標准的異常類型幾乎涵蓋了您通常會拋出的所有內容。 我應該為此目的使用errno錯誤代碼表,還是應該為我的應用程序創建一個單獨的錯誤代碼表以包含所有特定於它的東西? 還是每個函數都應該維護自己的特定錯誤代碼表?
最后,關於錯誤的其他信息:我發現,至少在C#中,能夠拋出預定義類型的異常並帶有提供更多信息的特定注釋非常有用。 C錯誤管理中使用了類似的東西嗎? 我應該如何實施?
為什么庫函數同時使用返回碼和 errno而不是一個或另一個? 同時使用兩者有什么好處?
看來,您可以使用errno
變量和perror()
函數。 在大多數情況下,它們有助於提供失敗原因。
為了指示失敗,大多數庫函數通常會返回帶前綴的負值(通常為-1
),並將errno
變量設置為特定值,具體取決於錯誤的類型。
通常用於此目的嗎?
是的,這就是擁有errno
和perror()
的目的,恕我直言。
現在,對於用戶定義的函數,您可以為庫函數返回的錯誤創建自己的錯誤代碼(需要一段時間的映射),並且可以從函數中返回自己的值。
編輯:
同時使用兩者有什么好處?
在發生故障事件時,以一種簡單的方式來區分故障原因。
像這樣思考,而不是為錯誤設置固定的reurn值並設置errno,如果函數針對每種類型的錯誤返回不同的值,則需要多少個if-else
或switch
語句來檢查被調用函數是否成功函數,每次調用? 很大。
而是使用固定的返回值指示錯誤,如果有錯誤,則檢查errno以了解錯誤背后的原因。
[希望我很清楚。 英語不是我的母語,對不起]
關於: 我想在我主體的小部分中,從調用的函數中獲取錯誤代碼,確定發生了什么,並將錯誤消息打印到stderr並使用與我得到的相同的錯誤代碼退出
第一個問題-人們是否使用C語言做到這一點,就架構而言,這是否是一個好的目標?
有人這樣做,但所有人都應該這樣做。 被認為是好的形式
第二個問題是關於錯誤返回碼和錯誤號。 默認情況下,我應該在代碼中使用什么? 似乎這兩個約定在C標准庫中可以互換使用,這非常令人困惑。
請參閱第一個答案。 它涵蓋了這一點。 (+1到@Sourav)
第三個問題是關於錯誤代碼本身。 在C#和Java中,除了特定的注釋外,標准的異常類型幾乎涵蓋了您通常會拋出的所有內容。 我應該為此目的使用errno錯誤代碼表,還是應該為我的應用程序創建一個單獨的錯誤代碼表以包含所有特定於它的東西? 還是每個函數都應該維護自己的特定錯誤代碼表?
這完全取決於開發人員 。 創建應用程序時,我將使用生成條件的庫調用固有的特定錯誤代碼/消息。 當我創建一個API(通常是.dll)時,我通常會創建一個包含該應用程序所有可能的返回條件的枚舉,並將C本機庫錯誤也集成到該枚舉中。 成功條件為零/正值,錯誤條件為負。 同時,我創建了一個與每個返回條件相對應的字符串描述數組。 這些可以由應用程序通過一個函數調用,例如 int GetErrorDescription(int error, char *str);
。 但是,可以用幾種不同的方法來處理,這只是我的方法。
例:
/*------------------------------------------
//List of published error codes
/*-----------------------------------------*/
enum {
SUCCESS = 0,
COPYFILE_ERR_1 = -1, //"CopyFile() error. File not found or directory in path not found.",
COPYFILE_ERR_3 = -2, //"CopyFile() error. General I/O error occurred.",
COPYFILE_ERR_4 = -3, //"CopyFile() error. Insufficient memory to complete operation.",
COPYFILE_ERR_5 = -4, //"CopyFile() error. Invalid path or target and source are same.",
COPYFILE_ERR_6 = -5, //"CopyFile() error. Access denied.",
...
FOPEN_ERR_EIO = -11, //"fopen() error. I/O error.",
FOPEN_ERR_EBADF = -12, //"fopen() error. Bad file handle.",
FOPEN_ERR_ENOMEM = -13, //"fopen() error. Insufficient memory.",
...
GETFILESIZE_ERR_3 = -24, //"GetFileSize() error - Insufficient memory to complete operation.",
GETFILESIZE_ERR_4 = -25, //"GetFileSize() error - Invalid path or target and source are same.",
GETFILESIZE_ERR_5 = -26, //"GetFileSize() error - Access denied.",
...
PATH_MUST_BE_C_DRIVE = -38, //"SaveFile path variable must contain \"c:\\\".",
HEADER_FIELD_EMPTY = -39, //"one or more of the header fields are incorrect or empty.",
HEADER_FIELD_ILLEGAL_COMMA = -40, //"one or more of the header fields contains a comma.",
...
MAX_ERR = 46 // defines the size of the static char ErrMsg
};
然后一個函數來獲取的描述:
int API GetErrorMessage (int err, char * retStr)
{
//verify arguments are not NULL
if(retStr == NULL)
{
return UNINIT_POINTER_ARGUMENT;
}
switch(err) {
case COPYFILE_ERR_1 : strcpy(retStr, "CopyFile() error. File not found or directory in path not found." ); break;
case COPYFILE_ERR_3 : strcpy(retStr, "CopyFile() error. General I/O error occurred." ); break;
case COPYFILE_ERR_4 : strcpy(retStr, "CopyFile() error. Insufficient memory to complete operation." ); break;
case COPYFILE_ERR_5 : strcpy(retStr, "CopyFile() error. Invalid path or target and source are same." ); break;
case COPYFILE_ERR_6 : strcpy(retStr, "CopyFile() error. Access denied." ); break;
...
case FOPEN_ERR_EIO : strcpy(retStr, "fopen() error. I/O error." ); break;
case FOPEN_ERR_EBADF : strcpy(retStr, "fopen() error. Bad file handle." ); break;
case FOPEN_ERR_ENOMEM : strcpy(retStr, "fopen() error. Insufficient memory." ); break;
...
case GETFILESIZE_ERR_3 : strcpy(retStr, "GetFileSize() error - Insufficient memory to complete operation." ); break;
case GETFILESIZE_ERR_4 : strcpy(retStr, "GetFileSize() error - Invalid path or target and source are same." ); break;
case GETFILESIZE_ERR_5 : strcpy(retStr, "GetFileSize() error - Access denied." ); break;
...
case HEADER_FIELD_EMPTY : strcpy(retStr, " one or more of the header fields are incorrect or empty." ); break;
case HEADER_FIELD_ILLEGAL_COMMA : strcpy(retStr, " one or more of the header fields contains a comma." ); break;
case EVENT_FIELD_EMPTY : strcpy(retStr, " one or more of the event fields is incorrect or empty." ); break;
case EVENT_FIELD_ILLEGAL_COMMA : strcpy(retStr, " one or more of the event fields contains a comma." ); break;
...
default: strcpy(retStr, ""); break;
}
return 0;
}
最后,關於錯誤的其他信息:我發現,至少在C#中,能夠拋出預定義類型的異常並帶有提供更多信息的特定注釋非常有用。 C錯誤管理中使用了類似的東西嗎? 我應該如何實施?
解決上一個問題的評論也解決了這個問題 。
為什么庫函數同時使用返回碼和errno而不是一個或另一個? 同時使用兩者有什么好處?
再次 ,由蘇拉夫覆蓋。
一般而言,異常類型的行為不是用C語言完成的。約定是,每個函數的返回值都應由直接調用者以及該函數可能附帶的任何輔助變量(例如errno)進行檢查和處理。組。
話雖如此,您可以通過使用setjmp()
和longjmp()
來執行與異常相似的操作。 您可以使用setjmp()
設置跳轉點,並傳入jmp_buf
結構以保存跳轉點。 在代碼的后面,無論調用堆棧有多遠,您都可以將該jmpbuf
傳遞給longjmp()
,這將導致控制權返回到調用setjmp()
位置。 您還將一個值傳遞給longjmp()
,它將是setjmp()
的返回值。 然后,您可以將該值用作對錯誤表的查找,以打印更詳細的消息。
您還可以閱讀與longjmp()
相關的其他幾個問題。
編輯:
一個例子:
jmp_buf jbuf;
void some_function()
{
...
if (some_error()) {
longjmp(jbuf, 2);
/* control never gets here */
}
...
}
void run_program()
{
...
some_function();
...
}
int main()
{
int rval;
if ((rval=setjmp(jbuf)) == 0) {
run_program();
} else {
/* rval will be 2 if the error in some_function() is triggered */
printf("error %d: %s\n", rval, get_err_string(rval));
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.