C代码中的错误处理


152

在C库中以一致的方式处理错误时,您认为“最佳实践”是什么?

我一直在想两种方法:

始终返回错误代码。一个典型的函数如下所示:

MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize);

始终提供错误指针方法:

int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError);

使用第一种方法时,可以编写如下代码,将错误处理检查直接放在函数调用上:

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

看起来比这里的错误处理代码更好。

MYAPIError error;
int size;
size = getObjectSize(h, &error);
if(error != MYAPI_SUCCESS) {
    // Error handling
}

但是,我认为使用返回值返回数据可以使代码更具可读性。很明显,第二个示例中将一些内容写入了size变量。

您是否对我为什么应该偏爱这些方法中的任何一个或可能将它们混合或使用其他方法有任何想法?我不喜欢全局错误状态,因为它会使库的多线程使用更加痛苦。

编辑:关于此的C ++特定想法也很有趣,只要它们不涉及异常,因为这对我而言目前不是一个选择...


我只学习C大约两周,但是我得到的感觉是OUT参数是大多数函数的实际返回值,因为它避免了返回按值返回结构的开销并减轻了负担。由于大多数变量位于堆栈中,因此需要取消分配内存。因此,由于我没有在函数的实际值中使用“返回”,因此大多数时候我可以自由地使用它来进行错误处理。
乔尔·罗伯茨

Answers:


74

我喜欢将错误作为返回值方式。如果您正在设计api,并且希望尽可能轻松地使用您的库,请考虑以下补充:

  • 将所有可能的错误状态存储在一个typedef'ed枚举中,并在您的lib中使用它。不要只是返回整数,甚至更糟,请将整数或不同的枚举与返回代码混合使用。

  • 提供将错误转换为人类可读的功能。可以很简单。只是错误枚举,const char *输出。

  • 我知道这个想法使多线程的使用有点困难,但是如果应用程序程序员可以设置全局错误回调,那就太好了。这样,他们将能够在Bug搜索会话期间将断点放入回调中。

希望能帮助到你。


5
为什么这么说:“这个想法使多线程的使用有点困难。” 多线程使哪一部分变得困难?你能举个简单的例子吗?
SayeedHussain

1
@crypticcoder简单地说:可以在任何线程上下文中调用全局错误回调。如果仅打印出错误,则不会遇到任何问题。如果尝试纠正问题,则必须找出哪个调用线程导致了错误,这使事情变得困难。
Nils Pipenbrinck

9
如果您想传达错误的更多详细信息怎么办?例如,您遇到解析器错误,并希望提供语法错误的行号和列以及一种很好地打印所有错误的方法。
panzi

1
@panzi,那么您显然需要返回一个结构(如果该结构确实很大,则使用out指针),并具有一个将结构格式化为字符串的函数。
温格·森顿

我在这里用代码演示了您的前2个项目符号:stackoverflow.com/questions/385975/error-handling-in-c-code / ...
Gabriel Staples

92

我已经使用了这两种方法,并且它们对我都很好。无论我使用哪一个,我都始终尝试应用这一原则:

如果唯一可能的错误是程序员错误,请不要返回错误代码,请在函数内部使用断言。

验证输入的断言可以清楚地传达功能所期望的内容,而过多的错误检查可能会使程序逻辑难以理解。决定如何处理所有各种错误情况,确实会使设计复杂化。如果您可以坚持认为程序员永远不要传递一个空指针,为什么还要弄清楚functionX应该如何处理空指针呢?


1
在C中有一个断言示例?(我对C很绿)
thomthom

它通常很简单,assert(X)其中X是您想要为真的任何有效C语句。参见stackoverflow.com/q/1571340/10396
AShelly

14
gh,绝对不要在库代码中使用断言!另外,不要像其他代码一样在代码中混用各种错误处理方式
mirabilos 2014年

