我正在观察字符串操作的一些奇怪行为。
前:
int main()
{
std::string name("ABCDEFGHIJ");
std::cout << "Hello, " << name << "!\n";
name.clear();
std::cout << "Hello, " << name << "!\n";
name.assign("ABCDEF",6);
std::cout << "Hello, " << name << "!\n";
std::cout << "Hello, " << name[8] << "!\n";
}
输出:
Hello, ABCDEFGHIJ!
Hello, !
Hello, ABCDEF!
Hello, I!
string :: clear实际上没有清除,因为即使清除后我也能访问数据。根据文档,当我们访问超出范围的东西时,结果是未定义的。但在这里,我每次都得到相同的结果。 当我们调用clear或者opeartor []时,有人可以解释它在内存级别是如何工作的。
答案 0 :(得分:8)
欢迎来到C ++的惊人吸引力,称为“未定义的行为”。
当name
包含六个字符的字符串“ABCDEF”时,name[8]
会尝试访问字符串中不存在的成员,这是未定义的行为。
这意味着此操作的结果完全没有意义。
C ++标准没有定义访问字符串中不存在的成员字符的结果;因此未定义的行为。此操作的潜在结果可能是:
字符串中的某个先前值,位于给定位置。
一些垃圾,随机字符。
您的程序崩溃了。
还有别的。
每次执行程序时都会有不同的结果,从选项1到4中选择。
答案 1 :(得分:3)
name.assign( “ABCDEF”,6);
现在字符串的长度为6.因此,您可以合法地只访问元素0到5。
std :: cout&lt;&lt; “你好,”&lt;&lt; name [8]&lt;&lt; “\ N!”;
因此,这是未定义的行为。编译器可以随心所欲地做任何事情。不只是声明,而是整个程序,甚至前面的行!
此时,它返回了之前在该位置的角色。它可能已经返回任何其他东西,它可能已经崩溃,它可能完全跳过该声明,它可能已经跳过赋值和许多其他有趣的东西(包括让守护进程从你的鼻子中飞出来) !)。
而且我这样说是因为在各种情况下,所有这些行为(守护进程除外)都可以在野外实际观察到。
答案 2 :(得分:2)
正如其他人所说,访问它之外的std::string
逻辑边界(即[0,size()],注意包含size())是未定义的行为,因此编译器可以做任何事情发生。
现在,你所看到的UB的特殊风味并不是特别出乎意料。
clear()
只是将字符串的逻辑长度归零,但保留了它所使用的内存(标准实际需要它,而且如果没有这种行为,相当一些代码的工作速度会慢一些)。
鉴于没有充分的理由浪费时间将旧数据归零,通过访问超出界限的字符串,您可以看到之前在该索引处的内容。
如果你这样做可能会改变在shrink_to_fit()
之后调用clear()
方法,该方法要求字符串释放它保留的所有额外内存。
答案 3 :(得分:1)
我想添加其他答案,您可以使用std::string::at
而不是operator[]
。
std::string::at
执行边界检查,并在您尝试访问超出范围的元素时抛出std::out_of_range
。
答案 4 :(得分:0)
[我通过调试器运行你的代码。记下字符串的容量。它仍然是15.&#34;分配&#34;没有改变容量。所以你不会得到&#34;垃圾&#34;每个人都说的价值。您获得存储在同一位置的完全相同的数据。如上所述,字符串只是指向内存地址的指针。它将超过x个字节来访问该元素。 name [8]是一个常量值,它将转到完全相同的内存位置。 Here is a picture of the string in debugger