溢出发生了,但 errno 不是 ERANGE。这是怎么发生的?

5

我正在尝试利用 errno 来检测是否执行了导致溢出的操作。然而,虽然我编写了一个故意造成溢出的函数,但 errno == ERANGE 是错误的。这是怎么回事?

以下是代码:

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

int main(int argc, char* argv[]) {
    unsigned char c = 0;
    int i;

    for (i = 0; i< 300; i++) {
        errno = 0;
        c = c + 1;
        if (errno == ERANGE) {// we have a range error
            printf("Overflow. c = %u\n", c);
        } else {
            printf("No error. c = %u\n", c);
        }
    }
    return 0;
}

我原本期望在将255加1的时候会出现溢出错误,但是却没有出现。以下是(截取的)输出结果:

No error. c = 245
No error. c = 246
No error. c = 247
No error. c = 248
No error. c = 249
No error. c = 250
No error. c = 251
No error. c = 252
No error. c = 253
No error. c = 254
No error. c = 255
No error. c = 0
No error. c = 1
No error. c = 2
No error. c = 3
No error. c = 4
No error. c = 5
No error. c = 6
No error. c = 7
No error. c = 8
No error. c = 9

有人能解释一下为什么它没有检测到错误,以及我要如何更改它或制作一个可以检测是否溢出的函数吗?请注意:最终我想使用long int来做这件事,所以不能简单地将它转换为更大的数据类型。
编辑:
我后来找到了一些简单的函数,可以分别检测整数数据类型的加法和乘法溢出。虽然它并不涵盖所有情况,但它涵盖了很多情况。
乘法:
int multOK(long x, long y)
/* Returns 1 if x and y can multiply without overflow, 0 otherwise */
{
    long p = x*y;

    return !x || p/x == y;
}

有符号数加法:

int addOK(long x, long y)
/* Returns 1 if x and y can add without overflow, 0 otherwise */
{
    long sum = x+y;
    int neg_over = (x < 0) && (y < 0) && (sum >= 0);
    int pos_over = (x >= 0) && (y >= 0) && (sum < 0);
    return !neg_over && !pos_over;
}

无符号加法:

int unsignedAddOK(unsigned long x, unsigned long y)
/* Returns 1 if x and y can add without overflow, 0 otherwise */
{
    unsigned long sum = x+y;

    return sum > x && sum > y;
}

简单的递增操作不会引发错误,因此errno不会被设置。函数(例如strtod)会设置errno。 - jedwards
此外,查看 <limits.h> 可能会有所帮助,以及 这个相关问题 - jedwards
你可能希望使用一种检查整数溢出的语言(例如Ada或使用checked的C#),或者一种透明支持任意精度算术的语言(例如Python,Lisp,Haskell等)。 - Dietrich Epp
4个回答

2
据我所知,errno是由函数设置的。它无法检测加法溢出错误,除非您创建自己的函数来检测。您可以测试结果是否小于其中一个操作数。
#include <stdio.h>
#include <errno.h>

int main(int argc, char* argv[]) {
    unsigned char c = 0, result = 0;
    int i;

    for (i = 0; i< 300; i++) {
        result = c + 1;
        if (result < c) {// we have a range error
            printf("Overflow. c = %u\n", result);
        } else {
            c = result;
            printf("No error. c = %u\n", c);
        }
    }
    return 0;
}

是的,但这并不能涵盖所有可能性。例如,如果您添加一个值,该值本身超过了溢出限制,例如尝试将300添加到char中,则取决于“c”的原始值,您仍然可能得到一个大于“c”的数字,但仍然是错误的。 - limp_chimp

2

Errno不是处理器集变量,它仅被系统调用和一些标准函数用于报告错误。

我不知道检测你所问的任何技术,除了在每个循环迭代中验证c < c + 1

编辑:经过一些研究,我发现这个关于状态寄存器的维基百科,它是CPU标志,指示此类错误。


此帖子解释了为什么没有标准的状态寄存器检查。 - tomahh

2

除了其他人提到的关于errno的问题外,您正在使用unsigned char作为计数器。无符号类型被明确定义为环绕。换句话说,当c是8位unsigned char时,计算c=255; c=c+1;并不是错误:结果总是0


1

整数溢出是未定义行为。未定义行为不是可检测的条件。这是终局。一旦您调用了未定义行为,您就无法得出关于程序状态的任何结论。

除此之外,errno 与算术表达式无关。它用于报告库函数中的错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接