当我已经返回一个值时,从函数返回错误的最佳方法是什么?

时间:2008-11-15 00:04:12

标签: c return

我在C中编写了一个函数,它将字符串转换为整数并返回整数。当我调用该函数时,我也希望它告诉我字符串是不是有效数字。在过去,当发生此错误时,我返回-1,因为我不需要将字符串转换为负数。但现在我希望它将字符串转换为负数,那么报告错误的最佳方法是什么?

如果我不清楚这一点:我不希望此函数向用户报告错误,我希望它将错误报告给调用该函数的代码。 (“报告”可能是错误的用词......)

以下是代码:

s32 intval(const char *string) {
    bool negative = false;
    u32 current_char = 0;

    if (string[0] == '-') {
        negative = true;
        current_char = 1;
    }

    s32 num = 0;
    while (string[current_char]) {
        if (string[current_char] < '0' || string[current_char] > '9') {
            // Return an error here.. but how?
        }

        num *= 10;
        num += string[current_char] - '0';
        current_char++;
    }

    if (negative) {
        num = -num;
    }

    return num;
}

8 个答案:

答案 0 :(得分:35)

有几种方法。所有人都有他们的优点和缺点。

  • 让函数返回错误代码并传入指向某个位置的指针以返回结果。关于这一点的好处是没有结果的重载。不好的是,你不能直接在表达式中使用函数的实际结果。

    Evan Teran suggested一个变体,它使调用者传递一个指向成功变量的指针(如果调用者不在意,可以选择为NULL)并从函数返回实际值。这样做的好处是,当调用者在错误结果中使用默认值或者知道函数不能失败时,允许函数直接在表达式中使用。

  • 使用特殊的'sentinel'返回值来指示错误,例如负数(如果正常返回值不能为负数)或INT_MAXINT_MIN如果不能提供良好值那种极端。有时为了获得更详细的错误信息,需要查询另一个函数(例如GetLastError())或全局变量(例如errno)。当您的返回值没有无效值时,这种方法效果不佳,并且很多人认为这种方式很糟糕。

    使用此技术的示例函数是getc(),如果到达文件末尾或遇到错误,则返回EOF。

  • 让函数永远不会直接返回错误指示,但要求调用者查询另一个函数或全局函数。这类似于VB的“On Error Goto Next”模式的工作方式 - 而且它几乎普遍被认为是一种不好的方式。

  • 另一种方法是拥有'默认'值。例如,atoi()函数与intval()函数具有几乎相同的功能,当它无法转换任何字符时将返回0(它与函数的不同之处在于它消耗字符到转换,直到它到达字符串的结尾或不是数字的字符。)

    这里明显的缺点是,判断实际值是否已转换或者是否已将垃圾传递给atoi()可能会很棘手。

    我不是这种处理错误的忠实粉丝。

我会更新,因为其他选项在我的脑海中......

答案 1 :(得分:16)

那么,.NET在Int32.TryParse中处理这个问题的方法是返回成功/失败,并使用pass-by-reference参数传回解析后的值。同样可以在C:

中应用
int intval(const char *string, s32 *parsed)
{
    *parsed = 0; // So that if we return an error, the value is well-defined

    // Normal code, returning error codes if necessary
    // ...

    *parsed = num;
    return SUCCESS; // Or whatever
}

答案 2 :(得分:11)

一种常见的方法是将指针传递给成功标志,如下所示:

int my_function(int *ok) {
    /* whatever */
    if(ok) {
        *ok = success;
    }
    return ret_val;
}

这样称呼:

int ok;
int ret = my_function(&ok);
if(ok) {
    /* use ret safely here */
}

编辑:这里的示例实现:

s32 intval(const char *string, int *ok) {
    bool negative = false;
    u32 current_char = 0;

    if (string[0] == '-') {
        negative = true;
        current_char = 1;
    }

    s32 num = 0;
    while (string[current_char]) {
        if (string[current_char] < '0' || string[current_char] > '9') {
                // Return an error here.. but how?
                if(ok) { *ok = 0; }
        }

        num *= 10;
        num += string[current_char] - '0';
        current_char++;
    }

    if (negative) {
        num = -num;
    }
    if(ok) { *ok = 1; }
    return num;
}