10
我当然同意不混合样式。我对您对断言的推理感到好奇。如果我的函数文档说“参数X不能为NULL”或“ Y必须是该枚举的成员”,那么assert(X!=NULL);or或assert(Y<enumtype_MAX);怎么了?请参阅程序员的答案及其所链接的问题,以详细了解为什么我认为这是正确的方法。
AShelly

8
@AShelly的问题是它们通常不在发行版本中。
Calmarius

29

CMU的CERT提供了一组不错的幻灯片,其中包含有关何时使用每种常见的C(和C ++)错误处理技术的建议。最好的幻灯片之一是以下决策树:

错误处理决策树

我会亲自更改有关此购物车的两件事。

首先,我要澄清的是,有时对象应使用返回值来指示错误。如果函数仅从对象中提取数据但不对对象进行突变,则对象本身的完整性不会受到威胁,因此使用返回值指示错误更为合适。

其次,在C ++中使用异常并不总是合适的。异常之所以很好,是因为它们可以减少用于错误处理的源代码量,它们通常不影响函数签名,并且可以灵活地将哪些数据传递给调用堆栈。另一方面,出于以下几个原因,异常可能不是正确的选择:

  1. C ++异常具有非常特殊的语义。如果您不想要这些语义,那么C ++异常是一个不好的选择。抛出异常后必须立即对其进行处理,并且设计偏爱需要将调用堆栈展开几个级别的错误的情况。

  2. 引发异常的C ++函数以后不能包装为不引发异常,至少不会不付出异常的全部费用。返回错误代码的函数可以包装为引发C ++异常,从而使其更加灵活。C ++ new通过提供非抛出的变体来实现这一目标。

  3. C ++异常相对昂贵,但是对于明智地使用异常的程序,这种缺点大多被夸大了。程序根本不应该在关注性能的代码路径上抛出异常。程序报告错误并退出的速度并不重要。

  4. 有时C ++异常不可用。它们实际上在C ++实现中不可用,或者一个人的代码指南禁止使用它们。


由于原始问题是关于多线程上下文的,所以我认为本地错误指示符技术(在SirDarius答案中进行了描述)在原始答案中并未得到充分重视。它是线程安全的,不会强制调用者立即处理该错误,并且可以捆绑描述该错误的任意数据。缺点是它必须由一个对象(或者我想以某种方式与外部关联)持有,并且可以说比返回代码更容易忽略。


5
您可能会注意到,Google的C ++编码标准仍然说“ 我们不使用C ++异常”。
乔纳森·莱夫勒

19

每当创建库时,我都会使用第一种方法。使用类型定义的枚举作为返回码有几个优点。

  • 如果该函数返回更复杂的输出(例如数组),并且其长度不需创建任意结构即可返回。

    rc = func(..., int **return_array, size_t *array_length);
  • 它允许简单,标准化的错误处理。

    if ((rc = func(...)) != API_SUCCESS) {
       /* Error Handling */
    }
  • 它允许在库函数中进行简单的错误处理。

    /* Check for valid arguments */
    if (NULL == return_array || NULL == array_length)
        return API_INVALID_ARGS;
  • 使用类型定义的枚举还允许枚举名称在调试器中可见。这使得调试更加容易,而无需经常查阅头文件。具有将此枚举转换为字符串的函数也很有帮助。

无论使用哪种方法,最重要的问题是保持一致。这适用于函数和参数命名,参数排序和错误处理。


9

使用setjmp

http://en.wikipedia.org/wiki/Setjmp.h

http://aszt.inf.elte.hu/~gsd/halado_cpp/ch02s03.html

http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

#include <setjmp.h>
#include <stdio.h>

jmp_buf x;

void f()
{
    longjmp(x,5); // throw 5;
}

