首先:为什么会崩溃?让我们像调试器一样单步执行您的程序。
注意:我假设你的循环体不是空的,而是访问字符串。如果不是这种情况,则崩溃的原因是整数溢出导致的未定义行为。请参阅 Richard Hansens 对此的回答。
std::string s = "aa";//assign the two-character string "aa" to variable s of type std::string
for ( int i = 0; // create a variable i of type int with initial value 0
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 1!
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 2!
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 3!
.
.
我们预计检查i < s.length() - 3
会立即失败,因为 的长度s
是 2(我们只在开始时给它一个长度并且从未改变它)并且2 - 3
is -1
,0 < -1
是假的。但是,我们确实在这里得到了“OK”。
这是因为s.length()
不是2
。是2u
。std::string::length()
返回类型size_t
为无符号整数。所以回到循环条件,我们首先得到 的值s.length()
,所以2u
,现在减去3
。3
是一个整数文字,编译器将其解释为 type int
。所以编译器必须计算2u - 3
, 两个不同类型的值。对原始类型的操作仅适用于相同类型,因此必须将一种类型转换为另一种类型。有一些严格的规则,在这种情况下,unsigned
“获胜”,所以3
get 转换为3u
. 在无符号整数中,2u - 3u
不能-1u
因为这样的数字不存在(好吧,因为它当然有符号!)。相反,它计算每个操作modulo 2^(n_bits)
,其中n_bits
是这种类型的位数(通常为 8、16、32 或 64)。所以不是-1
我们得到4294967295u
(假设是32位)。
所以现在编译器完成了s.length() - 3
(当然它比我快得多;-)),现在让我们进行比较:i < s.length() - 3
. 输入值:0 < 4294967295u
。再次,不同的类型,0
变成0u
,比较0u < 4294967295u
显然是正确的,循环条件是肯定的,我们现在可以执行循环体。
递增后,上面唯一变化的是 的值i
。的值i
将再次转换为无符号整数,因为比较需要它。
所以我们有
(0u < 4294967295u) == true, let's do the loop body!
(1u < 4294967295u) == true, let's do the loop body!
(2u < 4294967295u) == true, let's do the loop body!
问题来了:你在循环体中做了什么?大概你访问你的字符串的i^th
字符,不是吗?尽管这不是你的本意,但你不仅访问了第零个和第一个,而且还访问了第二个!第二个不存在(因为你的字符串只有两个字符,第零个和第一个),你访问你不应该访问的内存,程序做任何它想要的(未定义的行为)。请注意,程序不需要立即崩溃。再过半个小时它似乎可以正常工作,所以这些错误很难被发现。但是越界访问内存总是很危险的,这是大多数崩溃的来源。
所以总而言之,你得到的值与s.length() - 3
你所期望的不同,这会导致一个积极的循环条件检查,这会导致循环体的重复执行,它本身会访问它不应该访问的内存。
现在让我们看看如何避免这种情况,即如何告诉编译器您在循环条件中的实际含义。
字符串的长度和容器的大小本质上是无符号的,因此您应该在 for 循环中使用无符号整数。
由于unsigned int
相当长,因此不希望在循环中一遍又一遍地写入,只需使用size_t
. 这是 STL 中每个容器用于存储长度或大小的类型。您可能需要包含cstddef
以声明平台独立性。
#include <cstddef>
#include <string>
using namespace std;
int main() {
string s = "aa";
for ( size_t i = 0; i + 3 < s.length(); i++) {
// ^^^^^^ ^^^^
}
}
由于a < b - 3
在数学上等价于a + 3 < b
,我们可以互换它们。但是,a + 3 < b
防止b - 3
成为一个巨大的价值。回想一下,s.length()
返回一个无符号整数和无符号整数执行操作模块2^(bits)
,其中位是类型中的位数(通常为 8、16、32 或 64)。因此与s.length() == 2
, s.length() - 3 == -1 == 2^(bits) - 1
。
或者,如果您想i < s.length() - 3
用于个人喜好,您必须添加一个条件:
for ( size_t i = 0; (s.length() > 3) && (i < s.length() - 3); ++i )
// ^ ^ ^- your actual condition
// ^ ^- check if the string is long enough
// ^- still prefer unsigned types!