int ok;
s32 val = intval("123a", &ok);
if(ok) {
    printf("conversion successful\n");
}

答案 3 :(得分:6)

os风格的全局errno变量也很受欢迎。使用errno.h

如果errno不为零,则出现问题。

以下是errno的手册页参考。

答案 4 :(得分:4)

看看标准库如何处理这个问题:

long  strtol(const  char  * restrict str,  char **restrict endptr, int base);

这里,在调用之后,endptr指向第一个无法解析的字符。如果endptr == str,则没有转换任何字符,这是一个问题。

答案 5 :(得分:3)

总的来说,我更喜欢Jon Skeet提出的方式,即。返回bool(int或uint)关于成功并将结果存储在传递的地址中。但是你的函数与strtol非常相似,所以我认为为你的函数使用相同(或类似)的API是个好主意。如果你给它一个类似my_strtos32这样的名字,这样就可以很容易地理解这个函数在没有任何文档阅读的情况下的作用。

编辑:由于你的函数明确是基于10的,my_strtos32_base10是一个更好的名字。只要您的功能不是瓶颈,您就可以跳过实施。并简单地包围strtol:


s32
my_strtos32_base10(const char *nptr, char **endptr)
{
    long ret;
    ret = strtol(nptr, endptr, 10);
    return ret;
}

如果您后来意识到它是一个瓶颈,您仍然可以根据自己的需要对其进行优化。

答案 6 :(得分:0)

您可以返回属性为感兴趣的值的类的实例,另一个属性是某种状态的标志。或者,传入结果类的实例..

Pseudo code
  MyErrStatEnum = (myUndefined, myOK, myNegativeVal, myWhatever)

ResultClass
  Value:Integer;
  ErrorStatus:MyErrStatEnum

示例1:

result := yourMethod(inputString)

if Result.ErrorStatus = myOK then 
   use Result.Value
else
  do something with Result.ErrorStatus

free result

示例2

create result
yourMethod(inputString, result)

if Result.ErrorStatus = myOK then 
   use Result.Value
else
  do something with Result.ErrorStatus

free result

这种方法的好处是,您可以通过向Result类添加其他属性来随时扩展回来的信息。

为了进一步扩展这个概念,它也适用于具有多个输入参数的方法调用。例如,代替CallYourMethod(val1,val2,val3,bool1,bool2,string1)而不是具有与val1,val2,val3,bool1,bool2,string1匹配的属性的类,并将其用作单个输入参数。它可以清除方法调用,并使代码在将来更容易修改。我相信你已经看到使用多个参数的方法调用更难以使用/调试。 (7是我想说的绝对最多。)

答案 7 :(得分:0)

  

当我已经返回值时,从函数返回错误的最佳方法是什么?

对各种答案的一些其他想法。


返回结构

代码可以返回一个值和一个错误代码。问题在于类型的扩散。

typedef struct {
  int value;
  int error;
} int_error;

int_error intval(const char *string);

...

int_error = intval(some_string);
if (int_error.error) {
  Process_Error();
}

int only_care_about_value = intval(some_string).value;
int only_care_about_error = intval(some_string).error;

非数字和NULL

在函数返回类型提供它时使用一个特殊值。
C不需要非数字,而是无处不在。

#include <math.h>
#include <stddef.h>

double y = foo(x);
if (isnan(y)) {
  Process_Error();
}

void *ptr = bar(x);
if (ptr == NULL) {
  Process_Error();
}

_Generic /函数重载

考虑error_t foo(&dest, x)dest_t foo(x, &error)的优缺点,

通过级联使用_Generic或函数重载作为编译器扩展,选择2种或更多类型,可以根据调用的参数(而不是返回值)来区分要调用的基础函数值。返回常见类型,错误状态。

示例:函数error_t narrow(destination_t *, source_t)将一种类型的值转换为较窄的类型,例如将long long转换为short并测试源 value 是否为在目标 type 的范围内。

long long ll = ...; 
int i;
char ch; 
error = narrow(&i, ll);
...
error = narrow(&ch, i);