根据之前question的答案,这里是针对我的嵌入式C应用程序的简单错误报告系统的设计。我很感激一些反馈。
我的应用程序的代码构成了一个整体程序以及几层低级库。在最低级别,库获取一个用于错误代码的字节,因此总共有255个错误。它们将以枚举编码,例如:
enum lib1ErrorCodes {
ERR_NO_ERROR,
ERR_NO_CONNECTION,
...
ERR_MISC
};
这是通过全局变量传递给链的:
unsigned char lib1ErrNo;
下一个更高级别包含它使用的库的错误代码:
enum lib2ErrorCodes {
ERR_NO_ERROR,
ERR_LIB1,
ERR_FILE_EXISTS,
...
ERR_MISC
}
检测到Lib1的错误,并在此级别的错误变量中标记:
unsigned char lib2ErrNo = ERR_LIB1;
在顶层,当有时间将所有这些报告给用户时,会检测到这些:
if (lib3ErrNo == ERR_LIB2)
if (lib2ErrNo == ERR_LIB1)
printf("Error %d: %s", lib1ErrNo, lib1ErrDesc);
我在这个方案中可以想到的唯一不利之处是需要在每个库中留出一些错误代码以指向其下的库,并且顶级程序需要将所有这些级别包含在其错误代码中
如果不是这样,这样做的恰当方法是什么?
我想:
答案 0 :(得分:4)
首先,除非你有8位架构,否则我会使用更大的变量来保存所有错误。
其次,我不会让系统复杂化。我只是对所有错误都有一个枚举,每当发生错误时,我都会调用一个向前报告它的错误处理程序。通常,当你得到一个错误时,你会得到很多错误,如果每个库只有一个错误代码,你就会冒失去其中一些错误的风险,可能是最重要的那个(第一个),除非你非常警惕不允许任何东西发生错误后发生其他事情。这也可能变得非常复杂。
答案 1 :(得分:3)
我认为对于不同的错误具有相同的值 - 在您的示例中,ERR_NO_CONNECTION
和ERR_LIB1
的值均为1 - 不明智。
我建议为每个库保留值范围,并将这些值明确地分配给错误代码。
enum lib1ErrorCodes {
ERR_NO_ERROR = 0,
ERR_NO_CONNECTION = 1,
...
ERR_MISC = ...
};
enum lib2ErrorCodes {
ERR_NO_ERROR = 0,
ERR_LIB1 = 101,
ERR_FILE_EXISTS = 102,
...
ERR_MISC = ...
}
答案 2 :(得分:3)
根据我的经验,您能够报告的有关错误的信息越多,您就越好。
我解决这些类型问题的一种方法是提出一个中央“状态管理器”,如果发现问题,所有软件包都可以与之交互。在最低限度,可以使用枚举指示问题以及另一个枚举/整数来调用状态管理器,以帮助您找到问题所在。 - 在某些系统(通常是16位+至少128K RAM的系统)上,我甚至知道存储文本字符串来描述问题。
我还假设您可以建立可用于提取此数据的后门界面和/或专有界面。
答案 3 :(得分:2)
我倾向于尝试在每一层翻译我的错误,以便在这种情况下有意义。例如,假设您有一个主程序调用通信堆栈,而后者又调用串行驱动程序。串行驱动程序可能有一些错误,如:
typedef enum
{
SER_NO_ERR,
SER_TX_ERR,
SER_BAD_ARGS,
...
} SER_ERR;
通信堆栈可能包含:
typedef enum
{
COMM_OK,
COMM_TIMEOUT,
COMM_TX_ERR,
...
} COMM_ERR;
在你的comm堆栈中你可能会看到一些代码:
// lets assume you are using the global variables serErr and commErr
// to store error codes for their respective libraries.
serial_transmit(someDataPtr, someSize);
switch(serErr)
{
case SER_NO_ERR:
commErr = COMM_OK;
break;
case SER_BAD_ARGS:
case SER_TX_ERR:
commErr = COMM_TX_ERR;
break;
...
}
我意识到可能会引入相当多的代码,但我喜欢它维护层之间的抽象。您还可以定义一些函数来处理翻译,例如:
void comm_handle_serial_error(SER_ERR err)
{
//copy in the switch statement from above
}
但是错误在不同的环境中可能意味着不同的东西,因此我建议您在设计系统时牢记这一点。
现在处于最高级别,您需要做的就是担心在调用通信功能时处理COMM_ERR
定义的所有错误。请注意,所有这些示例都有点人为,但希望您能得到这个想法。
答案 4 :(得分:0)
我更倾向于将错误代码与标志位一起存储,以指示它来自哪个库。
答案 5 :(得分:0)
取决于您的程序空间/内存/速度限制,C ++的框架就像C. This is an excellent article中的异常处理一样,我过去曾用它来解决动态非常低端的平台上的问题内存分配不可用。如果使用得当,这可以使您摆脱与在调用树上处理错误代码相关的问题。
另一方面,我在某些情况下也使用了类似于其他提供答案的架构。最常见的是集中式错误状态模块,它将接受错误代码并调用适当的处理函数。
答案 6 :(得分:0)
使用__FILE __,__ FUNCTION__和__LINE__宏来识别位置。因为识别了模块,所以不需要唯一的错误代码。请考虑以下事项:
#define ERROR_PRINTF( code, fmt, ... ) error_printf( "\n%s::%s(%u) Error %u : " fmt "\n", __FILE__, __FUNCTION__, __LINE__, code, __VA_ARGS__ )
int error_printf( const char* fmt, ... )
{
va_list args ;
va_start (args, fmt) ;
vprintf (fmt, args) ;
va_end (args) ;
}
然后说main.c函数中的main.c,第20行出现以下行:
ERROR_PRINTF( ERR_NO_CONNECTION, "Connection failed on port %d", port ) ;
将出现以下文字:
main.c::main(20) : Error 1 : Connection failed on port 2
就个人而言,由于这允许表达错误消息和精确位置,我根本不会打扰错误代码业务。每个错误都由其位置唯一标识,这更有用。