C++字符数组null终止符位置

C++ char array null terminator location

本文关键字:位置 终止 null 字符 数组 C++      更新时间:2023-10-16

我是一名学习C++的学生,我正在努力了解以null结尾的字符数组是如何工作的。假设我定义了一个如下的字符数组:

char* str1 = "hello world";

正如预期的那样,strlen(str1)等于11,并且它是空终止的。

如果上面的char数组的所有11个元素都填充了字符"helloworld",C++将null终止符放在哪里?它是否真的分配了一个长度为12而不是11的数组,其中第12个字符是''?CPlusPlus.com似乎表明,11个中的一个需要是'',除非它确实分配了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的字符。

然而,如果我这样做:

// Null-terminate the second one
str2[strlen(str1)] = '';
// 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) )会更好吗?

在字符串文字的情况下,编译器实际上为元素保留了一个额外的char元素。

// Create a new char array
char* str2 = (char*) malloc( strlen(str1) );

这是新C程序员经常犯的错误。为char*分配存储时,您需要再分配字符数+1才能存储。这里没有分配额外的存储意味着这条线也是非法的

// Null-terminate the second one
str2[strlen(str1)] = '';

在这里,你实际上是在写你分配的内存的末尾。当分配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] = ''strncpy函数将使用空终止符填充所有额外的空格。这里str1中只有size - 1元素,因此数组中的最后一个元素是不需要的,将用 填充

它是否真的分配了一个长度为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;

文字"hello world"是一个char数组,看起来像:

{ 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '' }

所以,是的,文字的大小是12chars。

此外,由于strlen返回字符串的长度,不包括NUL终止符,因此malloc( strlen(str1) )正在为比所需少1个字节的字节分配内存。写入str[strlen(str1)]就是写入超出已分配内存量1个字节的内存。

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

我想你对strlen的返回值感到困惑。它返回字符串的长度,不应将其与包含字符串的数组的大小混淆。考虑这个例子:

char* str = "Hello world";

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

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

char* str1 = "Hello 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个条目的后备数组。

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

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

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

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

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

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