欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-16 13:56:22
...

简单来讲,在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中printf()的压栈问题
但如果在CLion或者是Code::Blocks上编译时,运行结果就会变成:
VC6.0中printf()的压栈问题
VC6.0中printf()的压栈问题

出现上述结果的主要原因是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中printf()的压栈问题
说明运行结束后,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中printf()的压栈问题

其他

以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)
VC6.0中printf()的压栈问题
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 ↩︎