VC6.0中printf()的压栈问题

简单来讲,在VC6.0中printf()是从右到左入栈的,而且如果函数语句中存在后++或者后- -时,++与- -将在最后出栈。
*后来发现,好像C-Free对printf()编译时也遵循同样的规则。

引入

首先,我们要知道,在Standard C中并没有对stdio.h库中函数
int __cdecl printf(const char * __restrict__ _Format,...);(以下简称为printf)的具体入栈方式做定义,所以会出现printf中如果带有自加自减运算的时候,使用不同的C编译器就会输出不同结果的情况。

现象

示例1

#include <stdio.h>

int main()
{
    int i=8;
    printf("%d, %d, %d, %d\n",i,--i,i,i--);
    return 0;
}

当上述代码运行在VC6.0上时,就会出现如下结果:
VC6.0_Test01
但如果在CLion或者是Code::Blocks上编译时,运行结果就会变成:
CLion_Test01
C::B_Test01

出现上述结果的主要原因是CLion和Code::Blocks目前使用的都是GCC(GNU Compiler Collection,GNU编译器套件)编译,所以结果一致;
但由于部分头文件是C99标准引入的新特性,微软的VC编译器不支持C99,那是不是VC就不能用了呢?好东西,当然人人眼馋,微软虽然表面上说不支持C99,但是有用的特性还是会引入,因此VS2010也引入了新版头文件,在VS2010及其以后的版本中,可以放心使用。但是要注意,VC6.0只是引入了这个新特性,而不是支持C99。1

解释

VC6.0的入栈方法

以上述例子为例,VC6.0编译printf函数的时候遵循两个原则:

  1. 从右往左入栈;
  2. 后自加后自减永远在栈底。

按第一点的逻辑,我们发现VC6.0在实际编译时应该如下表操作:

栈顶
i - -
i
- - i
i
栈底

依照栈的先进后出的特点,第一个计算的应该是i - -,而后自加后自减的执行与汇编的EBP寻址有关2,可以简单理解为在执行后自加或后自减时,i原来的值保存在了一个缓存中,然后再进行自减操作。
但是如果只是这样的话,按右序计算i, --i, i, i--再printf也应该输出6, 6, 7, 8才对,而这个错误的发生就与第二点规则有关;
用两个实例可以证明第二点规则:
修改原示例1为:

#include <stdio.h>

int main()
{
    int i=8;
    printf("%d, %d, %d, %d\n",i,--i,i,i--);
    printf("%d\n",i); //输出所有计算完成后的i的值
    return 0;
}

则结果为
VC6.0_Test02
说明运行结束后,i的值变成了6,所以我们可以把整个计算过程看成(i,i,--i,i)--;
这样就解释得通为什么在VC6.0中输出的结果是7,7,8,8而不是6, 6, 7, 8
如果还不能解释的话,就请修改示例1为:

#include <stdio.h>

int main()
{
    int i=8;
    printf("%d, %d, %d, %d\n",i,--i,i--,i--);
    printf("%d\n",i); //输出所有计算完成后的i的值
    return 0;
}

输出结果如下:
VC6.0_Test03

其他

以GCC编译器为基础的IDE(Integrated Development Environment,集成开发环境)们对printf的编译规律大致可以理解为:3

  1. 从右至左依次计算;
  2. 每个后自加/后自减都有一个独立的缓存空间;
  3. 所有计算结束,最后格式化输出。
验证

我们可以用命令行工具(cmd.exe)在.c文件所在路径下使用gcc -S -fverbose-asm Test01.c4把示例1以汇编语句混杂注释C语言语句输出。这样,在.c文件的同一路径下我们就得到了一个以ASM(ASseMbly,汇编)代码写的.s文件(可以直接用记事本打开,这里我用的是notepad++.exe)
ASM_Test01
5

Reference


  1. 程序员C语言快速上手——基础篇(三)——血色v残阳 ↩︎

  2. printf函数压栈(i++/i–,++i/–i) 终极解密——spring-go ↩︎

  3. 关于printf("%d,%d",i–,i++)的问题——问路1 ↩︎

  4. GCC -S选项:生成汇编文件——C语言中文网 ↩︎

  5. 汇编语言实例讲解(1)——niedong0816 ↩︎

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学渣戊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值