c#发展

首页 » 常识 » 问答 » printf为什么会有两种返回值
TUhjnbcbe - 2024/7/13 18:13:00

先来一个测验。下面的程序将返回什么?

intmain(){  printf("HelloWorld!\n");}

很简单,对吧?它必须是0,因为至少ISO/IEC:(又名“C99”),如果你这个健忘的程序员没有显式地返回一个值,main将隐式返回0:

5.1.2.2.3程序终止如果main函数的返回类型与int兼容,从初始调用到main函数的返回等价于用main函数返回的值作为参数调用exit函数;到达结束main函数的}返回值为0。

但是如果你不使用c99或更新的呢?您的编译器可能默认为早期版本,或者您可能从某个地方继承了CFLAGS中的-ansi。然后呢?

那么,你就可以得到每个程序员最喜欢的东西:nasaldemons,又名“未定义行为”:

3.4.3undefinedbehavior

在使用不可移植或错误的程序构造或错误数据时的未定义行为/错误行为,本国际标准对此不作任何要求。注意:可能的错误未定义行为范围从完全忽略情况而导致不可预测的结果,到在翻译或程序执行过程中以文档化的方式表现环境特征(有或没有发布诊断消息),到终止翻译或执行(发布诊断消息)。

但是等等,你不记得main返回上一个语句的返回值了吗?这里,让我们来试一试:

$catex.cintfunc(){  return42;}intmain(){  func();}$cc-ansiex.c$./a.out$echo$?42$

好的,证实了。所以我们的原始程序应该返回任何printf(“HelloWorld!“n”)。Printf(3)返回”打印的字符数”所以应该是...13对吗?好吧。

$catex.c#includestdio.hintmain(){  printf("HelloWorld!\n");}$cc-ansiex.c$./a.outHelloWorld!$echo$?10$

嗯......等等?为什么是10?也许我们不会数,所以让我们来验证一下:

$catex.c#includestdio.hintmain(){  inta=printf("HelloWorld!\n");  returna;}$cc-ansiex.c$./a.outHelloWorld!$echo$?13$

所以printf(“HelloWorld!N)确实返回了13。我们可以省略最后一个返回值a;语句,因为无论如何我们应该得到最后一个返回值:

$catex.c#includestdio.hintmain(){  inta=printf("HelloWorld!\n");}$cc-ansiex.c$./a.outHelloWorld!$echo$?13$

.还有..

$catex.c#includestdio.hintmain(){  inta=printf("HelloWorld!\n");  printf("%d\n",a);}$cc-ansiex.c$./a.outHelloWorld!13$echo$?3$

...显示最后一个printf调用产生了预期的返回值:“13n”的长度确实是3,这就是返回值。那么,为什么我们在最初的程序中又回到了10?

使用gdb(1)检查$rax寄存器中的返回值显示:

$cc-g-ansia.c$gdb-qa.outReadingsymbolsfroma.out(gdb)li1#includestdio.h23intmain(){4inta=printf("HelloWorld!\n");5printf("%d\n",a);6printf("HelloWorld!\n");7}(gdb)brmainBreakpoint1at0xc2:filea.c,line4.(gdb)runStartingprogram:/tmp/a.outBreakpoint1,main()ata.c:44inta=printf("HelloWorld!\n");(gdb)ir$raxrax0xffffffffffffffff-1(gdb)nHelloWorld!5printf("%d\n",a);(gdb)ir$raxrax0xd13(gdb)nprintf("HelloWorld!\n");(gdb)ir$raxrax0x33(gdb)nHelloWorld!7}(gdb)ir$raxrax0xa10(gdb)n0x08edin___start()(gdb)ir$raxrax0xa10(gdb)nSinglesteppinguntilexitfromfunction___start,whichhasnolinenumberinformation.[Inferior1(process)exitedwithcode](gdb)

好的,所有的事情都说得通,直到第6行。Printf(“HelloWorld!在第4行中返回13,但是printf(“HelloWorld!在第6行返回10。这怎么可能?

如果我告诉你..

没有Printf

通常在调试没有意义的东西时,我们需要确定我们看到的东西是不是我们认为我们看到的东西。我们编写了一些代码,然后运行了一个可执行文件,但是谁能说这个可执行文件使用了我们写入代码的指令呢?

C不是直译语言,而是编译语言,这意味着我们使用一个工具----编译器----将高级代码翻译成机器指令。如果您还记得编译器的各个阶段,您将注意到其中一个阶段包括代码优化。那么让我们来看看编译器到底生成了什么:

$catex.c#includestdio.hintmain(){printf("HelloWorld!\n");}$cc-ansi-Sex.c$nlex.s1.file"ex.c"2.text3.section.rodata4.LC0:5.string"HelloWorld!"6.text7.globlmain8.typemain,

function9main:10.LFB3:11.cfi_startproc12pushq%rbp13.cfi_def_cfa_offset.cfi_offset6,-movq%rsp,%rbp16.cfi_def_cfa_registermovl$.LC0,%edi18callputs19nop20popq%rbp21.cfi_def_cfa7,ret23.cfi_endproc24.LFE3:25.sizemain,.-main26.ident"GCC:(nb)7.5.0"$

就在这里,在第18行,我们看到我们调用的是puts(3),而不是printf(3)!也就是说,gcc已经决定替换printf(“HelloWorld!带有一个puts(“HelloWorld!”),puts(3)只返回“一个非负整数表示成功,EOF表示错误”。为什么海湾合作委员会要这么做?当我们调用“printf(”%dn”,a)时,它为什么没有这样做呢?

Gcc有许多启发式方法来决定何时用更有效的puts(3)调用替换printf(3)。简而言之:如果使用返回代码,就不会进行优化(这就解释了为什么“inta=printf(“HelloWorld!如果格式字符串是字符串或者只是“%s”、“string”,那么调用将被替换为调用puts(3)。

我们可以通过“-fno-builtin”标志关闭这个优化,然后观察预期的返回值以及在汇编中对printf的调用:

$cc-ansi-fno-builtinex.c$./a.outHelloWorld!$echo$?13$cc-S-ansiex.c-oputs.s$cc-S-fno-builtin-ansiex.c-oprintf.s$diff-buputs.sprintf.s---puts.s-10-:02:27.++++printf.s-10-:02:30.+

-2,7+2,7

.text.section.rodata.LC0:-.string"HelloWorld!"+.string"HelloWorld!\n".text.globlmain.typemain,

function

-15,7+15,8

movq%rsp,%rbp.cfi_def_cfa_register6movl$.LC0,%edi-callputs+movl$0,%eax+callprintfnoppopq%rbp.cfi_def_cfa7,8$

好吧,有时当我们认为我们调用printf(3)时,我们实际上得到了puts(3)——但为什么要调用puts(“HelloWorld!”)返回10?这个问题的答案其实很简单,可以在这里找到来源:

intputs(charconst*s){  size_tc;  struct__suiouio;  struct__sioviov[2];  constvoid*vs=s;  intr;  /*Thisavoids-Werror=nonnull-

1
查看完整版本: printf为什么会有两种返回值