为啥我不能用动态参数设置 printf 的输出格式?
Posted
技术标签:
【中文标题】为啥我不能用动态参数设置 printf 的输出格式?【英文标题】:Why can't I set printf's output format with dynamic args?为什么我不能用动态参数设置 printf 的输出格式? 【发布时间】:2015-08-01 06:41:30 【问题描述】:我想用动态参数控制printf()
函数的输出格式,如下代码所示:
#include<stdio.h>
int main(int argc,char ** argv)
printf(argv[1],"hello,world");
return 0;
然后我编译并运行它:
$ gcc -o test test.c
$ ./test "\t%s\n"
结果很奇怪:
\thello,world\n$
为什么"\n"
和"\t"
没有效果?
【问题讨论】:
好吧,你显然可以用那个 arg 设置 printf 的格式,你仍然有你的 \t 和 \n。问题是这些字符似乎不被解读为制表和换行。另外,不要使用用户输入的第一个参数调用 print,这是一个 huuuuuge 安全漏洞!!! 例如见format string vulnerability :) 这个问题(或其答案)实际上不是关于 C 或printf
,而是关于为什么你的 shell 不以与 C 编译器相同的方式解释字符串内部的 \t
和 \n
确实。
【参考方案1】:
因为您使用的转义符(\t
和 \n
)是由 C 编译器而不是 printf()
在字符串文字中解释的。这个:
const char *newline1 = "\n", newline2[] = '\n', 0 ;
将在newline1
和newline2
中生成完全相同的内容,无论这些内容是否曾经传递给printf()
;无论如何,字符串都在那里。
您的代码的行为就像这样:
printf("\\t%s\\n", "hello,world");
在这里,我对特殊字符进行了双重转义,以生成与命令行参数具有相同实际内容的字符串,即"\t%s\n"
(六个字符而不是四个字符)。
动态控制printf()
的正确方法是在代码中构建格式字符串。如果您想在运行时进行类似 C 的转义,则需要自己以某种方式对其进行解释。
【讨论】:
如果希望 shell 解释特殊的转义序列,可以在反引号之间使用另一个命令来首先解释转义并将其作为字符串传递给程序。【参考方案2】:C/C++ 中字符串或字符文字中的序列\n
是单个字节,数值为10(在ASCII 系统上)。当在终端上输出时(试试putchar(10)
!)它只是将终端上下一个字符的输出位置设置为下一行的开头(在*nix上;我认为在MacOS上,你需要一个额外的\r
, 或 13 用于回车以使输出位置位于行首)。
同样,\t
是值为 9 的单个字节的表示法,这使得大多数终端将光标前进到下一个制表符位置。
您需要在命令行中插入这些值的一个字节。如何做到这一点取决于您的外壳;在 bash 中,您可以通过预先按 Ctrl-V 来阻止 shell 解释特殊字符。输出例如一个选项卡,通过显示一些空白空间来显示(而不是让 shell 显示可能的字符串延续或 bash 中的任何选项卡)。单引号或双引号中的 bash 字符串可以包含换行符而无需进一步努力——只需按 Enter。
这是在 cygwin 终端中使用 bash 运行的示例。我在指示位置按下指示键;我像往常一样在第二行结束单引号后用 [return] 完成了命令。
pressed Ctrl-v,[TAB] here | pressed [return] there
v v
$ ./printf-arg.exe ' %s
> '
hello,world
第二行中的>
是我在单引号分隔的字符串中按回车后由shell 输出的。 (在字符串中插入换行符)。这表明正在编辑的字符串在该行继续。
顺便说一句,在潜在的敌对环境中以这种方式使用命令行参数可能是不安全的。精心制作的字符串可以访问不应访问的内存,例如重定向返回地址,从而破坏程序。
【讨论】:
【参考方案3】:这是因为编译器会处理 "\n"
等转义序列,而且它只在字符串或字符文字中处理。
【讨论】:
【参考方案4】:如果您将解释的“\t%s\n”传递给命令,它将起作用。然而,在 shell 中构造这样的字符串是很棘手的。我知道的最简单的方法是:
./test $'\t%s\n'
对于$'magick'
,请参阅man bash 中的ANSI 引用
【讨论】:
以上是关于为啥我不能用动态参数设置 printf 的输出格式?的主要内容,如果未能解决你的问题,请参考以下文章
为啥visual+studio+code里面用c语言写的printf输出中终端显示问号?
c语言在 电脑上用scanf输入001,为啥printf输出显示是1,怎么把输出显示也变成001?