C语言中的溢出和下溢

3

一个人应该怎么做才能发现他们的系统对溢出条件的响应?描述一种人们可以测试他们的系统对下溢条件的响应的方法。

我理解什么是溢出和下溢,我在维基百科上查过,但我不明白如何从系统到系统进行测试。


获取一个数字,将其递增直到溢出,查看结果是什么。 - Hot Licks
1
这个问题是关于算术溢出以及软件系统是否能够优雅地处理它们还是忽略它们。如果它是关于测试现有软件(如计算器),您可以通过输入应用程序无法处理的数字来手动触发溢出。可能的和适当的响应将是一个错误消息(例如“溢出错误”)。不适当的响应将是由于溢出而导致的错误计算结果。 - tiguchi
据我所知,算术下溢问题是指一个数字太小而无法在内存中表示为浮点数。您可以将两个非常小的数字相乘,其乘积将小于最小值,并查看结果。 - tiguchi
@HotLicks:试着使用浮点数。一旦它达到这样一个值,使得N + 1.0 == N,你就会陷入无限循环。 - Keith Thompson
@KeithThompson - 那么你已经得到了答案。 - Hot Licks
显示剩余5条评论
4个回答

2

使用无符号整数,C要求下溢和上溢的行为有一定规律。例如,请看以下代码:

unsigned int uintUnderflow = 0;
unsigned int uintOverflow = UINT_MAX;

printf("%u\n", --uintUnderflow); // Guaranteed to be UINT_MAX
printf("%u\n", ++uintOverflow);  // Guaranteed to be 0

现在有了signed整数,实现可以按照自己的意愿定义下溢和上溢情况。这就是未定义行为的特点。如果您找不到有关它如何运作的文档,那么您将不得不自己进行试验:

int intUnderflow = INT_MIN;
int intOverflow = INT_MAX;

printf("%d\n", --intUnderflow); // Implementation defined
printf("%d\n", ++intOverflow);  // Implementation defined

1
假设你指的是 C 语言。类似以下这样的代码:
 int main()
 {

    int i = 0;

    while(1)
   {
     i++;
    }

 }

点燃保险丝并远离现场。

我不理解这种测试溢出或下溢的区别。 - Cordelia Williams
1
这不会产生任何输出。有符号整数溢出的典型响应是静默环绕。严格来说,这个程序具有未定义的行为,但实际上它可能只是一个无限循环。 - Keith Thompson
1
现在你知道系统对溢出的响应了 - 静默忽略。那就是这个练习的目的。 - pm100

1
对于使用2补码编码的整数,有一种方法可以在加法操作后检测溢出:检查两个操作数和结果的符号。如果操作数具有相同的符号,但结果没有,那么就发生了溢出。
int is_overflow (char a, char b, char res)
{
  char mask = (1<<(8*sizeof(char)-1));
  if ((a & mask) == (b & mask) && (a & mask) != (res & mask))
    return 1;
  else
    return 0;
}

例如:

char a = 127;
char b = 1;
char c = a + b; 
is_overflow (a, b, c) => 1

char a = -128;
char b = -1;
char c = a + b; 
is_overflow (a, b, c) => 1

char a = -1;
char b = 1;
char c = a + b; 
is_overflow (a, b, c) => 0

char a = 1;
char b = 1;
char c = a + b; 
is_overflow (a, b, c) => 0

对于下溢,情况有所不同。我不了解整数下溢,但是浮点下溢。对于这些情况,数值处理器有标志来测试最后一次操作是否下溢,但我不知道从C语言中测试这些标志的可移植方式。


2
2的补码编码并不一定意味着在溢出时会有任何行为。通常情况下,计算“INT_MAX + 1”将会回绕并给出“INT_MIN”,但这种行为并没有被C标准定义;它可能会崩溃或者做任何事情。 - Keith Thompson
我相信2补码编码在溢出时有明确定义的行为,这也是CPU级别加法运算的方式。实际上,通常存储在标志寄存器中的溢出位是按照我展示的方式(或等效方式)计算的。为什么一个溢出的加法应该使进程崩溃(本身而不是由溢出导致的任何副作用)? - mcleod_ideafix
2
在大多数实现中,是的。但是二进制补码定义了一种存储格式;它本身并没有定义溢出时的行为。ISO C语言标准允许使用二进制补码、反码和原码表示有符号整数类型,但明确规定算术溢出的行为是未定义的。这实际上可能会产生可见的影响,因为优化编译器可能会假设程序的行为不是未定义的;例如,它可能将INT_MAX + 1 > INT_MAX简化为true(1),而不实际执行计算。 - Keith Thompson
你有测试过你的代码吗?因为 sizeof(char) 定义为 1,所以你的掩码始终为 1,你的代码没有检查溢出,而是检查函数参数是奇数还是偶数的奇怪组合。 - Art
糟糕!这里忘记了一个*8。谢谢! - mcleod_ideafix

0
你可以尝试这样做:
#include <stdio.h>
#include <limits.h>

int main() {
    int n = INT_MAX;
    ++n;
    printf("INT_MAX + 1: %d\n", n);

    unsigned u = UINT_MAX;
    ++u;
    printf("UINT_MAX + 1: %u\n", u);

    n = INT_MIN;
    --n;
    printf("INT_MIN - 1: %d\n", n);

    u = 0;
    --u;
    printf("0u - 1: %u\n", u);

    return 0;
}

无符号整数类型的行为由C标准规定;例如,对于任何符合C实现的情况,UINT_MAX + 1 == 0 - Keith Thompson

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