【问题标题】:returning result or fault to the caller of the function将结果或错误返回给函数的调用者
【发布时间】:2018-01-30 08:27:50
【问题描述】:

在 C 中错误处理的所有示例和 SO 上的几个答案中,所有程序都只是以状态码退出。我不是在寻找这些。

错误是输入和输出的一部分,例程可以返回结果或错误。我希望函数返回正确的错误详细信息,而不仅仅是异常退出或成功时的结果。

以下不是有效的,但我正在寻找类似的东西,以指示函数返回的结果或错误。

struct Error {
    int code;
    char *message;
};

int div(int x, int y) {
    if( y == 0){
        struct Error *error = {0, "Division by zero!"};
        return error;
    } else {
        return x / y;
    }
}

int main() {
    printf("%d", div(4, 2));
    printf("%d", div(4, 0));
}

【问题讨论】:

  • 您是否在寻找例外情况?在 C 中没有这样的东西。但在 C++ 中有
  • 顺便说一句,上面的程序应该如何表现?
  • 任何可以帮助避免程序崩溃和函数优雅返回错误的方法。
  • 也许一个想法是创建一个包含结果和错误字段的结构,调用者在返回时检查它们。
  • 当心返回指向在函数的本地堆栈上创建的结构的指针!它可能实际上似乎在某些编译器上“工作”,但指针将无效。如果你返回一个指针,它应该指向调用者栈上的某个东西,一个全局的,或者堆上的某个东西。

标签: c error-handling


【解决方案1】:

struct Error *error = -> struct Error error =,然后将返回类型设为struct Error

但是由于你有一个结构,按值返回通常不是一个好主意。此外,数学函数的返回值很可能是数学结果。因此,比返回错误更好的方法是使用可选参数:

#include <stdio.h>
#include <stdlib.h>

typedef enum
{
  ERR_NONE,
  ERR_DIV_BY_ZERO
} err_code_t;

typedef struct 
{
    err_code_t code;
    const char *message;
} err_t;

int division (int x, int y, err_t* err) 
{
  int result;

  if( y == 0)
  {
    if(err != NULL)
    {
      err->code = ERR_DIV_BY_ZERO;
      err->message = "Division by zero";
    }
    result = 0;
  } 
  else 
  {
    result = x / y;
  }

  return result;
}

void error_handler (const err_t* err)
{
  fprintf(stderr, "Error %d: %s", err->code, err->message);
  exit(EXIT_FAILURE);
}

int main() 
{
  err_t err;
  int result;

  result = division(4, 2, NULL); // NULL meaning don't give any error information
  printf("%d\n", result);

  result = division(4, 0, &err);
  if(err.code != ERR_NONE)
  {
    error_handler(&err);
  }
  printf("%d\n", result);
}

【讨论】:

  • 我喜欢这个实现,我应该注意多线程环境中的任何检查
  • @App2015 并非如此,除了 stdio.h 的实现之外,代码本身是可重入的。如果您希望在线程之间传递错误,那就另当别论了。
  • 不,我不会在线程之间传递错误。该代码将在多线程环境(如 Web 服务器)上运行,以处理多个传入的并发 http 请求。只需确认每个用户是否会收到自己的错误或结果,就像用户 A 不会得到用户 B 的结果或错误,反之亦然。我是服务器上的 C 新手,所以只想安全。
  • 我不喜欢 'division(x, y, err)' 在传递 0 的除数时返回 0... 显然没有办法解决这个问题,除非更改返回类型。
  • @App2015 你必须为每个线程声明一个“err_t”实例。
【解决方案2】:

Zed Shaw 的“以艰难的方式学习 C”尽管存在所有缺陷,但其中有一个关于“C 错误处理问题”的精彩部分,您可能会觉得有趣。

我认为解决此问题的一种方法是使用调试宏来包装 C 提供的全局“errno”值。

下面的 'dbg.h' 代码取自 Zed Shaw 的书:

dbg.h:

#ifndef __dbg_h__
#define __dbg_h__

#include <stdio.h>
#include <errno.h>
#include <string.h>

#ifdef NDEBUG
#define debug(M, ...)
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n",\
        __FILE__, __LINE__, ##__VA_ARGS__)
#endif

#define clean_errno() (errno == 0 ? "None" : strerror(errno))

#define log_err(M, ...) fprintf(stderr,\
        "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__,\
        clean_errno(), ##__VA_ARGS__)

