C字符数组及其长度
C character array and its length
我现在正在使用 "C Programming Absolute Beginner's Guide"(第 3 版)学习 C,其中写道所有字符数组的大小都应等于 string length + 1
(字符串终止长度为零)。但是这段代码:
#include <stdio.h>
main()
{
char name[4] = "Givi";
printf("%s\n",name);
return 0;
}
输出 Givi
而不是 Giv
。数组大小为 4
,在这种情况下它应该输出 Giv
,因为 4(字符串长度)+ 1(字符串终止零字符长度)= 5,而字符数组大小仅为 4
.
为什么我的代码输出 Givi
而不是 Giv
?
我正在使用 MinGW 4.9.2 SEH 进行编译。
您击中了被认为是 undefined behavior 的东西。它现在正在工作,但由于偶然,而不是正确性。
你的情况是因为你的程序内存一开始就可能全部清零了。因此,即使您的字符串未正确终止,它之后的内存恰好为零,因此 printf
知道何时停止。
+-----------------------+
|G|i|v|i|[=10=]|[=10=]|... |
+-----------------------+
| your | rest of |
| stuff | memory (stack)|
+-----------------------+
其他语言,例如 Java,有针对这种情况的保护措施。然而,像 C 这样的语言,手牵手的事情更少,一方面,它允许更多的灵活性,但另一方面,给你更多的方式来解决诸如这个这样的微妙问题。换句话说,如果你的代码编译通过,那并不意味着它是正确的,它现在不会崩溃,5 分钟后或 5 年内。
在现实生活中,几乎从来没有这种情况,您的字符串可能最终会存储在其他东西旁边,而这些东西最终总是会与您的字符串一起打印出来。你永远不会想要这个。像这样的情况可能会导致崩溃、漏洞利用和机密信息泄露。
示例见下图。假设您正在 Web 服务器上工作,并且字符串 "secret"--用户的密码或密钥存储在您的无害字符串旁边:
+-----------------------+
|G|i|v|i|s|e|c|r|e|t |
+-----------------------+
| your | rest of |
| stuff | memory (stack)|
+-----------------------+
每次你输出你认为的"Givi",你最终会打印出秘密字符串,这不是你想要的。
下一行:
char name[4] = "Givi";
可能会发出如下警告:
string for array of chars is too long
因为行为是未定义,编译器仍然可以通过它。但是如果你调试,你会看到:
name[0] 'G'
name[1] 'i'
name[2] 'V'
name[3] '[=12=]'
所以输出是
Giv
不是你在问题中提到的给予!
我正在使用 GCC 编译器。
但是如果你这样写:
char name[4] = "Giv";
编译正常!输出是
Giv
你书上说的基本上是对的,就是少了一句"at least"。阵列可以更大。
您已经说明了最小长度要求的原因。那么关于这个例子,这告诉了你什么?这是废话!
它展示的内容称为 未定义行为 (UB),可能会导致守护程序飞出你的鼻子 printf()
- 而不是初始化程序。它只是没有被 C 标准涵盖(好吧,标准实际上说这是 UB),所以编译器(和你的库)预计不会正确运行。
对于这种情况,不会显式附加终止符,因此在传递给 `printf()" 时字符串未正确终止。
这不会产生错误的原因可能是一些遗留代码确实利用它来保护一些内存字节。因此,它不会报告隐式尾随 '[=11=]'
终止符不适合的错误,而只是不附加它。静默截断字符串文字也不是一个好主意。
最后一个字符后的字节必须始终为 0,否则 printf
将不知道字符串何时终止,并会尝试访问未终止的字节(或 char
s) 0.
正如 Andrei 所说,显然它只是发生了,编译器在您的字符串数据之后放置了至少一个值为 0 的字节,因此 printf
识别了字符串的结尾。
这可能因编译器而异,因此是未定义的行为。
例如,可能有机会 printf
访问您的程序不允许的地址。这会导致崩溃。
在 C 中,文本字符串存储为以零结尾的字符数组。这意味着文本字符串的结尾由特殊字符表示,数字值为零 (0),以指示字符串的结尾。
因此,用于存储 C 文本字符串的文本字符数组必须包括每个字符的数组元素以及字符串末尾的附加数组元素。
所有的 C 文本字符串函数(strcpy()
、strcmp()
、strcat()
等)都期望文本字符串的结尾由零值指示.这包括将文本打印或输出到屏幕或文件的 printf()
系列函数。由于这些函数依赖于看到一个零值来终止字符串,因此使用 C 文本字符串时的一个错误来源是由于缺少零终止符而复制了太多字符,或者将长文本字符串复制到较小的缓冲区中。这种类型的错误称为缓冲区溢出错误。
C 编译器会自动为您执行某些类型的调整。例如:
char *pText = "four"; // pointer to a text string constant, compiler automatically adds zero to an additional array element for the constant "four"
char text[] = "four"; // compiler creates a array with 5 elements and puts the characters four in the first four array elements, a value of 0 in the fifth
char text[5] = "four"; // programmer creates array of 5 elements, compiler puts the characters four in the first four array elements, a value of 0 in the fifth
在您提供的示例中,一个好的 C 编译器应该至少发出一个警告,并且可能是一个错误。但是,看起来您的编译器正在将字符串截断为数组大小,并且没有添加额外的零字符串终止符。你很幸运,因为字符串末尾后有一个零值。我想 C 编译器也有可能添加一个额外的数组元素,但这似乎不太可能。
我现在正在使用 "C Programming Absolute Beginner's Guide"(第 3 版)学习 C,其中写道所有字符数组的大小都应等于 string length + 1
(字符串终止长度为零)。但是这段代码:
#include <stdio.h>
main()
{
char name[4] = "Givi";
printf("%s\n",name);
return 0;
}
输出 Givi
而不是 Giv
。数组大小为 4
,在这种情况下它应该输出 Giv
,因为 4(字符串长度)+ 1(字符串终止零字符长度)= 5,而字符数组大小仅为 4
.
为什么我的代码输出 Givi
而不是 Giv
?
我正在使用 MinGW 4.9.2 SEH 进行编译。
您击中了被认为是 undefined behavior 的东西。它现在正在工作,但由于偶然,而不是正确性。
你的情况是因为你的程序内存一开始就可能全部清零了。因此,即使您的字符串未正确终止,它之后的内存恰好为零,因此 printf
知道何时停止。
+-----------------------+
|G|i|v|i|[=10=]|[=10=]|... |
+-----------------------+
| your | rest of |
| stuff | memory (stack)|
+-----------------------+
其他语言,例如 Java,有针对这种情况的保护措施。然而,像 C 这样的语言,手牵手的事情更少,一方面,它允许更多的灵活性,但另一方面,给你更多的方式来解决诸如这个这样的微妙问题。换句话说,如果你的代码编译通过,那并不意味着它是正确的,它现在不会崩溃,5 分钟后或 5 年内。
在现实生活中,几乎从来没有这种情况,您的字符串可能最终会存储在其他东西旁边,而这些东西最终总是会与您的字符串一起打印出来。你永远不会想要这个。像这样的情况可能会导致崩溃、漏洞利用和机密信息泄露。
示例见下图。假设您正在 Web 服务器上工作,并且字符串 "secret"--用户的密码或密钥存储在您的无害字符串旁边:
+-----------------------+
|G|i|v|i|s|e|c|r|e|t |
+-----------------------+
| your | rest of |
| stuff | memory (stack)|
+-----------------------+
每次你输出你认为的"Givi",你最终会打印出秘密字符串,这不是你想要的。
下一行:
char name[4] = "Givi";
可能会发出如下警告:
string for array of chars is too long
因为行为是未定义,编译器仍然可以通过它。但是如果你调试,你会看到:
name[0] 'G'
name[1] 'i'
name[2] 'V'
name[3] '[=12=]'
所以输出是
Giv
不是你在问题中提到的给予!
我正在使用 GCC 编译器。
但是如果你这样写:
char name[4] = "Giv";
编译正常!输出是
Giv
你书上说的基本上是对的,就是少了一句"at least"。阵列可以更大。
您已经说明了最小长度要求的原因。那么关于这个例子,这告诉了你什么?这是废话!
它展示的内容称为 未定义行为 (UB),可能会导致守护程序飞出你的鼻子 printf()
- 而不是初始化程序。它只是没有被 C 标准涵盖(好吧,标准实际上说这是 UB),所以编译器(和你的库)预计不会正确运行。
对于这种情况,不会显式附加终止符,因此在传递给 `printf()" 时字符串未正确终止。
这不会产生错误的原因可能是一些遗留代码确实利用它来保护一些内存字节。因此,它不会报告隐式尾随 '[=11=]'
终止符不适合的错误,而只是不附加它。静默截断字符串文字也不是一个好主意。
最后一个字符后的字节必须始终为 0,否则 printf
将不知道字符串何时终止,并会尝试访问未终止的字节(或 char
s) 0.
正如 Andrei 所说,显然它只是发生了,编译器在您的字符串数据之后放置了至少一个值为 0 的字节,因此 printf
识别了字符串的结尾。
这可能因编译器而异,因此是未定义的行为。
例如,可能有机会 printf
访问您的程序不允许的地址。这会导致崩溃。
在 C 中,文本字符串存储为以零结尾的字符数组。这意味着文本字符串的结尾由特殊字符表示,数字值为零 (0),以指示字符串的结尾。
因此,用于存储 C 文本字符串的文本字符数组必须包括每个字符的数组元素以及字符串末尾的附加数组元素。
所有的 C 文本字符串函数(strcpy()
、strcmp()
、strcat()
等)都期望文本字符串的结尾由零值指示.这包括将文本打印或输出到屏幕或文件的 printf()
系列函数。由于这些函数依赖于看到一个零值来终止字符串,因此使用 C 文本字符串时的一个错误来源是由于缺少零终止符而复制了太多字符,或者将长文本字符串复制到较小的缓冲区中。这种类型的错误称为缓冲区溢出错误。
C 编译器会自动为您执行某些类型的调整。例如:
char *pText = "four"; // pointer to a text string constant, compiler automatically adds zero to an additional array element for the constant "four"
char text[] = "four"; // compiler creates a array with 5 elements and puts the characters four in the first four array elements, a value of 0 in the fifth
char text[5] = "four"; // programmer creates array of 5 elements, compiler puts the characters four in the first four array elements, a value of 0 in the fifth
在您提供的示例中,一个好的 C 编译器应该至少发出一个警告,并且可能是一个错误。但是,看起来您的编译器正在将字符串截断为数组大小,并且没有添加额外的零字符串终止符。你很幸运,因为字符串末尾后有一个零值。我想 C 编译器也有可能添加一个额外的数组元素,但这似乎不太可能。