为什么 printf 打印一个未作为参数传递的变量?

Why printf prints a variable not passed as argument?

好久没写C代码了,生疏了。任何人都知道为什么以下代码将 "rtyaze" 打印到标准输出?我期待 "rty"。

#include <stdio.h>

int main (void) {
  char s[] = "aze";
  char ss[][3] = { "rty" };
  printf("%s\n", ss[0]);
}

通过使 ss 的第一个元素处的字符串具有 3 个字符,您将消除空终止符。

所以 printf 继续,直到找到空终止符。碰巧,您的另一个字符串一定是在您的第一个字符串之后立即放入内存中的。

如果您将 ss[][3] 中的 3 更改为 4,您应该会得到预期的行为。

您的数组声明没有为终止空字符留出空间,因此 "rty" 末尾没有空字符。由于 %s 格式需要一个 null-terminated 字符串作为参数,因此您会导致未定义的行为。

在这种情况下,s 的内存恰好在 ss 的内存之后,因此 printf() 在搜索空终止符时将其打印出来。

将您的声明更改为:

char ss[][4] = { "rty" };

C 中的字符串由以空字节结尾的字符序列组成。 ss 的元素没有足够的空间来存储给定的字符串,该字符串占用 4 个字节,包括空终止符。当您随后尝试打印 ss[0] 时,您读取了数组的末尾。这会调用未定义的行为。

将第二个数组维度的大小更改为4以留出足够的空间space。

char ss[][3] = { "rty" }; 定义了 3char 个数组的数组。由于未指定数组的数量([] 中没有任何内容),因此通过计算初始化器来确定。只有一个初始值设定项,即字符串文字 "rty"。因此,结果是 1 个数组 char 的数组,其中包含 r、t 和 y。尽管字符串文字 "rty" 隐式包含一个空字符,但该数组被明确定义为只包含三个字符,因此空字符不会成为数组的一部分。

printf("%s\n", ss[0]);ss[0]第一个字符的地址传给printf。结果行为是未定义的,因为 printf 应该传递给 字符串 的第一个字符,这意味着以空字符终止的字符序列,但是 ss[0] 确实不包含空字符。

在某些情况下,当您执行此操作时,由 char s[] = "aze"; 定义的另一个对象可能恰好在内存中跟随 ssprintf,同时它正在尝试打印字符串,可能会继续超出 r、t 和 y 以打印 a、z 和 e,然后找到空终止符。

在其他情况下,当您执行此操作时,另一个对象 s 可能不会在内存中跟随 ss。编译器可能在优化期间删除了 s,因为它未被使用,因此在程序中不需要。或者编译器可能将它放在不同的位置。在这种情况下,printf 可能继续到其他内存并打印不同的字符,或者它可能继续到不可访问的内存并导致段冲突或其他程序终止。

在其他情况下,当您执行此操作时,编译器可能会识别出 printf 调用由于缺少终止空字符而未定义,并且它可能会从中删除 printf 调用整个程序,因为 C 标准允许 C 实现用它想要的任何行为替换未定义的行为。

最终,该行为未由 C 标准定义。

格式说明符 %s 用于输出以零字符结尾的字符序列的字符串。

您声明数组的单个(第一个)元素不包含字符串。

char ss[][3] = { "rty" };

实际上数组是用下面的等价方式声明的

char ss[][3] = { { 'r', 't', 'y' } };

即字符串文字的终止零被排除在初始值设定项列表之外,因为内部数组的大小仅等于 3。

要输出你可以写的数组

printf("%3.3s\n", ss[0]);

明确指定要输出的字符数。

如果你想将它输出为字符串,你应该像这样放大它

char ss[][4] = { "rty" };

包括字符串文字的终止零 "rty"

对于原始程序,编译器似乎按以下顺序将数组放入堆栈中 ss,然后是 s。那就是分配给数组的内存如下所示。

{ 'r', 't', 'y', 'a', 'z', 'e', '[=14=]' }
  |___________|  |_________________|
      ss                  s

注意这个声明

char s[] = "aze";

等同于

char s[] = { 'a', 'z', 'e', '[=16=]' };

即字符串文字包含终止零,因此数组 s 将包含一个字符串。

另外你应该知道这样的声明

char ss[][3] = { "rty" };

在 C++ 中是不允许的。在 C++ 中你至少要写成 like

char ss[][4] = { "rty" };

你的程序 "prints a variable not passed as an argument" 的原因是你的 "rty" 不是空终止的。这会导致 printf 继续打印字符,直到找到空终止符。

我运行这个实验:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char end[] = "[=10=]";
    char layout[7] = " layout";
    char stack[6] = " stack";
    char the[4] = " the";
    char is[3] = " is";
    char this[4] = "This";

    printf("%s\n", this);
    return 0;
}

macOS 输出 (LLVM)

This is the stack layout

Linux 输出 (gcc)

This stack layout

在 Linux 上使用 GDB 表明变量在堆栈上的声明顺序与代码中的顺序不同。具体

(gdb) print &this[0]
 = 0x7fffffffe287 "This stack layout"
(gdb) print &is[0]
 = 0x7fffffffe280 " is theThis stack layout"

我写这个示例程序是因为有时一个实际的例子可以更容易地形象化这种行为。