意外的 printf 输出

Ver*_*hol 11 c printf c-strings undefined-behavior output

我刚刚发现 C 编译器有非常奇怪的行为。这是非常简单的代码。我在很多在线C编译器中尝试过,但结果总是一样的,这让我抓狂。

#include <stdio.h>

int main()
{
    char Buffer[10] = "0123456789";
    char ID[5] = "abcde";
    printf("%s",ID);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

花点时间尝试预测函数的结果printf。如果你是像我一样的人,那么我认为最明显的解决方案是“abcde”,这是不正确的!但如果你以某种方式算出了“abcde0123456789”,那么你就在消耗电力来生活。

怎么、怎么可能?我只选择ID要打印的数组,那么为什么Buffer也用它打印呢?这没有道理。即使ID数组也不够大,无法容纳所有数据。我真的要失去理智了。

Vla*_*cow 15

格式规范%s需要一个指向字符串的指针:以零字符结尾的字符序列'\0'

然而两个数组

char Buffer[10] = "0123456789";
char ID[5] = "abcde";
Run Code Online (Sandbox Code Playgroud)

不包含字符串。因此,调用printf会调用未定义的行为。

你应该写

char Buffer[] = "0123456789";
char ID[] = "abcde";
Run Code Online (Sandbox Code Playgroud)

或者

char Buffer[11] = "0123456789";
char ID[6] = "abcde";
Run Code Online (Sandbox Code Playgroud)

请注意,字符串文字存储为添加零字符“\0”的字符数组。

例如这个声明

char ID[] = "abcde";
Run Code Online (Sandbox Code Playgroud)

事实上相当于

char ID[] = { 'a', 'b', 'c', 'd', 'e', '\0' };
Run Code Online (Sandbox Code Playgroud)

和这个声明

char ID[5] = "abcde";
Run Code Online (Sandbox Code Playgroud)

相当于

char ID[5] = { 'a', 'b', 'c', 'd', 'e' };
Run Code Online (Sandbox Code Playgroud)

即在最后一种情况下,零字符'\0'不用作数组的初始值设定项ID

如果要输出不包含字符串的字符数组,可以使用精度字段,例如

printf( "%.5s\n", ID );
Run Code Online (Sandbox Code Playgroud)

或者

printf( "%.*s\n", 5, ID );
Run Code Online (Sandbox Code Playgroud)

或者

printf( "%.*s\n", ( int )sizeof( ID ), ID );
Run Code Online (Sandbox Code Playgroud)

还要记住,与 C++ 中的 C 相反,这样的声明如下

char ID[5] = "abcde";
Run Code Online (Sandbox Code Playgroud)

是无效的。在 C++ 中,您不能忽略'\0'用作初始值设定项的字符串文字的终止零字符。否则初始化器的数量将超过初始化数组元素的数量。

  • @Vertinhol 更好的是,根本不要“添加额外的字节”。只需说 `char ID[] = "abcde";`,然后让计算机进行计数。计算字符是计算机最擅长的那种烦人的任务。 (2认同)

ana*_*ciu 9

的行为printf是未定义的,因为不能将其视为ID字符串(也称为空终止字符数组),在printf给定格式说明符的情况下,该函数%s依赖于该空终止符来知道在哪里停止打印。由于没有找到空字节,它会将数组溢出到相邻的内存中寻找它并打印其中的任何内容。碰巧的是,在该区域中是另一个 char 数组Buffer,这就是打印的内容,如果偶然在此连续内存的第一个字节中找到空字节,它可能是完全包括预期结果的其他内容,请注意定义未定义行为

使用不可移植或错误的程序结构或错误数据时的行为,本国际标准对此没有强加任何要求。

可能的未定义行为包括完全忽略导致不可预测结果的情况,到在翻译或程序执行期间以环境特有的记录方式表现[...]


我测试的大多数编译器和相应版本的行为确实与您按顺序描述和打印两个数组一样,但不是全部。正如您在此处看到的那样,这不是您可以依赖的模式:

https://godbolt.org/z/1E396Y3KG(经过优化的 gcc)

或者在这里:

https://godbolt.org/z/roa6GxWvr (msvc)

结果并不总是如此abcde0123456789


至于它没有空终止符('\0')的原因,是因为没有足够的空间,如果您将大小声明为具有额外的元素,它将由编译器自动添加:

char ID[6] = "abcde"; //will automatically append \0 to the char array
        ^
Run Code Online (Sandbox Code Playgroud)

省略大小实际上是一种更好的做法,编译器将推断出所需的大小,而无需计算字符数,因此不太容易出错:

char ID[] = "abcde";
Run Code Online (Sandbox Code Playgroud)

  • @Vertinhol “每台机器的结果都是相同的”这句话并不准确,正如我在链接代码中向您展示的那样。 (3认同)