int main()
{
    // output of this program is 5.

    int i = 0;

    if ( (i = setjmp(x)) == 0 )// try{
    {
        f();
    } // } --> end of try{
    else // catch(i){
    {
        switch( i )
        {
        case  1:
        case  2:
        default: fprintf( stdout, "error code = %d\n", i); break;
        }
    } // } --> end of catch(i){
    return 0;
}

#include <stdio.h>
#include <setjmp.h>

#define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
#define CATCH } else {
#define ETRY } }while(0)
#define THROW longjmp(ex_buf__, 1)

int
main(int argc, char** argv)
{
   TRY
   {
      printf("In Try Statement\n");
      THROW;
      printf("I do not appear\n");
   }
   CATCH
   {
      printf("Got Exception!\n");
   }
   ETRY;

   return 0;
}

2
第二段代码基于答案顶部引用的Francesco Nidito页面上的较早版本的代码。ETRY自编写此答案以来,已对代码进行了修订。
乔纳森·莱夫勒

2
Setjmp是一种可怕的错误处理策略。如果在setjmp和longjmp调用之间分配任何资源,这会导致代价高昂,容易出错(带有非易失性已更改的局部变量,并且不保留其更改的值和所有变量),并且会泄漏资源。在收回sigjmp / longjmp的费用之前,您应该能够执行30次返回和int-val检查。大多数调用栈都没有那么深入,特别是如果您不沉迷于递归(如果这样做的话,除了返回和检查的成本之外,还存在性能问题)。
PSkocik

1
如果先分配内存然后抛出,内存将永远泄漏。这也是setjmp很昂贵的,即使没有抛出错误,它也会消耗大量的CPU时间和堆栈空间。在Windows上使用gcc时,您可以在C ++的不同异常处理方法之间进行选择,其中一种是基于这种方法的setjmp,这会使您的代码在实践中的速度降低多达30%。
梅基

7

我个人更喜欢前一种方法(返回错误指示符)。

必要时,返回结果应仅指示发生了错误,并使用另一个函数来找出确切的错误。

在您的getSize()示例中,我认为大小必须始终为零或正数,因此返回负数结果可能表示错误,就像UNIX系统调用一样。

我想不出我曾经使用过的任何一种库,后者都将后一种方法与作为指针传入的错误对象一起使用。 stdio,等等都带有返回值。


1
作为记录,我见过的使用后一种方法的库是Maya编程API。它是一个c ++库,而不是C。它在处理错误方面的方式非常不一致,有时错误会作为返回值传递,而其他时候会将结果作为引用传递。
Laserallan

1
不要忘记strtod,好吧,最后一个参数不仅用于指示错误,而且它也可以指示错误。
quinmars

7

在编写程序时,在初始化期间,我通常会剥离线程以进行错误处理,并为错误初始化一个特殊的结构,包括锁。然后,当我检测到错误时,通过返回值,将异常中的信息输入结构,并将SIGIO发送到异常处理线程,然后查看是否无法继续执行。如果不能,则将SIGURG发送到异常线程,该线程将正常停止程序。


7

返回错误代码是C中错误处理的常用方法。

但是最近,我们也尝试了传出错误指针方法。

