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

时间:2012-04-06 23:06:19

标签: c++ arrays char null-terminated

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

char* str1 = "hello world";

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

如果上面的char数组的所有11个元素都填充了字符“hello world”,那么C ++在哪里放置null终止符?它实际上是分配一个长度为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) )会不会更好?

6 个答案:

答案 0 :(得分:12)

对于字符串文字,编译器实际上为char元素保留了额外的\0元素。

// 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函数将使用null终止符填充所有额外空格。此处size - 1中只有str1元素,因此数组中的最后一个元素是不需要的,并将填充\0

答案 1 :(得分:6)

  

它实际上是分配一个长度为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;

答案 2 :(得分:2)

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

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

所以,是的,文字大小为12 char

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

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

答案 3 :(得分:2)

我认为您对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不计算它)。这是你所期望的吗?

答案 4 :(得分:1)

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

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

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

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

答案 5 :(得分:0)

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

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

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