Linux系統C/C++通用錯誤碼實現模板
背景
公司C++專案初期是安排不同的人編寫不同的模組,有嵌入式ARM的,有socket協議的,有mysql的,有redis的,不同人風格不同。由於當時我還在運維小組搞docker,沒參與規則的制定,後來接手時,開始確定編碼規範,也建立了Git管理(Git嘗試過sub modules,不過太過繁瑣,最終棄用了),由於歷史原因,後面要不斷重構程式碼。
錯誤碼大型專案中都會使用到,不同模組雖然有不同的錯誤返回值,但還是有必要建立一套基本的錯誤碼使用系統,讓大家都遵守。否則,錯誤碼隨便定義、使用,重構起來十分麻煩。
本文是在重構專案錯誤碼之前進行的自測,與專案正式使用的程式碼有些許不同,但本質上是一致的。參考Linux核心和系統呼叫,確定了一些基本的通用類錯誤碼。至於與業務有關的,則自由定製,對外形式、介面保持不同。
程式碼
閒話不提,直接上程式碼。
標頭檔案
標頭檔案my_errorcode.h如下:
/**
* @file my_errorcode.h
* @author Late Lee
* @date 2018.9.1
*
* @brief
* 通用錯誤碼定義及實現
*
* @note
* 1、錯誤碼從0開始計算,順序新增。
* 2、實際使用時,除E_OK外,錯誤碼可以是負數(需要手工新增'-'),也可以是正數。
* 3、錯誤碼可以是任何數字,但不在列舉之列則顯示未知錯誤碼。
*
* @log
*
*/
#ifndef _MY_ERRORCODE_H
#define _MY_ERRORCODE_H
enum errorcode{
E_OK = 0, ///< 成功
E_FAIL, ///< 一般性錯誤
E_INNER, ///< 內部錯誤(一般在同一個模組內使用,不對外公開
E_POINTER, ///< 非法指標
E_INVALARG, ///< 非法引數
E_NOTIMPL = 5, ///< 功能未實現
E_OUTOFMEM, ///< 記憶體申請失敗/記憶體不足
E_BUFERROR, ///< 緩衝區錯誤(不足,錯亂)
E_PERM, ///< 許可權不足,訪問未授權的物件
E_TIMEOUT, ///< 超時
E_NOTINIT = 10, ///< 未初始化
E_INITFAIL, ///< 初始化失敗
E_ALREADY, ///< 已初始化,已經在執行
E_INPROGRESS, ///< 已在執行、進行狀態
E_EXIST, ///< 申請資源物件(如檔案或目錄)已存在
E_NOTEXIST, ///< 資源物件(如檔案或目錄)、命令、裝置等不存在
E_BUSY, ///< 裝置或資源忙(資源不可用)
E_FULL, ///< 裝置/資源已滿
E_EMPTY, ///< 物件/記憶體/內容為空
E_OPENFAIL, ///< 資源物件(如檔案或目錄、socket)開啟失敗
E_READFAIL, ///< 資源物件(如檔案或目錄、socket)讀取、接收失敗
E_WRITEFAIL, ///< 資源物件(如檔案或目錄、socket)寫入、傳送失敗
E_DELFAIL, ///< 資源物件(如檔案或目錄、socket)刪除、關閉失敗
E_CODECFAIL, ///< 加解密、編碼解密失敗
E_CRC_FAIL, ///< crc校驗錯誤
E_TOOMANY, ///< 訊息、緩衝區、內容太多
E_TOOSMALL, ///< 訊息、緩衝區、內容太少
E_NETNOTREACH, ///< 網路不可達(無路由,閘道器錯誤)
E_NETDOWN, ///< 網路不可用(斷網)
// more...
E_END, ///< 佔位,無實際作用
};
const char* get_errorcode(int ec);
#endif
實現檔案
my_errorcode.c檔案內容,注意,使用2種方法實現:
#include "my_errorcode.h"
#if 01
///////////// 推薦方式
static const char* faults[] = {
[E_OK] = "Success",
[E_FAIL] = "General Failed",
[E_INNER] = "Internal error",
[E_POINTER] = "Invalid Pointer",
[E_INVALARG] = "Invalid argument",
[E_NOTIMPL] = "Not implemented",
[E_OUTOFMEM] = "Out of memory",
[E_BUFERROR] = "Buffer error",
[E_PERM] = "Permission denied",
[E_TIMEOUT] = "Timed out",
[E_NOTINIT] = "Object not init",
[E_INITFAIL] = "Object init failed",
[E_ALREADY] = "Operation already in progress",
[E_INPROGRESS] = "Operation now in progress",
[E_EXIST] = "Object exist",
[E_NOTEXIST] = "Object not exist",
[E_BUSY] = "Device or resource busy",
[E_FULL] = "Device or resource full",
[E_EMPTY] = "Device or resource empty",
[E_OPENFAIL] = "Device or resource open failed",
[E_READFAIL] = "Device or resource read failed",
[E_WRITEFAIL] = "Device or resource write failed",
[E_DELFAIL] = "Device or resource delete failed",
[E_CODECFAIL] = "Encode or decode failed",
[E_CRC_FAIL] = "CRC failed",
[E_TOOMANY] = "Object too many",
[E_TOOSMALL] = "Object too small",
[E_NETNOTREACH] = "Network is unreachable",
[E_NETDOWN] = "Network is down",
// more...
};
const char* get_errorcode(int ec)
{
if (ec < 0) ec = -ec;
printf("ec: %d\n", ec);
if (ec < E_OK || ec >= E_END) return "Unknown error code";
return faults[ec];
}
////////////////////////// 另一種方式實現
#else
typedef struct {
int ec;
const char *str;
} errorstring;
static const errorstring faults[] = {
{E_OK, "Success"},
{E_FAIL, "General Failed"},
{E_INNER, "Internal error"},
{E_POINTER, "Invalid Pointer"},
{E_INVALARG, "Invalid argument"},
{E_NOTIMPL, "Not implemented"},
{E_OUTOFMEM, "Out of memory"},
{E_BUFERROR, "Buffer error"},
{E_PERM, "Permission denied"},
{E_TIMEOUT, "Timed out"},
{E_NOTINIT, "Object not init"},
{E_INITFAIL, "Object init failed"},
{E_ALREADY, "Operation already in progress"},
{E_INPROGRESS, "Operation now in progress"},
{E_EXIST, "Object exist"},
{E_NOTEXIST, "Object not exist"},
{E_BUSY, "Device or resource busy"},
{E_FULL, "Device or resource full"},
{E_EMPTY, "Device or resource empty"},
{E_OPENFAIL, "Device or resource open failed"},
{E_READFAIL, "Device or resource read failed"},
{E_WRITEFAIL, "Device or resource write failed"},
{E_DELFAIL, "Device or resource delete failed"},
{E_CODECFAIL, "Encode or decode failed"},
{E_CRC_FAIL, "CRC failed"},
{E_TOOMANY, "Object too many"},
{E_TOOSMALL, "Object too small"},
{E_NETNOTREACH, "Network is unreachable"},
{E_NETDOWN, "Network is down"},
};
const char* get_errorcode(int ec)
{
if (ec < 0) ec = -ec;
for (unsigned int i = 0; i < sizeof(faults)/sizeof(faults[0]); i++)
if (faults[i].ec == ec)
return faults[i].str;
return "Error code unknown";
}
#endif
測試程式碼
main.c檔案內容如下:
/**
列印結果:
Success
Error code unknown
-6(Out of memory)
9(Timed out)
*/
#include <stdlib.h>
#include <stdio.h>
#include "my_errorcode.h"
int foo()
{
return -E_OUTOFMEM;
}
int bar()
{
return E_TIMEOUT;
}
int main(void)
{
printf("%s\n", get_errorcode(0));
printf("%s\n", get_errorcode(-250));
int ret = foo();
printf("%d(%s)\n", ret, get_errorcode(ret));
ret = bar();
printf("%d(%s)\n", ret, get_errorcode(ret));
return 0;
}
設計理念
錯誤碼使用列舉型別定義,方便後續的新增,注意,列舉型別預設是累積1的。
另外,如果不想這樣設計,也可以使用:
#define E_OK 0 ///< 成功
這種巨集定義。
如果想看到具體的錯誤碼數值,則要在列舉後面手動新增數字。
錯誤碼0表示成功,其它值表示失敗,一般使用負數表示,本文所示程式碼,在實際返回時需要新增負號-
,如return -E_OUTOFMEM;
。當然,如果不要這麼麻煩,就直接返回,那麼其值就是正數。但是,為了相容,也為了列舉型別的定義,獲取錯誤碼資訊時需要將負數轉成正數。
至於獲取錯誤碼資訊get_errorcode
的實現,則使用了GNU C編譯器的擴充套件語法,從程式碼編寫看十分簡潔,不過,這個檔案必須使用c語法,即gcc編譯,g++編譯會失敗。
小結
通過上面的實現,可以得到一套基本滿足應用的錯誤碼模板。本文僅定義通用的錯誤碼,根據需求新增業務相關錯誤碼即可。
李遲 2018.9.1 深夜,與博導、蔡總、劉總夜宵回來