与返回值方法相比,它具有一些优点:

  • 您可以将返回值用于更有意义的目的。

  • 必须写出该错误参数会提醒您处理错误或传播错误。(您永远不会忘记检查的返回值fclose,不是吗?)

  • 如果使用错误指针,则可以在调用函数时将其向下传递。如果有任何功能对其进行设置,则该值不会丢失。

  • 通过在错误变量上设置数据断点,您可以捕获错误首先在何处发生。通过设置条件断点,您也可以捕获特定的错误。

  • 它使自动化检查是否处理所有错误变得更加容易。代码约定可能会强制您将错误指针称为,err并且它必须是最后一个参数。因此,脚本可以匹配字符串,err);然后检查其后是否有if (*err。实际上在实践中,我们制作了一个名为CER(check err return)和CEG(check err goto)的宏。因此,当我们只想返回错误时,您不必总是将其键入,并且可以减少视觉混乱。

但是,并非我们代码中的所有函数都具有该传出参数。这个传出参数的东西用于通常会引发异常的情况。


6

过去,我做了很多C编程。而且我真的很欣赏错误代码的返回值。但是有几个可能的陷阱:

  • 错误号重复,可以使用全局errors.h文件解决。
  • 忘记检查错误代码,应该通过线索提示和较长的调试时间来解决。但是最后您将学习(或者您将知道其他人将进行调试)。

2
第二个问题可以通过适当的编译器警告级别,适当的代码检查机制以及静态代码分析器工具来解决。
伊利亚

1
您还可以按照以下原则进行工作:如果调用了API函数并且未检查返回值,则存在错误。
乔纳森·勒夫勒

6

UNIX方法与您的第二个建议最相似。返回结果或单个“出错”值。例如,open将在成功时返回文件描述符,或在失败时返回-1。发生故障时,它还会设置errno一个外部全局整数,以指示发生了哪些故障。

就其价值而言,可可也一直采用类似的方法。许多方法返回BOOL并采用一个NSError **参数,以便在失败时设置错误并返回NO。然后错误处理如下所示:

NSError *error = nil;
if ([myThing doThingError: &error] == NO)
{
  // error handling
}

这是您的两个选项之间的某处:-)。



5

我认为这是一种有趣的方法,需要一定的纪律。

假定句柄类型变量是在其上操作所有API函数的实例。

这个想法是句柄后面的结构将先前的错误存储为具有必要数据(代码,消息...)的结构,并且向用户提供了一个函数,该函数返回指向此错误对象的指针。每个操作都会更新指向的对象,因此用户甚至无需调用函数即可检查其状态。与errno模式相反,错误代码不是全局的,只要正确使用每个句柄,该方法就使线程安全。

例:

MyHandle * h = MyApiCreateHandle();

/* first call checks for pointer nullity, since we cannot retrieve error code
   on a NULL pointer */
if (h == NULL)
     return 0; 

/* from here h is a valid handle */

/* get a pointer to the error struct that will be updated with each call */
MyApiError * err = MyApiGetError(h);


MyApiFileDescriptor * fd = MyApiOpenFile("/path/to/file.ext");

/* we want to know what can go wrong */
if (err->code != MyApi_ERROR_OK) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}

MyApiRecord record;

/* here the API could refuse to execute the operation if the previous one
   yielded an error, and eventually close the file descriptor itself if
   the error is not recoverable */
MyApiReadFileRecord(h, &record, sizeof(record));

/* we want to know what can go wrong, here using a macro checking for failure */
if (MyApi_FAILED(err)) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}

4

第一种方法是更好的恕我直言:

  • 这样编写函数更容易。当您在函数中间发现错误时,您只需返回一个错误值。在第二种方法中,您需要将错误值分配给其中一个参数,然后返回某事.....但是您将返回什么-您没有正确的值,也没有返回错误值。
  • 它更受欢迎,因此更易于理解,维护

4

我绝对更喜欢第一个解决方案:

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

我会稍作修改,以:

int size;
MYAPIError rc;

rc = getObjectSize(h, &size)
if ( rc != MYAPI_SUCCESS) {
  // Error handling
}

另外,即使当前函数范围允许您这样做,我也绝不会将合法的返回值与错误混为一谈,也永远不会知道将来函数实现的方式。

如果我们已经在讨论错误处理,我建议将其goto Error;作为错误处理代码,除非undo可以调用某些函数来正确处理错误处理。


3

您可以做的而不是返回错误,从而禁止您使用函数返回数据的方法是,使用包装器作为返回类型:

typedef struct {
    enum {SUCCESS, ERROR} status;
    union {
        int errCode;
        MyType value;
    } ret;
} MyTypeWrapper;

然后,在被调用的函数中:

