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

时间:2016-01-25 14:33:58

标签: c string valgrind null-terminated

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

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

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

打印

并使用 valgrind 运行它我收到了错误

  

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

为什么会这样?

5 个答案:

答案 0 :(得分:6)

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

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

因此,如果是

这样的定义
char

数组的大小为4,它包含char full_name[] = { 't', 'o', 'a', 'n' }; 't''o''a'

OTOH,如果是

'n'

char full_name[] = "toan"; 的大小为5,其中包含full_name't''o''a''n'

当您尝试使用在字符串上运行的任何函数的前一个数组时(即,期望一个以null结尾的'\0'数组),您将获得{{3}因为大多数字符串函数将在搜索null-terminator时超出范围。

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

  

fprintf()如果不存在s长度修饰符,则参数应为指向初始值的指针   字符类型数组的元素。 280) 数组中的字符是   写入(但不包括)终止空字符。如果   指定精度,不超过写入的多个字节。 如果   精度未指定或大于数组的大小,数组应   包含空字符。

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

答案 1 :(得分:6)

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

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

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

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

答案 2 :(得分:1)

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

通常这意味着这个流浪的指针会冒险进入未知的内存,而Valgrind会将此视为错误。

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

如果您不包含终止\0,则会违反合同。

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