C++ char 数组空终止符位置

Posted

技术标签:

【中文标题】C++ char 数组空终止符位置【英文标题】:C++ char array null terminator location 【发布时间】:2012-04-20 11:16:11 【问题描述】:

我是一名学习 C++ 的学生,我正在尝试了解以空字符结尾的字符数组的工作原理。假设我像这样定义一个 char 数组:

char* str1 = "hello world";

正如预期的那样,strlen(str1) 等于 11,并且以空值结尾。

如果上述 char 数组的 11 个元素都用字符“hello world”填充,C++ 将空终止符放在哪里?它实际上是否分配了一个长度为 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 指向的位置的内存,直到遇到它解释为空字符的内容。

但是,如果我这样做:

// 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 分配的内存空间之外写入,因为str2[11] 是第12 个字节,但我们只分配了11 个字节?

运行此代码似乎不会导致任何编译器警告或运行时错误。这在实践中安全吗?使用malloc( strlen(str1) + 1 )而不是malloc( strlen(str1) )会更好吗?

【问题讨论】:

不,11 个字符之一确实是 \0... 开玩笑 :-) 既然你在学习,重要的是你从一开始就知道你正在学习的东西的名称。表达式char* str1 = "hello world"; 没有定义字符数组,而是一个指向文字的指针(顺便说一下,从const char*char* 的转换已被弃用,因此编译器应该已经警告过你了)。 literal 本身是一个由 constant 字符组成的 array,带有一个空终止符,但您定义的变量是一个 pointer. 【参考方案1】:

对于字符串文字,编译器实际上为\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';

在这里,您实际上是在写超出您分配的内存的末尾。在分配 X 元素时,您可以访问的最后一个合法字节是由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 函数将使用空终止符填充所有额外的空格。这里str1中只有size - 1元素,所以数组中的最后一个元素是不需要的,将用\0填充

【讨论】:

在您的示例中明确定义size_t size = strlen(str1) + sizeof(char); 的目的是什么?只使用malloc(strlen(str1)+1) 可以吗,因为我们知道 char 是 1 个字节? @JohnMahoney 我使用 size 本地有两个原因。首先是性能。 strlen 函数虽然不贵,但是 O(N) 并且由于字符串不会更改,因此没有理由多次运行它。 + sizeof(char) 部分主要是风格。 + 1 做同样的事情我只是更喜欢更明确的sizeof(char) 表示法 更好:char *str2 = malloc(str1) + 1); if (str2 == NULL) /* handle allocation failure */ strcpy(str2, str1);sizeof (char) is 1 by definition. strncpy` 在这种情况下恰好可以工作,但它只是strcpy 的“更安全”版本。【参考方案2】:

它实际上是否分配了一个长度为 12 而不是 11 的数组,第 12 个字符是 '\0'?

是的。

但是写入str2[11] 是否意味着我们在str2 分配的内存空间之外进行写入,因为str2[11] 是第12 个字节,但我们只分配了11 个字节?

是的。

使用malloc( strlen(str1) + 1 ) 代替malloc( strlen(str1) ) 会更好吗?

是的,因为第二种形式不够长,无法将字符串复制到其中。

运行此代码似乎不会导致任何编译器警告或运行时错误。

除了最简单的情况外,在所有情况下都检测到这一点是一个非常困难的问题。所以编译器的作者根本不用费心。


这种复杂性正是您在编写 C++ 时应该使用 std::string 而不是原始 C 样式字符串的原因。就这么简单:

std::string str1 = "hello world";
std::string str2 = str1;

【讨论】:

【参考方案3】:

文字 "hello world" 是一个 char 数组,如下所示:

 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' 

所以,是的,字面量的大小是 12 chars。

另外,malloc( strlen(str1) ) 分配的内存比需要的少 1 个字节,因为 strlen 返回字符串的长度,不包括 NUL 终止符。写入str[strlen(str1)] 是在写入超过您分配的内存量的 1 个字节。

您的编译器不会告诉您,但如果您通过 valgrind 或系统上可用的类似程序运行程序,它会告诉您是否正在访问您不应该访问的内存是。

【讨论】:

【参考方案4】:

我认为您对strlen 的返回值感到困惑。它返回字符串的长度,不应与保存字符串的数组的大小混淆。考虑这个例子:

char* str = "Hello\0 world";

我在字符串中间加了一个空字符,完全有效。这里数组的长度为 13(12 个字符 + 最后一个空字符),但 strlen(str) 将返回 5,因为在第一个空字符之前有 5 个字符。 strlen 只计算字符,直到找到一个空字符。

所以如果我使用你的代码:

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,并且不会以空字符结尾(因为 strlen 不算在内)。这是您的预期吗?

【讨论】:

Similar Question【参考方案5】:

对于标准 C 字符串,存储字符串的数组的长度总是比字符串长度(以字符为单位)长一个字符。因此,您的 "hello world" 字符串的字符串长度为 11,但需要一个包含 12 个条目的后备数组。

原因很简单,就是读取这些字符串的方式。处理这些字符串的函数基本上是一个接一个地读取字符串的字符,直到找到终止字符'\0' 并在此时停止。如果缺少该字符,则这些函数只需继续读取内存,直到它们到达导致主机操作系统终止您的应用程序的受保护内存区域,或者直到它们找到终止字符。

另外,如果你初始化一个长度为 11 的字符数组并将字符串"hello world" 写入其中会产生大量问题。因为该数组预计至少包含 12 个字符。这意味着内存中数组后面的字节被覆盖。导致不可预知的副作用。

此外,当您使用 C++ 时,您可能需要查看std:string。如果您使用 C++ 并提供更好的字符串处理,则可以访问此类。这可能值得研究。

【讨论】:

【参考方案6】:

我认为您需要知道的是 char 数组从 0 开始一直到数组长度为 1 并且位置数组长度具有终止符('\0')。 在你的情况下:

str1[0] == 'h';  
str1[10] == 'd';  
str1[11] == '\0';  

这就是为什么正确 str2[strlen(str1)] = '\0'; strncpy 之后输出的问题是因为它复制了 11 个元素(0..10),所以您需要手动放置终止符(str2[11] = '\0')。

【讨论】:

以上是关于C++ char 数组空终止符位置的主要内容,如果未能解决你的问题,请参考以下文章

哪些语言标准允许忽略固定大小数组上的空终止符?

C ++中的非空终止字符数组

初始化char数组以保存非空终止字符串[重复]

我正在尝试计算 char 数组中的内容直到空终止,但是每次编译时我都会得到一个大于数组的数字

是 char 空终止符是不是包含在长度计数中

通过 PAnsiChar (char *) 传递 AnsiString 是不是明确需要空终止符?