首页 > 解决方案 > 溢出如何不同地发生?

问题描述

我是 C 新手,仍在努力理解溢出是如何发生的。假设我们有以下错误代码来确定一个字符串是否比另一个长:

int strlonger(char *s, char *t) {
   return strlen(s) - strlen(t) > 0;  // let's say the first return value of strlen(s) is s1, abd the second is s2
}

并且我们知道它不会像strlen()is size_twhich is的返回类型那样工作unsigned int,所以当我们有类似1u - 2u > 0; 时,左操作数会溢出。

我有点明白,它就像是 1u - 2u-1,但因为 s1 和 s2 都是unsigned int,结果也应该是unsigned int,因此它溢出。

但考虑到不同的情况:

int a= 1048577;
size_t b = 4096;
long long unsigned c= a* b;

由于 1048577*4096 = 4294971392 超出了 int 或 unsigned b 的范围,所以结果不是应该先溢出吗?为什么仅仅因为左操作数 clong long unsigned可以保存值而保留结果以保留值?,仅以这种方式使其工作不是更明智:

long long unsigned a= 1048577;
long long unsigned b = 4096;
long long unsigned c= a* b;

标签: c

解决方案


我有点明白,它就像 1u - 2u 是 -1,但因为 s1 和 s2 都是无符号整数,结果也应该是无符号整数,因此它溢出。

一点也不。

结果是您希望的任何类型,当然(它可以是double我所关心的),但结果类型并不重要 - 或者至少它不是最重要的,因为它不影响操作本身是否是“好”与否。必须先定义操作本身,然后才能开始考虑将结果转换为任何类型(或将其保留为“自然”类型)。

您应该关注的是是否定义了对相同无符号类型的两个值进行减法等操作。事实上,它总是被定义的。C 标准说明了结果是什么 - 很明显没有溢出。事实上,它更清楚:结果永远不会溢出

涉及无符号操作数的计算永远不会溢出,因为无法由结果无符号整数类型表示的结果会以比结果类型可以表示的最大值大一的数字为模减少。(ISO/IEC 9899:1999 (E) §6.2.5/9)

不仅如此,整数和无符号整数之间的转换也得到了很好的定义,并且-1(整数类型)转换为您将其转换为的任何无符号类型的最大值。基本上,-1转换为 unsigned int 是一种简短的写作方式UINT_MAX等。

unsigned char uc = -1;
assert(uc == UCHAR_MAX);
unsigned short us = -1;
assert(us == USHORT_MAX);
unsigned int ui = -1;
assert(ui == UINT_MAX);
unsigned long ul = -1;
assert(ul == ULONG_MAX);
// etc.

long long unsigned c= a* b; 由于 1048577*4096 = 4294971392 超出了 int 或 unsigned b 的范围,所以结果不是应该先溢出吗?

C 语言根本不是为了按照您的方式解释它而设计的。就这样。编程语言设计中的大多数决定都是完全任意的。当然,您可能会惊讶于设计师做出的决定与您所做的不同,但两者都同样武断。

这里发生的是整个计算都是使用该long long unsigned类型执行的,并且因为它是无符号类型,所以它永远不会溢出。C标准是这么说的。这就是它的全部。

有人可能会争辩说,按照您建议的方式进行操作会更糟,因为要获得看起来应该有效的东西,需要输入更多的内容。如果 C 以您想要的方式工作,您需要编写如下表达式:

int a = 1048577;
size_t b = 4096;
long long unsigned c = (long long unsigned)a * (long long unsigned)b;

有人可能会争辩说,以这种方式强迫每个人用无休止的强制转换来污染他们的代码至少可以说是不友好的。C 比你想象的要好。

当然,C 也充满了令人憎恶的东西,所以你很幸运,你问了这个问题,而不是说,关于为什么gets()不好的第 100 万个问题。事实是:gets()就像伏地魔。你不说gets,你不使用gets,一切都很好。


推荐阅读