MyTypeWrapper MYAPIFunction(MYAPIHandle h) {
    MyTypeWrapper wrapper;
    // [...]
    // If there is an error somewhere:
    wrapper.status = ERROR;
    wrapper.ret.errCode = MY_ERROR_CODE;

    // Everything went well:
    wrapper.status = SUCCESS;
    wrapper.ret.value = myProcessedData;
    return wrapper;
} 

请注意,使用以下方法,包装程序将具有MyType的大小加上一个字节(在大多数编译器上),这是非常有利的;并且您不必在调用函数时(returnedSizereturnedError在您介绍的两种方法中)将另一个参数压入堆栈


3

这是一个简单的程序,用于展示Nils Pipenbrinck在此处回答的前2个项目符号。

他的前2个子弹是:

  • 将所有可能的错误状态存储在一个typedef枚举中,并在您的lib中使用它。不要只是返回整数,甚至更糟糕的是,将整数或不同的枚举与返回代码混合使用。

  • 提供将错误转换为人类可读的功能。可以很简单。只是错误枚举,const char *输出。

假设您已经编写了一个名为的模块mymodule首先,在mymodule.h中,定义基于枚举的错误代码,并编写一些与这些代码相对应的错误字符串。在这里,我使用的是一个C字符串数组(char *),只有当您的第一个基于枚举的错误代码的值为0,并且此后您不对数字进行操作时,它才能很好地工作。如果您确实使用带有空格或其他起始值的错误代码号,则只需从使用映射的C字符串数组(如下所述)更改为使用使用switch语句或if / else if语句的函数即可。从枚举错误代码映射到可打印的C字符串(我没有演示)。这是你的选择。

mymodule.h

/// @brief Error codes for library "mymodule"
typedef enum mymodule_error_e
{
    /// No error
    MYMODULE_ERROR_OK = 0,
    
    /// Invalid arguments (ex: NULL pointer where a valid pointer is required)
    MYMODULE_ERROR_INVARG,

    /// Out of memory (RAM)
    MYMODULE_ERROR_NOMEM,

    /// Make up your error codes as you see fit
    MYMODULE_ERROR_MYERROR, 

    // etc etc
    
    /// Total # of errors in this list (NOT AN ACTUAL ERROR CODE);
    /// NOTE: that for this to work, it assumes your first error code is value 0 and you let it naturally 
    /// increment from there, as is done above, without explicitly altering any error values above
    MYMODULE_ERROR_COUNT,
} mymodule_error_t;

// Array of strings to map enum error types to printable strings
// - see important NOTE above!
const char* const MYMODULE_ERROR_STRS[] = 
{
    "MYMODULE_ERROR_OK",
    "MYMODULE_ERROR_INVARG",
    "MYMODULE_ERROR_NOMEM",
    "MYMODULE_ERROR_MYERROR",
};

// To get a printable error string
const char* mymodule_error_str(mymodule_error_t err);

// Other functions in mymodule
mymodule_error_t mymodule_func1(void);
mymodule_error_t mymodule_func2(void);
mymodule_error_t mymodule_func3(void);

mymodule.c包含我的映射函数,用于从枚举错误代码映射到可打印的C字符串:

mymodule.c

#include <stdio.h>

/// @brief      Function to get a printable string from an enum error type
/// @param[in]  err     a valid error code for this module
/// @return     A printable C string corresponding to the error code input above, or NULL if an invalid error code
///             was passed in
const char* mymodule_error_str(mymodule_error_t err)
{
    const char* err_str = NULL;

    // Ensure error codes are within the valid array index range
    if (err >= MYMODULE_ERROR_COUNT)
    {
        goto done;
    }

    err_str = MYMODULE_ERROR_STRS[err];

done:
    return err_str;
}

// Let's just make some empty dummy functions to return some errors; fill these in as appropriate for your 
// library module

mymodule_error_t mymodule_func1(void)
{
    return MYMODULE_ERROR_OK;
}

