繁体   English   中英

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

[英]Why printf prints a variable not passed as argument?

好久没写C代码了,生疏了。 有人知道为什么下面的代码将“rtyaze”打印到 stdout 吗? 我期待“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格式需要以空字符结尾的字符串作为参数,因此您会导致未定义的行为。

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

将您的声明更改为:

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

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

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

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

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

在某些情况下,当你这样做时,另一个对象,由char s[] = "aze";定义char s[] = "aze"; 可能碰巧跟在内存中的ss之后,并且printf尝试打印字符串时,可能会继续超出 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"的终止零。

在原始程序的情况下,编译器似乎按以下顺序将数组放入堆栈中sss 即分配给数组的内存如下所示。

{ 'r', 't', 'y', 'a', 'z', 'e', '\0' }
  |___________|  |_________________|
      ss                  s

注意这个声明

char s[] = "aze";

相当于

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

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

你也应该知道这样的声明

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

在 C++ 中是不允许的。 在 C++ 中,你至少要这样写

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

您的程序“打印未作为参数传递的变量”的原因是您的“rty”不是空终止的。 这会导致printf继续打印字符,直到找到空终止符为止。

我进行了这个实验:

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

int main(void) {
    char end[] = "\0";
    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]
$8 = 0x7fffffffe287 "This stack layout"
(gdb) print &is[0]
$9 = 0x7fffffffe280 " is theThis stack layout"

我编写了这个示例程序,因为有时一个实际的示例可以更容易地可视化这种行为。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM