[英]C++ char array null terminator location
我是一名学习 C++ 的学生,我正在尝试了解以 null 结尾的字符 arrays 是如何工作的。 假设我像这样定义一个 char 数组:
char* str1 = "hello world";
正如预期的那样, strlen(str1)
等于 11,并且以 null 结尾。
C++ 把 null 结束符放在哪里,如果上面的 char 数组的 11 个元素都填满了字符“hello world”? 它实际上分配了一个长度为 12 而不是 11 的数组,第 12 个字符是'\0'
吗? CPlusPlus.com似乎暗示 11 之一需要是'\0'
,除非它确实在分配 12。
假设我执行以下操作:
// Create a new char array
char* str2 = (char*) malloc( strlen(str1) );
// Copy the first one to the second one
strncpy( str2, str1, strlen(str1) );
// Output the second one
cout << "Str2: " << str2 << endl;
这输出Str2: hello worldatcomY╗°g♠↕
,我假设它是 C++ 在指针char* str2
指向的位置读取 memory 直到遇到它解释为 null 的字符。
但是,如果我这样做:
// Null-terminate the second one
str2[strlen(str1)] = '\0';
// Output the second one again
cout << "Terminated Str2: " << str2 << endl;
它按预期输出Terminated Str2: hello world
。
但是写入str2[11]
是否意味着我们在str2
分配的 memory 空间之外写入,因为str2[11]
是第 12 个字节,但我们只分配了 11 个字节?
运行此代码似乎不会导致任何编译器警告或运行时错误。 这在实践中安全吗? 使用malloc( strlen(str1) + 1 )
而不是malloc( strlen(str1) )
会更好吗?
在字符串文字的情况下,编译器实际上为\0
元素保留了一个额外的char
元素。
// Create a new char array
char* str2 = (char*) malloc( strlen(str1) );
这是新 C 程序员常犯的错误。 为char*
分配存储空间时,您需要分配字符数 + 1 来存储\0
。 不在这里分配额外的存储意味着这一行也是非法的
// Null-terminate the second one
str2[strlen(str1)] = '\0';
在这里,您实际上写的是您分配的 memory 的末尾。 分配 X 元素时,您可以访问的最后一个合法字节是 memory 地址偏移X - 1
。 写入X
元素会导致未定义的行为。 它通常会起作用,但它是一颗定时炸弹。
正确的写法如下
size_t size = strlen(str1) + sizeof(char);
char* str2 = (char*) malloc(size);
strncpy( str2, str1, size);
// Output the second one
cout << "Str2: " << str2 << endl;
在此示例中,实际上不需要str2[size - 1] = '\0'
。 strncpy
function 将用 null 终止符填充所有额外的空间。 这里在str1
中只有size - 1
元素,所以数组中的最后一个元素是不需要的,将用\0
填充
它实际上分配了一个长度为 12 而不是 11 的数组,第 12 个字符是 '\0' 吗?
是的。
但是写入
str2[11]
是否意味着我们在str2
分配的 memory 空间之外写入,因为str2[11]
是第 12 个字节,但我们只分配了 11 个字节?
是的。
使用
malloc( strlen(str1) + 1 )
而不是malloc( strlen(str1) )
会更好吗?
是的,因为第二种形式不够长,无法将字符串复制到其中。
运行此代码似乎不会导致任何编译器警告或运行时错误。
除了最简单的情况外,在所有情况下检测到这一点是一个非常困难的问题。 所以编译器作者根本就不会打扰。
如果您正在编写 C++,这种复杂性正是您应该使用std::string
而不是原始 C 样式字符串的原因。就这么简单:
std::string str1 = "hello world";
std::string str2 = str1;
文字"hello world"
是一个char
数组,如下所示:
{ 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' }
所以,是的,文字的大小是 12 char
。
此外, malloc( strlen(str1) )
为 memory 分配比所需字节少 1 个字节,因为strlen
返回字符串的长度,不包括 NUL 终止符。 写入str[strlen(str1)]
是在您分配的 memory 之后写入 1 个字节。
你的编译器不会告诉你,但如果你通过valgrind或系统上可用的类似程序运行你的程序,它会告诉你你是否正在访问 memory 你不应该。
我认为您对strlen
的返回值感到困惑。 它返回字符串的长度,不应与保存字符串的数组的大小相混淆。 考虑这个例子:
char* str = "Hello\0 world";
我在字符串中间加了一个null字符,完全有效。 这里数组的长度为 13(12 个字符 + 最后的 null 个字符),但strlen(str)
将返回 5,因为在第一个 null 个字符之前有 5 个字符。 strlen
只是对字符进行计数,直到找到 null 个字符。
所以如果我使用你的代码:
char* str1 = "Hello\0 world";
char* str2 = (char*) malloc(strlen(str1)); // strlen(str1) will return 5
strncpy(str2, str1, strlen(str1));
cout << "Str2: " << str2 << endl;
str2 数组的长度为 5,并且不会以 null 字符结尾(因为strlen
不计算它)。 这是你所期望的吗?
对于标准的 C 字符串,存储字符串的数组长度始终比字符串的字符长度长一个字符。 因此,您的"hello world"
字符串的字符串长度为 11,但需要一个包含 12 个条目的支持数组。
原因很简单,就是读取这些字符串的方式。 处理那些字符串的函数基本上是一个一个地读取字符串的字符,直到找到终止字符'\0'
并在此处停止。 如果缺少此字符,这些函数只需继续阅读 memory,直到它们命中受保护的 memory 区域,导致主机操作系统终止您的应用程序或直到它们找到终止字符。
此外,如果您初始化一个长度为 11 的字符数组并将字符串"hello world"
写入其中,则会产生大量问题。 因为数组预计至少包含 12 个字符。 这意味着 memory 中数组后面的字节被覆盖。 导致不可预知的副作用。
此外,当您使用 C++ 时,您可能需要查看std:string
。 如果您使用 C++ 并提供更好的字符串处理,则可以访问此 class。 这可能值得研究一下。
我认为您需要知道的是 char arrays 从 0 开始一直到数组长度为 1,而在 position 数组长度上有终止符('\0')。
在你的情况下:
str1[0] == 'h';
str1[10] == 'd';
str1[11] == '\0';
这就是为什么正确的 str2[strlen(str1)] = '\0';
strncpy 之后的 output 的问题是因为它复制了 11 个元素(0..10),因此您需要手动放置终止符(str2[11] = '\0')。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.