当我们在C中的字符串末尾不包含’\ 0’时会发生什么?

在C中,当我以这种方式初始化我的数组时:

char full_name[] = { 't', 'o', 'a', 'n' }; 

并使用printf("%s", full_name);打印printf("%s", full_name);

valgrind运行它我得到了错误

未初始化的值是通过堆栈分配创建的

为什么会这样?

由于%s格式说明符需要以空字符结尾的字符串,因此未定义代码的结果行为。 您的程序被认为是不正确的,可以产生任何输出,不产生输出,崩溃等等。 简而言之,不要这样做。

这并不是说所有字符数组都必须以空值终止:该规则仅适用于打算用作C字符串的字符数组,例如,要传递给%s格式说明符上%s printf ,或传递给strlen或标准C库的其他字符串函数。

如果您打算将char数组用于其他内容,则不需要将其终止。 例如,这个用法是完全定义的:

 char full_name[] = { 't', 'o', 'a', 'n' }; for (size_t i = 0 ; i != sizeof(full_name) ; i++) { printf("%c", full_name[i]); } 

如果您没有在逗号分隔的大括号括号初始化列表的末尾提供'\0' ,从技术上讲, full_name不是字符串 ,因为char数组不是以空值终止的。

只是为了清除一些东西,不像初始化程序是字符串文字,逗号分隔列表不会自动计数并将终止空字符放入数组中。

所以,如果有像这样的定义

 char full_name[] = { 't', 'o', 'a', 'n' }; 

数组的大小是4,它有't''o''a''n'

OTOH,如果是的话

 char full_name[] = "toan"; 

full_name的大小为5,其中包含't''o''a''n''\0'

当你试图利用对字符串操作的任何函数使用前一个数组时(即,期望一个以null结尾的char数组),你会得到未定义的行为,因为大多数字符串函数在搜索null时会越界终止子。

在您的特定示例中,对于带有printf() %s格式说明符,引用C11标准,章节§7.21.6.1, fprintf()函数说明( 强调我的

s
如果不存在l length修饰符,则参数应为指向字符类型数组的初始元素的指针。 280) 来自数组的字符被写入(但不包括)终止空字符 。 如果指定了精度,则不会写入多少个字节。 如果未指定精度或大于数组的大小,则数组应包含空字符。

这意味着, printf()将寻找一个空终止符来标记/理解数组的结尾。 在您的示例中,缺少null终止符将导致printf()超出分配的内存( full_name[3] )并访问将导致UB的超出内存( full_name[4] )。

如果使用非空终止的char序列作为字符串,C函数将继续运行。 它是’\ 0’告诉他们停止。 因此,无论在序列中将序列作为字符串的一部分发生在内存中。 这可能最终会越过内存边界并导致错误,或者如果恰好在某处找到’\ 0’并停止,则可能只是打印乱码。

printf将“%s”解释为标准C字符串。 这意味着生成的代码将只是继续读取字符,直到找到空终止符(\ 0)。

通常这意味着这个漂移的指针会冒险进入未知的内存,而Valgrind会注意到这是一个错误。

如果您打算在某个时刻将其用作字符串,则必须在初始化char数组时显式添加自己的null终止符。

在将指令指针传递给期望ac字符串的函数之前,您隐式输入与该代码块具有法律约束力的合同。 在本合同的主要部分,双方同意避免交换专用的字符串长度信息,并断言声明为字符串的所有传递参数都指向由\0终止的字符序列,这使得每一方都可以选择计算长度。

如果您不包含终止\0您将违反合同。

操作系统法庭将随意起诉您的可执行文件,甚至是死亡。