带有负值的 size_t 算术运算

4

我被要求在一项作业中复制std::string,但实现substr函数时遇到了问题。教师给我们提供了一组测试数据,其中一个测试数据的length等于-1。我的substr函数声明如下:

Cadena substr(size_t start, size_t length) const;

我曾经认为 size_t 可以防止传入负数值。但问题在于,定义中我检查了 size() < start + length(假设 tam_ 等同于 size()):

    if (tam_ <  start + length)
        throw std::out_of_range("Error");

在我的系统中,-1unsigned 中表示为 18446744073709551615,因此例如假设 start9tam_10,我期望的结果是:
10 <  9 + 18446744073709551615

因此,预期会抛出异常,但实际上我得到的是:
10 < 9 + (-1)

其中一个说法是错误的,并且不会抛出异常。随着函数的继续,它为大小为length + 1的char数组分配内存,但由于new[]正常处理size_t,将其视为应该的18446744073709551615,这个数字非常大,导致程序崩溃。

我想知道为什么我的期望结果不是正确的。


1
你可能会对std::string::npos感兴趣,它在标准库中执行类似的操作。你可能需要明确检查length(size_t)-1的情况。 - François Andrieux
1
使用 unsigned 并不能防止负数被传递。它们只会被转换为一个很大的正数。 - Barmar
1
unsigned类型使用模数算术。 - Barmar
1
快速演示:https://ideone.com/JMkBod - user4581301
2
@Cako 但你可以将其用作以前如何完成的参考,这应该是重点。 - Drise
显示剩余6条评论
1个回答

2

溢出和下溢问题很难检查。一般来说,你需要在进行算术运算之前检查,而不是之后。

首先,让我们清楚地了解正在发生的事情。

// The call
foo.substr(9, -1);
-1是一个int,但该函数需要一个std::size_t,因此-1被转换为一个很大的数字(RBN)。接着,您可以进行测试:
size() < start + length

这些是无符号类型,所以如果算术计算(即加法)超过了可以表示的范围的上限或下限,该值将会进行环绕运算。(对于有符号类型,行为将是未定义的。)
这里,“length”是RBN。当您将“start”和RBN相加时,加法会环绕到一个小的数字上。您错过了溢出的检测。
为了解决此问题,您需要检查两件事:(1)“start”是否在范围内,(2)字符串在“start”后的余数是否比“length”长。
if (start > size() || size() - start < length)
  throw std::::out_of_range("Error");

第一步非常重要,因为它保证了第二步中的减法不会低于0。


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