#define log_warn(M, ...) fprintf(stderr,\
        "[WARN] (%s:%d: errno: %s) " M "\n",\
        __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n",\
        __FILE__, __LINE__, ##__VA_ARGS__)

#define check(A, M, ...) if(!(A)) {\
    log_err(M, ##__VA_ARGS__); errno=0; goto error; }

#define sentinel(M, ...)  { log_err(M, ##__VA_ARGS__);\
    errno=0; goto error; }

#define check_mem(A) check((A), "Out of memory.")

#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__);\
    errno=0; goto error; }

#endif

在您想要检查的代码中,您可以执行以下操作:

#include <stdio.h>
#include "dbg.h"

int div(int x, int y) {
    check ( y != 0, "Division by zero!");
    return x / y;
error:
    log_err("Div by zero error.");
    return 0;
}

int main() {
    printf("%d", div(4, 2));
    printf("%d", div(4, 0));
}

这样,您的错误就会被“dbg.h”中的代码捕获和处理。 现在这有一个明显的问题,即当调用 div(x, 0) 时:div 返回 0。问题是 div() 必须返回一个 int,并且所有 int 都可以由 div(x, y) 的某种有效组合产生对于整数 x 和 y。

一种解决方案是将 div 包装在另一个函数中:

#include <stdio.h>
#include <stdlib.h>
#include "dbg.h"

char *div_interface(int x, int y) {
    check ( y != 0, "Division by zero!");
    char *buffer = malloc(100);
    sprintf(buffer, "%d", (x / y));
    return buffer;
error:
    log_err("Div by zero error.");
    char *err_ret= strdup("Div by zero error.");
    return err_ret;
}

int main() {
    char *div_1 = div_interface(4, 2);
    printf("%s\n", div_1);
    free(div_1);
    char *div_2 = div_interface(4, 0);
    printf("%s\n", div_2);
    free(div_2);
}

虽然这也不是特别优雅!然后调用者必须进行从 char* 到 int 的转换...

【讨论】:

  • 我不同意“on error goto”是很好的错误处理。这段代码对我来说只不过是混淆。分配和取消分配应该发生在同一个地方,否则设计不好。错误时最好使用包装器函数和return 从实际函数到包装器,然后将错误处理留给包装器。示例:void the_wrapper(args) { char* ptr = malloc(n); err_t result = function(ptr, args); free(ptr);/* cleanup resources here*/ if(result == bad) { handle_error(); } }。比 goto spaghetti à la 1980s BASIC 干净得多。
  • @Lundin 是的,我明白你在说什么,但是一旦你有了像 'dbg.h' 这样的东西并知道它是如何工作的,它会为你节省大量的写作,因为整个 if(result ==坏){句柄错误(); } 函数不必每次都编写。
  • “节省击键”是一个糟糕的设计原理。编程就是写文本。如果您希望避免代码重复,可以通过其他方式解决。根据我的经验,明确地写出每个函数的错误检查是一件非常好的事情,因为它表明程序员已经 1)首先考虑了错误处理,以及 2)他们考虑了哪些错误。如果你把它隐藏在抽象层后面,那么你就有隐藏错误的风险。
  • 如果您发现自己一遍又一遍地输入完全相同的代码,那么将其包含在某个宏中还不够吗?宏 'check'、'check_mem' 等非常通用,每次执行 malloc 时只需输入相同的代码即可。
  • 用奇怪的宏弄乱你的代码通常会导致比代码重复更多的问题。我会选择最易读的版本。对于普通 C 程序员来说,something=func(); if (something == ERROR) { handle_error(something); } 很可能比 MYSTERIOUS_MACRO(something); .... label_out_of_the_blue: ... 更具可读性。
【解决方案3】:

函数返回 int 时不能返回结构。但是你可以返回不同的整数,然后像这样处理它。

typedef struct Error {
    int code;
    char message[512];
};

int div(int x, int y, Error *err) {
    if( y == 0) {
        err->code = 0;
        strcpy(err->message, "Division by zero!");
        return -1;
    } else {
        return x / y;
    }
}

int main() {

    Error err;
    int retValue = div(4, 2, &err);

    if (retValue < 0)
    {
        printf("Code: %d, Message: %s\n", err->code, err->message);
    }
    else
    {
        printf("%d\n", retValue);
    }
}

我知道有时结果可能小于零,但这只是一个粗略的例子。

【讨论】:

    猜你喜欢
    • 2019-09-07
    • 2019-03-18
    • 2021-04-16
    • 2010-11-19
    • 2010-11-16
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    • 2018-05-04
    相关资源
    最近更新 更多