1. 程式人生 > >Linux系統C/C++通用錯誤碼實現模板

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 深夜,與博導、蔡總、劉總夜宵回來