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上时,就会出现如下结果:
但如果在CLion或者是Code::Blocks上编译时,运行结果就会变成:
出现上述结果的主要原因是CLion和Code::Blocks目前使用的都是GCC(GNU Compiler Collection,GNU编译器套件)编译,所以结果一致;
但由于部分头文件是C99标准引入的新特性,微软的VC编译器不支持C99,那是不是VC就不能用了呢?好东西,当然人人眼馋,微软虽然表面上说不支持C99,但是有用的特性还是会引入,因此VS2010也引入了新版头文件,在VS2010及其以后的版本中,可以放心使用。但是要注意,VC6.0只是引入了这个新特性,而不是支持C99。1
解释
VC6.0的入栈方法
以上述例子为例,VC6.0编译printf函数的时候遵循两个原则:
- 从右往左入栈;
- 后自加后自减永远在栈底。
按第一点的逻辑,我们发现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;
}
则结果为
说明运行结束后,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;
}
输出结果如下:
其他
以GCC编译器为基础的IDE(Integrated Development Environment,集成开发环境)们对printf的编译规律大致可以理解为:3
- 从右至左依次计算;
- 每个后自加/后自减都有一个独立的缓存空间;
- 所有计算结束,最后格式化输出。
验证
我们可以用命令行工具(cmd.exe)在.c文件所在路径下使用gcc -S -fverbose-asm Test01.c
4把示例1以汇编语句混杂注释C语言语句输出。这样,在.c文件的同一路径下我们就得到了一个以ASM(ASseMbly,汇编)代码写的.s文件(可以直接用记事本打开,这里我用的是notepad++.exe)
5
Reference
上一篇: yycg项目第六天
下一篇: 文言文能编程了,口水战也开始了