mymodule_error_t mymodule_func2(void)
{
    return MYMODULE_ERROR_INVARG;
}

mymodule_error_t mymodule_func3(void)
{
    return MYMODULE_ERROR_MYERROR;
}

main.c包含一个测试程序,以演示如何调用某些函数并从中打印一些错误代码:

main.c

#include <stdio.h>

int main()
{
    printf("Demonstration of enum-based error codes in C (or C++)\n");

    printf("err code from mymodule_func1() = %s\n", mymodule_error_str(mymodule_func1()));
    printf("err code from mymodule_func2() = %s\n", mymodule_error_str(mymodule_func2()));
    printf("err code from mymodule_func3() = %s\n", mymodule_error_str(mymodule_func3()));

    return 0;
}

输出:

演示
来自mymodule_func1()的C(或C ++)错误代码中的基于枚举的错误代码=来自mymodule_func2()的MYMODULE_ERROR_OK
错误代码来自mymodule_func3()的MYr
错误代码= MYMODULE_ERROR_MYERROR

参考文献:

您可以在以下位置自己运行此代码:https : //onlinegdb.com/ByEbKLupS


2

除了已经说过的话,在返回错误代码之前,请在返回错误时触发断言或类似的诊断,因为这将使查找变得更加容易。我这样做的方法是有一个自定义的断言,该断言在发布时仍会被编译,但仅在软件处于诊断模式时才被触发,并具有静默向日志文件报告或在屏幕上暂停的选项。

我本人将错误代码作为负整数返回,其中no_error为零,但这确实使您可能遇到以下错误

if (MyFunc())
 DoSomething();

一种替代方法是使故障始终返回零,并使用LastError()函数提供实际错误的详细信息。


2

我多次遇到这个问答,并想提出一个更全面的答案。我想思考的最佳方式是如何错误返回主调,和什么你回来。

怎么样

有3种方法可以从函数返回信息:

  1. 返回值
  2. 争论不休
  3. 带外,包括非本地goto(setjmp / longjmp),文件或全局范围的变量,文件系统等。

返回值

您只能将value返回为单个对象,但是,它可以是任意复杂的对象。这是一个错误返回函数的示例:

  enum error hold_my_beer();

返回值的优点之一是它允许链接调用以减少介入式错误处理:

  !hold_my_beer() &&
  !hold_my_cigarette() &&
  !hold_my_pants() ||
  abort();

这不仅涉及可读性,而且还可以允许以统一的方式处理此类函数指针的数组。

争论不休

您可以通过一个参数通过多个对象返回更多信息,但是最佳实践的确建议将参数总数保持在较低水平(例如,<= 4):

void look_ma(enum error *e, char *what_broke);

enum error e;
look_ma(e);
if(e == FURNITURE) {
  reorder(what_broke);
} else if(e == SELF) {
  tell_doctor(what_broke);
}

带外

使用setjmp()可以定义一个位置以及如何处理int值,然后通过longjmp()将控制权转移到该位置。请参阅C中setjmp和longjmp的实际用法

什么

  1. 指示符
  2. 目的
  3. 打回来

指示符

错误指示符仅告诉您存在问题,而与上述问题的性质无关:

struct foo *f = foo_init();
if(!f) {
  /// handle the absence of foo
}

这是函数传达错误状态的最无力的方法,但是,如果调用者仍然无法以渐进的方式对错误进行响应,则此方法非常理想。

错误代码会告诉调用者问题的性质,并且可能允许做出适当的响应(来自上述内容)。它可以是返回值,也可以是错误参数上方的look_ma()示例。

目的

使用错误对象,可以将任意复杂的问题告知调用者。例如,错误代码和合适的人类可读消息。它还可以通知调用方有多处错误,或者在处理集合时每个项目都出错:

struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
   if(reason[i] == NOT_FOUND) find(friends[i]);
}

