简体   繁体   English

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

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

I didn't write C code for a long time, I'm rusty.好久没写C代码了,生疏了。 Anybody knows why the following code prints "rtyaze" to stdout ?有人知道为什么下面的代码将“rtyaze”打印到 stdout 吗? I was expecting "rty".我期待“rty”。

#include <stdio.h>

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

By making your string at the first element of ss have 3 characters you are eliminating the null terminator.通过使 ss 的第一个元素的字符串具有 3 个字符,您可以消除空终止符。

So printf continues until it finds a null terminator.所以 printf 继续直到它找到一个空终止符。 By chance, your other string must have been placed in memory right after your first one.偶然地,您的另一个字符串必须在您的第一个字符串之后立即放置在内存中。

If you change the 3 in ss[][3] to a 4 you should get expected behaviour.如果您将 ss[][3] 中的 3 更改为 4,您应该会得到预期的行为。

Your array declaration doesn't leave room for the terminating null character, so there's no null character at the end of "rty" .您的数组声明没有为终止空字符留出空间,因此"rty"末尾没有空字符。 Since %s format requires a null-terminated string as the argument, you're causing undefined behavior.由于%s格式需要以空字符结尾的字符串作为参数,因此您会导致未定义的行为。

In this case, the memory for s happened to be right after the memory for ss , so printf() printed it when it searched for the null terminator.在这种情况下, s的内存恰好在ss的内存之后,因此printf()在搜索空终止符时打印它。

Change your declaration to:将您的声明更改为:

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

A string in C consists of a sequence of characters terminated by a null byte. C 中的字符串由以空字节结尾的字符序列组成。 The elements of ss don't have enough room to store the given string which occupies 4 bytes including the null terminator. ss的元素没有足够的空间来存储给定的字符串,该字符串占用 4 个字节,包括空终止符。 When you then attempt to print ss[0] you read past the end of the array.当您然后尝试打印ss[0]您读取的内容超过了数组的末尾。 This invokes undefined behavior.这会调用未定义的行为。

Change the size of the second array dimension to 4 to leave enough space.将第二个数组维度的大小更改为 4 以留出足够的空间。

char ss[][3] = { "rty" }; defines an array of arrays of 3 char .定义一个由 3 个char组成的数组。 Since the number of arrays is not specified (nothing is inside [] ), it is determined by counting the initializers.由于未指定数组的数量( []内没有任何内容),它是通过对初始化程序进行计数来确定的。 There is just one initializer, the string literal "rty" .只有一个初始值设定项,即字符串文字"rty" Thus, the result is an array of 1 array of 3 char , which contains r, t, and y.因此,结果是一个由 3 个char组成的 1 个数组,其中包含 r、t 和 y。 Although the string literal "rty" implicitly contains a null character, the array is defined to explicitly contain just three characters, so the null character does not become part of the array.尽管字符串文字"rty"隐式包含一个空字符,但数组被定义为仅显式包含三个字符,因此空字符不会成为数组的一部分。

printf("%s\\n", ss[0]); passes the address of the first character of ss[0] to printf .ss[0]的第一个字符的地址传递给printf The resulting behavior is undefined because printf should be passed the first character of a string , which means a sequence of characters terminated by a null character, but ss[0] does not contain a null character.结果行为未定义,因为printf应该传递string的第一个字符,这意味着以空字符结尾的字符序列,但ss[0]不包含空字符。

On some occasions when you do this, the other object, defined by char s[] = "aze";在某些情况下,当你这样做时,另一个对象,由char s[] = "aze";定义char s[] = "aze"; may happen to follow ss in memory, and printf , while it is attempting to print the string, may continue beyond r, t, and y to print a, z, and e, after which it finds the null terminator.可能碰巧跟在内存中的ss之后,并且printf尝试打印字符串时,可能会继续超出 r、t 和 y 以打印 a、z 和 e,之后它会找到空终止符。

On other occasions when you do this, the other object, s , might not follow ss in memory.在其他情况下,当您这样做时,另一个对象s可能不会在内存中跟随ss The compiler might have removed s during optimization, since it is not used and hence is not needed in the program.编译器可能在优化期间删除了s ,因为它没有被使用,因此在程序中也不需要它。 Or the compiler might have put it in a different location.或者编译器可能将它放在不同的位置。 On such occasions, printf might continue on to other memory and print different characters, or it might continue on to inaccessible memory and cause a segment violation or other program termination.在这种情况下, printf可能会继续到其他内存并打印不同的字符,或者它可能会继续到不可访问的内存并导致段冲突或其他程序终止。

On yet other occasions when you do this, the compiler might recognize that the printf call is undefined due to the lack of a terminating null character, and it might remove the printf call from the program entirely, because the C standard allows a C implementation to substitute any behavior it wants for undefined behavior.在其他情况下,当您执行此操作时,编译器可能会识别出printf调用由于缺少终止空字符而未定义,并且它可能会从程序中完全删除printf调用,因为 C 标准允许 C 实现用它想要的任何行为代替未定义的行为。

Ultimately, the behavior is not defined by the C standard.最终,行为不是由 C 标准定义的。

The format specifier %s is used to output strings that is sequences of characters terminated with zero characters.格式说明符%s用于输出以零字符结尾的字符序列的字符串。

You declared the array the single (first) element of which does not contain a string.您声明的数组的单个(第一个)元素不包含字符串。

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

In fact the array is declared the following equivalent way实际上,该数组是按以下等效方式声明的

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

that is the terminating zero of the string literal was excluded from the list of initializers because the size of the internal array is equal to only 3.也就是说,字符串文字的终止零被排除在初始值设定项列表之外,因为内部数组的大小仅等于 3。

To output the array you could write要输出您可以编写的数组

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

explicitly specifying the number of characters you are going to output.明确指定要输出的字符数。

If you want to output it as a string you should enlarge it like如果要将其输出为字符串,则应将其放大

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

that to include the terminating zero of the string literal "rty" .包括字符串文字"rty"的终止零。

In case of the original program it seems that the compiler placed in the stack the arrays in the following order ss and then s .在原始程序的情况下,编译器似乎按以下顺序将数组放入堆栈中sss That is the memory allocated to the arrays looks the following way.即分配给数组的内存如下所示。

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

Pay attention to that this declaration注意这个声明

char s[] = "aze";

is equivalent to相当于

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

that is the string literal includes the terminating zero and consequently the array s will contain a string.即字符串文字包括终止零,因此数组s将包含一个字符串。

Also you should know that such a declaration你也应该知道这样的声明

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

is not allowed in C++.在 C++ 中是不允许的。 In C++ you have to write at least like在 C++ 中,你至少要这样写

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

The reason your program "prints a variable not passed as an argument" is that your "rty" is not null terminated.您的程序“打印未作为参数传递的变量”的原因是您的“rty”不是空终止的。 This causes printf to continue printing characters until if finds a null terminator.这会导致printf继续打印字符,直到找到空终止符为止。

I ran this experiment:我进行了这个实验:

#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 Output (LLVM) macOS 输出 (LLVM)

This is the stack layout

Linux Output (gcc) Linux 输出 (gcc)

This stack layout

Messing around with GDB on Linux showed that the variables were declared on the stack in a different order than in the code.在 Linux 上使用 GDB 表明,变量在堆栈上的声明顺序与代码中的顺序不同。 Specifically具体来说

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

I wrote this example program because sometimes a practical example makes it easier to visualize this kind of behavior.我编写了这个示例程序,因为有时一个实际的示例可以更容易地可视化这种行为。

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

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