根据我的知识和一些线程like this,如果要在C中打印字符串,则必须执行以下操作:
printf("%s some text", value);
然后将显示该值,而不显示%s
。
我写了这段代码:
char password[] = "default";
printf("Enter name: \n");
scanf("%s", password);
printf("%s is your password", password); // All good - the print is as expected
但是我注意到,没有价值部分,我可以做完全相同的事情,它仍然可以工作:
printf("%s is your password");
所以我的问题是,为什么%s
占位符在没有我给它的情况下得到一个值,又怎么知道给它一个什么值?
答案 0 :(得分:9)
这是未定义的行为,任何可能发生的事情都包括看起来正确的事情。但是这是错误的。 如果使用正确的选项,编译器可能会告诉您问题。
标准说(强调是我的):
7.21.6.1 fprintf 函数
- fprintf函数将输出写入流指向的流, 在格式所指向的字符串的控制下,该字符串指定了 后续参数将转换为输出。 如果有 如果格式的参数不足,则行为未定义。 在保留参数的同时,格式已用尽,多余的参数 (一如既往)被评估,但被忽略。 fprintf 遇到格式字符串的结尾时,函数将返回。
答案 1 :(得分:4)
printf()
函数使用C语言功能,使您可以将可变数量的参数传递给函数。 (在技术上称为“可变函数”-https://en.cppreference.com/w/c/variadic-我将简称为“ varargs”。)
在C语言中调用函数时,该函数的参数将被压入stack(*)-但是varargs功能的设计无法让被调用函数知道传入了多少参数。
执行printf()
函数时,它将扫描格式字符串,然后%s告诉它在变量参数列表的下一个位置中查找字符串。由于列表中没有 个参数,因此代码“从数组末尾移出”并获取它在内存中看到的下一个内容。我怀疑发生了什么事,因为您先前对password
的调用中,内存中的下一个位置仍然具有scanf
的地址,并且由于该地址指向一个字符串,因此您告诉printf
打印一个字符串,您很幸运,并且有效。
尝试在调用printf("%s %s %s\n","X","Y","Z")
和scanf("%s", password);
之间插入另一个函数调用(例如:printf("%s is your password");
),几乎可以肯定会看到不同的行为。
免费的建议:C有很多尖角和未定义的位,但是一个好的编译器(和静态分析或'lint'工具)可以警告您许多常见的错误。如果您要使用C进行工作,请学习如何将编译器警告最大化,了解所有错误和警告的含义(发生时,并非一次全部!),并强迫自己编写无需任何编译的C代码警告。它将为您节省很多不必要的麻烦。
(*)为了简单起见在此进行概括-有时可以在寄存器中传递参数,有时可以内联东西,等等等等。
答案 2 :(得分:3)
因此,有很多帖子告诉您您不应该printf("%s is your password");
,而您只是很幸运。我想从您的问题中您知道这一点。但是很少有人告诉您为什么幸运的可能原因。
要了解可能发生的情况,我们必须了解如何传递函数参数。函数的调用者必须将参数放在约定的位置,以便函数查找参数。因此,对于参数1 ... N,我们将这些位置称为r1
... rN
。 (这种协议是我们称为“函数调用约定”的一部分)
这意味着该代码:
scanf("%s", password);
printf("%s is your password",password);
可能会被编译器转换为该伪代码
r1="%s";
r2=password;
call scanf;
r1="%s is your password";
r2=password;
call printf;
如果您现在从printf
调用中删除第二个参数,您的伪代码将如下所示:
r1="%s";
r2=password;
call scanf;
r1="%s is your password";
call printf;
请注意,在call scanf;
之后,r2
可能未做修改,但仍设置为password
,因此call printf;
是有效的
您可能认为您通过消除r2=password;
分配之一发现了一种优化代码的新方法。这对于旧的“哑”编译器可能是正确的,但对于现代编译器则不是。
在安全的情况下,现代编译器已经可以执行此操作。而且它并不总是安全的。之所以不安全,可能是因为scanf
和printf
具有不同的调用约定,r2
可能已经被修改了,等等。
为了更好地了解编译器的功能,我建议您以不同的优化级别查看编译器的汇编输出。
请始终使用-Wall
进行编译。编译器通常会很擅长告诉您什么时候做蠢事。