我一直在C中处理字符串。在处理声明和初始化字符串的方法时,我发现了一些我不理解的奇怪行为。
#include<stdio.h>
#include<string.h>
int main()
{
char str[5] = "World";
char str1[] = "hello";
char str2[] = {'N','a','m','a','s','t','e'};
char* str3 = "Hi";
printf("%s %zun"
"%s %zun"
"%s %zun"
"%s %zun",
str, strlen(str),
str1, strlen(str1),
str2, strlen(str2),
str3, strlen(str3));
return 0;
}
样本输出:
Worldhello 10
hello 5
Namaste 7
Hi 2
在某些情况下,上面的代码使str
包含Worldhello
,其余代码与初始化时一样。在其他一些情况下,上面的代码使str2
包含Namastehello
。它发生在我从未连接过的不同变量中。那么,它们是如何结合在一起的呢?
要处理字符串,必须在每个字符串的末尾留出一个空字符的空间。在有char str[5]="World";
的情况下,只允许使用五个字符,编译器会用"World"填充它们,但它们后面没有空字符的空间。尽管字符串文字"World"
在其末尾包含一个自动空字符,但您没有在数组中为其提供空间,因此不会复制它。
在有char str1[]="hello";
的情况下,编译器通过计算字符(包括字符串文字末尾的null字符)来确定数组大小。
在有字符str2[]={'N','a','m','a','s','t','e'};
的情况下,没有字符串文字,只有单个字符的列表。编译器通过对数组大小进行计数来确定数组大小。由于没有空字符,因此不为其提供空间。
未能用null字符终止字符串的一个潜在后果是,printf
将继续读取字符串之外的内存,并根据它找到的值打印字符。当编译器将其他字符数组放在要打印的数组之后时,这些数组中的字符可能会出现在输出中。
如果在str
中为空字符留出空间,并在str2
中提供零值,则程序将按顺序打印字符串:
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[6] = "World"; // 5 letters plus a null character.
char str1[] = "hello";
char str2[] = {'N', 'a', 'm', 'a', 's', 't', 'e', 0}; // Include a null.
char *str3 = "Hi";
printf("%s %zun%s %zun%s %zun%s %zun",
str, strlen(str),
str1, strlen(str1),
str2, strlen(str2),
str3, strlen(str3));
return 0;
}
非中未定义的行为-null终止、相邻存储的C字符串
你为什么得到这个部分:
Worldhello 10
hello 5
而不是这个?
World 5
hello 5
答案是printf()
打印字符,直到它碰到一个空字符,这是一个二进制零,通常写为' '
字符。而且,编译器恰好将包含hello
的字符数组放置在包含World
的字符数组之后。由于通过str[5]
显式强制将str
的大小设置为5
,因此编译器无法在字符串末尾放置自动空字符。因此,由于hello
恰好在World
之后(不保证是),并且printf()
正在打印,直到它看到二进制零,它打印了World
,没有看到终止的空字符,并继续在它之后的hello
字符串中。这导致它打印Worldhello
,然后只有当它看到hello
之后的终止字符时才停止,哪个字符串被正确地终止。
此代码依赖于未定义的行为,这是一个错误。这是不能依赖的。但是,这就是这个案例的解释。
在64位Linux机器上使用gcc在线运行:在线GDB:非null终止的C字符串中的未定义行为
@Eric Postpischil给出了一个很好的答案,并在这里提供了更多的见解。
来自C标签wiki:
根据ISO 9899标准(最新版本,9899:2018,除非另有规定——也用c89、c99、c11等标记特定于版本的请求)的定义,此标记应用于与C语言有关的一般问题。
你问了一个"如何"关于这些文件都没有定义的问题,所以在C的上下文中答案是不确定的。你只能通过不确定的行为来体验这种现象。
它们是如何组合在一起的?
没有这样的要求,即这些变量中的任何一个都是";组合的";或者直接位于彼此之后;试图观察那是不明确的行为。它可能会在您的机器上偶尔出现故障,或者使用其他机器或编译器等情况下,对您来说不约而同地工作(无论这意味着什么)。这纯属巧合,不可信赖。
在某些情况下,上面的代码会将str与Worldhello一起分配,其余部分则按原样分配。
在未定义行为的情况下,对代码的功能进行声明是没有意义的,正如您已经注意到的,功能是不稳定的。
我发现他们有一些奇怪的行为。
如果您想防止不稳定的行为,请停止通过越界访问数组来调用未定义的行为(即导致strlen
从数组末尾溢出)。
这些变量中只有一个可以安全地传递给strlen
;您需要确保数组包含一个null终止符。