当然,除了预先分配错误数组外,您还可以根据需要动态(重新)分配它。

打回来

回调是处理错误的最强大方法,因为您可以告诉函数在出现问题时希望看到什么行为。可以将回调参数添加到每个函数,或者仅对于每个结构实例都需要自定义ui,如下所示:

 struct foo {
    ...
    void (error_handler)(char *);
 };

 void default_error_handler(char *message) { 
    assert(f);
    printf("%s", message);
 }

 void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
    assert(f);
    f->error_handler = eh;
 }

 struct foo *foo_init() {
    struct foo *f = malloc(sizeof(struct foo));
    foo_set_error_handler(f, default_error_handler);
    return f;
 }


 struct foo *f = foo_init();
 foo_something();

回调的一个有趣的好处是,可以多次调用它,或者在没有错误的情况下完全调用一次,而在错误情况下,快乐路径上没有开销。

但是,存在控制反转。调用代码不知道是否调用了回调。这样,也可以使用指示器。


1

编辑:如果您只需要访问上一个错误,并且您不在多线程环境中工作。

您只能返回true / false(如果在C语言中工作并且不支持bool变量,则可以返回某种#define),并具有一个全局错误缓冲区,该缓冲区将保存最后一个错误:

int getObjectSize(MYAPIHandle h, int* returnedSize);
MYAPI_ERROR LastError;
MYAPI_ERROR* getLastError() {return LastError;};
#define FUNC_SUCCESS 1
#define FUNC_FAIL 0

if(getObjectSize(h, &size) != FUNC_SUCCESS ) {
    MYAPI_ERROR* error = getLastError();
    // error handling
}

的确可以,但是它不是C语言,可能不是由OS提供的。例如,如果您在实时操作系统上工作,则可能没有C语言。
伊利亚2012年

1

第二种方法允许编译器生成更优化的代码,因为当将变量的地址传递给函数时,在后续调用其他函数期间,编译器无法将其值保留在寄存器中。完成代码通常在调用之后仅使用一次,而从调用返回的“真实”数据可能会更频繁地使用


1

我更喜欢使用以下技术在C中进行错误处理:

struct lnode *insert(char *data, int len, struct lnode *list) {
    struct lnode *p, *q;
    uint8_t good;
    struct {
            uint8_t alloc_node : 1;
            uint8_t alloc_str : 1;
    } cleanup = { 0, 0 };

   // allocate node.
    p = (struct lnode *)malloc(sizeof(struct lnode));
    good = cleanup.alloc_node = (p != NULL);

   // good? then allocate str
    if (good) {
            p->str = (char *)malloc(sizeof(char)*len);
            good = cleanup.alloc_str = (p->str != NULL);
    }

   // good? copy data
    if(good) {
            memcpy ( p->str, data, len );
    }

   // still good? insert in list
    if(good) {
            if(NULL == list) {
                    p->next = NULL;
                    list = p;
            } else {
                    q = list;
                    while(q->next != NULL && good) {
                            // duplicate found--not good
                            good = (strcmp(q->str,p->str) != 0);
                            q = q->next;
                    }
                    if (good) {
                            p->next = q->next;
                            q->next = p;
                    }
            }
    }

   // not-good? cleanup.
    if(!good) {
            if(cleanup.alloc_str)   free(p->str);
            if(cleanup.alloc_node)  free(p);
    }

   // good? return list or else return NULL
    return (good ? list : NULL);
}

资料来源:http//blog.staila.com/?p = 114


1
好技术。我发现使用goto“”而不是“重复if” 更加整洁。参考文献:
Ant_222 '18

0

除了其他出色的答案,我建议您尝试将错误标志和错误代码分开,以便在每次调用时节省一行,即:

if( !doit(a, b, c, &errcode) )
{   (* handle *)
    (* thine  *)
    (* error  *)
}

当您进行大量错误检查时,这种小的简化确实很有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.