以下代码使运行时错误导致C++崩溃:
#include <string>
using namespace std;
int main() {
string s = "aa";
for (int i = 0; i < s.length() - 3; i++) {
}
}
Run Code Online (Sandbox Code Playgroud)
虽然此代码不会崩溃:
#include <string>
using namespace std;
int main() {
string s = "aa";
int len = s.length() - 3;
for (int i = 0; i < len; i++) {
}
}
Run Code Online (Sandbox Code Playgroud)
我只是不知道如何解释它.这种行为可能是什么原因?
Ant*_*nio 85
s.length()是无符号整数类型.当你减去3时,你将它减为负数.对于一个unsigned,它意味着非常大.
解决方法(有效期为字符串长达INT_MAX)将是这样的:
#include <string>
using namespace std;
int main() {
string s = "aa";
for (int i = 0; i < static_cast<int> (s.length() ) - 3; i++) {
}
}
Run Code Online (Sandbox Code Playgroud)
哪个永远不会进入循环.
一个非常重要的细节是您可能收到了"比较有符号和无符号值"的警告.问题是,如果忽略这些警告,则进入隐式 "整数转换" (*)的非常危险的字段,该字段具有已定义的行为,但很难遵循:最好的是永远不要忽略那些编译器警告.
ste*_*fan 28
首先:为什么会崩溃?让我们像调试器一样逐步执行您的程序.
注意:我假设你的循环体不是空的,但访问字符串.如果不是这种情况,则崩溃的原因是通过整数溢出的未定义行为.请参阅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!
.
.
Run Code Online (Sandbox Code Playgroud)
我们预计支票i < s.length() - 3马上失败,因为长度s为两(我们只每一个给定它在开始的长度,并没有改变它),2 - 3是-1,0 < -1是假的.但是我们在这里得到了"OK".
这是因为s.length()不是2.是的2u.std::string::length()具有size_t无符号整数的返回类型.所以回到循环条件,我们首先得到的值s.length(),所以2u,现在减去3.3是一个整数文字,由编译器解释为类型int.所以编译器必须计算2u - 3两种不同类型的值.对原始类型的操作仅适用于相同类型,因此必须将其转换为另一种类型.有一些严格的规则,在这种情况下,unsigned"胜利",所以3get转换为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再次转换为unsigned int,因为比较需要它.
所以我们有
(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!
Run Code Online (Sandbox Code Playgroud)
这就是问题:你在循环体中做了什么?想必您访问了i^th您的字符串的字符,不是吗?即使这不是你的意图,你不仅访问了第一个,而且第二个!第二个不存在(因为你的字符串只有两个字符,第0个和第一个),你访问内存你不应该,程序做任何它想要的(未定义的行为).请注意,程序不需要立即崩溃.它似乎可以再工作半小时,所以这些错误很难捕捉到.但是访问超出边界的内存总是很危险的,这是大多数崩溃的来源.
总而言之,你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++) {
// ^^^^^^ ^^^^
}
}
Run Code Online (Sandbox Code Playgroud)
由于a < b - 3在数学上等同于a + 3 < b,我们可以互换它们.但是,a + 3 < b防止b - 3成为巨大的价值.回想一下,s.length()返回无符号整数和无符号整数执行操作模块2^(bits),其中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!
Run Code Online (Sandbox Code Playgroud)
Som*_*ude 12
实际上,在第一个版本中,循环很长一段时间,与 包含非常大数字i的无符号整数进行比较.字符串的大小(实际上)与size_t无符号整数相同.当你3从该值中减去它时,它会下溢并继续成为一个很大的值.
在代码的第二个版本中,将此无符号值分配给有符号变量,这样您就可以得到正确的值.
并且它实际上不是导致崩溃的条件或值,它最有可能是将字符串索引出界限,这是一种未定义的行为.
for循环中遗漏了重要的代码这里的大多数人似乎无法重现崩溃 - 我自己包含 - 并且看起来这里的其他答案是基于你在for循环体中遗漏了一些重要代码的假设,并且丢失的代码是导致你的崩溃.
如果您使用i的主体访问内存(字符串中可能人物)for循环,你离开的代码你的问题,试图提供一个最小的例子,然后崩溃很容易被这样的事实解释s.length() - 3了SIZE_MAX由于无符号整数类型的模运算所致的值. SIZE_MAX是一个非常大的数字,所以i将继续变大,直到它用于访问触发段错误的地址.
但是,即使for循环体是空的,理论上你的代码也可以原样崩溃.我不知道任何会崩溃的实现,但也许你的编译器和CPU是异国情调.
以下说明并未假设您在问题中遗漏了代码.我们相信您在问题中发布的代码会按原样崩溃; 它不是一个崩溃的其他代码的缩写替代品.
您的第一个程序崩溃,因为这是对代码中未定义行为的反应.(当我尝试运行您的代码时,它会终止而不会崩溃,因为这是我的实现对未定义行为的反应.)
未定义的行为来自于溢出int.C++ 11标准说(在[expr]第5条第4款中):
如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义.
在您的示例程序中,s.length()返回size_t值为2.从中减去3将产生负1,除了size_t是无符号整数类型.C++ 11标准说(在[basic.fundamental]第3.9.1条第4款中):
声明的无符号整数
unsigned应遵守算术模2 n的定律,其中n是该特定整数大小的值表示中的位数.4646)这意味着无符号算术不会溢出,因为无法用结果无符号整数类型表示的结果是以比可以由结果无符号整数类型表示的最大值大1的数量减少的模数.
这意味着结果s.length() - 3是size_t有价值的SIZE_MAX.这是一个非常大的数字,大于INT_MAX(可表示的最大值int).
因为s.length() - 3这么大,执行会在循环中旋转直到i达到INT_MAX.在下一次迭代中,当它尝试递增时i,结果将为INT_MAX+ 1但不在可表示值的范围内int.因此,行为是不确定的.在您的情况下,行为是崩溃.
在我的系统上,我的实现在i过去增加时的行为INT_MAX是换行(设置i为INT_MIN)并继续.一旦i达到-1,通常的算术转换(C++ [expr]第5段第9段)会导致i相等,SIZE_MAX因此循环终止.
这两种反应都是合适的.这是未定义行为的问题 - 它可能会按照您的意图工作,它可能会崩溃,它可能会格式化您的硬盘驱动器,或者它可能会取消Firefly.你永远都不会知道.
与第一个程序一样,s.length() - 3是一个size_t有价值的类型SIZE_MAX.但是,这次将值分配给int.C++ 11标准说(在[conv.integral]第4.7条第3款中):
如果目标类型已签名,则如果可以在目标类型(和位字段宽度)中表示该值,则该值不会更改; 否则,该值是实现定义的.
该值SIZE_MAX太大而无法通过a表示int,因此len获取实现定义的值(可能为-1,但可能不是).i < len无论分配给哪个值,条件最终都将成立len,因此您的程序将终止而不会遇到任何未定义的行为.