我在c ++方面缺乏经验,我编写了以下代码来了解字符和字符串的工作原理。
#include "stdio.h"
#include <iostream>
#include <string>
using namespace std;
int main()
{
char asdf[] = "hello";
char test[5] = {'h','e','l','l','o'};
cout << test;
}
我原本应该输出&#34;你好&#34;,而是我得到了#34; hellohello&#34;,这对我来说真是令人费解。我做了一些实验:
如果我将asdf更改为另一个不同长度的字符串,则输出&#34; hello&#34;一般。 如果我改变测试中的字符数量,它会输出&#34;你好&#34;通常
我认为这只发生在两者长度相同的情况下,但是当我将它们都更改为&#34; hell&#34;它似乎输出&#34; hell&#34;通常
为了让事情变得更加混乱,当我让朋友在他们的计算机上运行这些代码时,它输出了#34;你好&#34;然后是一个随机的角色。
我在Ubuntu上运行全新的代码块安装。任何人都知道这里发生了什么?
答案 0 :(得分:8)
这是未定义的行为。
C和C ++中的原始char*
或char[]
字符串必须为NULL-terminated。也就是说,字符串需要以'\0'
字符结尾。你的test[5]
没有这样做,所以打印输出的函数在最后o
之后继续,因为它仍然在寻找NULL终止。
由于字符串如何存储在stack上(堆栈通常朝 lower 地址增长),它遇到的下一个字节是asdf[]
的字节,您已分配"hello"
。这就是内存布局的实际情况,箭头表示内存地址(思考指针)增加的方向:
---->
+-------------------
|hellohello\0 ...
+-------------------
\_ asdf
\_ test
现在在C ++和C中,像"hello"
这样的字符串文字是以NULL方式隐式终止的,因此编译器会在字符串末尾后面写一个隐藏的'\0'
。输出函数继续打印asdf char-by-char的内容,直到它到达隐藏的'\0'
然后停止。
如果要移除asdf
,您可能会在第一个hello
之后看到一些垃圾,然后是分段错误。但这是未定义的行为,因为您正在读出test
数组的边界。这也解释了为什么它在不同的系统上表现不同:例如,一些编译器可能决定在堆栈上以不同的顺序布置变量,因此在您的朋友系统上,test
实际上在堆栈中较低(记住,堆栈中的较低意味着在更高的地址处):
---->
+-------------------
|hello\0hello ...
+-------------------
\_ test
\_ asdf
现在,当您打印test
的内容时,它将打印hello
char-by-char,然后继续读取内存,直到找到\0
。 ...
的内容高度特定于所使用的架构和运行时,甚至可能是月亮的阶段和一天中的时间(并非完全严重),因此在您的朋友机器上它会打印一个“随机”字符然后停止。
您可以通过在'\0'
数组中添加0
或test
来解决此问题(您需要将大小更改为6)。但是,使用const char test[] = "hello";
是解决此问题的最佳方法。
答案 1 :(得分:5)
您必须使用ascii 0 char终止test
数组。现在发生的是在内存中它与你的asdf字符串相邻,所以由于test
没有终止,<<
将一直持续到asdf
结束时遇到ascii 0 }。
如果您想知道:填充asdf
时,会自动添加此ascii 0。
答案 2 :(得分:4)
原因是C样式字符串需要空字符来标记字符串的结尾。
由于你还没有将它放入数组test
,它只会继续打印字符,直到它找到一个。在你的情况下,数组asdf
碰巧在内存中跟随test
- 但这不能保证。
而是将代码更改为:
char test[] = {'h','e','l','l','o', 0};
答案 3 :(得分:2)
cout
正在打印从给定地址开头的所有字符(此处为test
,或等效表示法中的&test[0]
),直至找到空终止符。由于您没有将空终结符放入测试数组,因此它将继续打印,直到它意外地在内存中找到一个。从这一点来看,它几乎是未定义的行为。
答案 4 :(得分:0)
最后一个字符应为'\0'
以表示字符串结尾。
char test[6] = {'h','e','l','l','o','\0'};
答案 5 :(得分:0)
除非有operator<<
的重载用于引用5个字符的数组,否则数组将“衰减”为指向char的指针并被操作符视为C样式字符串。按照惯例,C样式字符串以0 char结尾,您的数组缺少该字符串。因此,操作员继续输出内存中的字节,将它们解释为可打印的字符。恰好在堆栈上,两个数组相邻,以便运算符进入asdf
的内存区域,输出这些字符,最后遇到{{1}末尾的隐式0字符。 }。如果省略另一个声明,则程序可能会崩溃,即下一个0字节晚于程序的内存边界。
通过指向该对象的指针访问对象外部的内存(此处为:"hello"
)是未定义的行为。
答案 6 :(得分:0)
字符序列需要 null terminator (\0
)。
char asdf[] = "hello"; // OK: String literals have '\0' appended at the end
char test[5] = {'h','e','l','l','o'}; // Oops, not null terminated. UB
修正:
char test[6] = {'h','e','l','l','o','\0'}; // OK
// ^ ^^^^