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

时间:2019-06-25 17:57:07

标签: c arrays printf c-strings string-literals

我很久没有写C代码了,我很生锈。有人知道为什么以下代码将“ rtyaze”打印到stdout吗?我期待“ rty”。

#include <stdio.h>

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

6 个答案:

答案 0 :(得分:4)

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

所以printf继续直到找到空终止符。偶然地,您的其他字符串必须已经在第一个字符串之后立即放入内存中。

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

答案 1 :(得分:3)

您的数组声明不会为终止的空字符留出空间,因此"rty"的末尾没有空字符。由于%s格式要求以空值结尾的字符串作为参数,因此您将导致未定义的行为。

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

将声明更改为:

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

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

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

在某些情况下,char s[] = "aze";定义的另一个对象可能会在内存中紧随ssprintf的后面,而它试图打印字符串时,可能会继续超出r,t和y来打印a,z和e,然后找到空终止符。

在其他情况下,其他对象s可能不跟随内存中的ss。编译器可能在优化过程中删除了s,因为它没有被使用,因此在程序中不需要。否则编译器可能会将其放置在其他位置。在这种情况下,printf可能会继续到其他内存并打印不同的字符,或者可能会继续到不可访问的内存并导致段冲突或其他程序终止。

在其他情况下,编译器可能会认识到printf调用是由于缺少终止空字符而未定义的,并且可能会从程序中删除printf调用完全是因为C标准允许C实现将所需的任何行为替换为未定义的行为。

最终,行为不是由C标准定义的。

答案 4 :(得分:2)

格式说明符%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', '\0' }
  |___________|  |_________________|
      ss                  s

请注意该声明

char s[] = "aze";

等同于

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

即字符串文字包含结尾的零,因此数组s将包含一个字符串。

另外,您应该知道这样的声明

char ss[][3] = { "rty" };
在C ++中,不允许

。在C ++中,您至少必须像

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

答案 5 :(得分:0)

我运行了这个实验:

#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"

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

我写这个示例程序是因为有时一个实际的示例可以使这种行为的可视化变得更容易。